An automated system that fetches upcoming programming contests from Codeforces and AtCoder, and syncs them directly to your Google Calendar. Never miss a contest again!
- Overview
- Features
- Tech Stack
- Project Structure
- Prerequisites
- Installation
- Google OAuth Setup
- Configuration
- Running the Application
- GitHub Actions (Automated Sync)
- API Endpoints
- How It Works
- Database Schema
- Troubleshooting
Contest Calendar is a Node.js application that automatically:
- Fetches upcoming contests from Codeforces and AtCoder APIs
- Creates Google Calendar events for each contest
- Tracks which contests have been added to prevent duplicates
- Runs automatically every 6 hours via GitHub Actions
The system can also be run locally via an Express server with REST API endpoints for manual control.
- Multi-Platform Support - Syncs contests from both Codeforces and AtCoder
- Automatic Scheduling - GitHub Actions workflow runs every 6 hours
- Duplicate Prevention - SQLite database tracks added contests to avoid duplicates
- Google Calendar Integration - Creates events with contest details, URLs, and reminders
- REST API - Manual endpoints for fetching and adding contests
- Persistent Storage - Database is committed to Git for persistence across workflow runs
- 30-Minute Reminders - Each calendar event includes a popup reminder
| Technology | Purpose |
|---|---|
| Node.js v20 | JavaScript runtime |
| Express.js v5 | REST API server |
| googleapis | Google Calendar API client |
| axios | HTTP client for API requests |
| sqlite3 | Lightweight database for tracking |
| node-cron | Background task scheduling |
| dotenv | Environment variable management |
| @qatadaazzeh/atcoder-api | AtCoder API wrapper |
contestCalendar/
├── .github/
│ └── workflows/
│ └── contest-sync.yml # GitHub Actions workflow
├── src/
│ ├── app.js # Express server entry point
│ ├── syncContests.js # Automated sync script
│ ├── config/
│ │ ├── auth.js # OAuth2 authentication setup
│ │ ├── credentials.json # Google OAuth credentials (gitignored)
│ │ └── token.json # OAuth tokens (gitignored)
│ ├── services/
│ │ ├── codeforcesService.js # Codeforces API integration
│ │ ├── atCoderService.js # AtCoder API integration
│ │ └── calendarService.js # Google Calendar API integration
│ ├── routes/
│ │ ├── codeforcesRoute.js # GET /api/codeforces
│ │ ├── addOnecfRound.js # GET /api/add-one-cf
│ │ ├── addAllCFRoute.js # GET /api/add-all-cf
│ │ └── addAllACRoute.js # GET /api/add-all-ac
│ └── utils/
│ ├── db.js # SQLite database initialization
│ ├── buildCalendarEvent.js # Calendar event builder
│ └── contestStore.js # Database queries
├── data.db # SQLite database file
├── package.json # Dependencies and scripts
├── .env # Environment variables (gitignored)
└── .gitignore # Git ignore rules
Before setting up the project, ensure you have:
- Node.js v20 or higher - Download
- Google Cloud Project - Console
- Google Calendar API enabled - In your Google Cloud project
- Git - For version control
git clone https://github.com/yourusername/contestCalendar.git
cd contestCalendarnpm installCreate a .env file in the project root:
PORT=8080
CLIENT_ID=your_google_client_id_hereTo interact with Google Calendar, you need to set up OAuth2 credentials.
- Go to Google Cloud Console
- Click Create Project and give it a name
- Select the project once created
- In the sidebar, go to APIs & Services > Library
- Search for "Google Calendar API"
- Click Enable
- Go to APIs & Services > OAuth consent screen
- Select External user type (or Internal if using Google Workspace)
- Fill in the required fields:
- App name:
Contest Calendar - User support email: Your email
- Developer contact: Your email
- App name:
- Click Save and Continue
- On the Scopes page, click Add or Remove Scopes
- Add
https://www.googleapis.com/auth/calendar - Save and continue through the remaining steps
- Go to APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Select Web application
- Add Authorized redirect URIs:
http://localhost:8080/oauth2callback
- Click Create
- Download the JSON file
- Rename the downloaded file to
credentials.json - Move it to
src/config/credentials.json
The file should look like this:
{
"web": {
"client_id": "your-client-id.apps.googleusercontent.com",
"project_id": "your-project-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "your-client-secret",
"redirect_uris": ["http://localhost:8080/oauth2callback"]
}
}- Run the authentication script:
node src/config/auth.js- A URL will be printed to the console
- Open the URL in your browser
- Sign in with your Google account
- Grant calendar access permissions
- You'll be redirected to
localhost:8080/oauth2callback - A
token.jsonfile will be created insrc/config/
| Variable | Description | Required |
|---|---|---|
PORT |
Server port (default: 8080) | No |
CLIENT_ID |
Google OAuth Client ID | Yes |
| File | Description |
|---|---|
src/config/credentials.json |
Google OAuth client credentials |
src/config/token.json |
OAuth access and refresh tokens |
Start the Express server for manual API access:
node src/app.jsThe server will start at http://localhost:8080
Run the sync script directly to add contests to your calendar:
node src/syncContests.jsThis will:
- Fetch all upcoming contests from Codeforces and AtCoder
- Check which contests haven't been added yet
- Create Google Calendar events for new contests
- Update the database to track added contests
The repository includes a GitHub Actions workflow that automatically syncs contests every 6 hours.
Encode your credentials files:
# On macOS/Linux
base64 -i src/config/credentials.json | tr -d '\n'
base64 -i src/config/token.json | tr -d '\n'
# On Windows (PowerShell)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("src/config/credentials.json"))
[Convert]::ToBase64String([IO.File]::ReadAllBytes("src/config/token.json"))Go to your repository Settings > Secrets and variables > Actions and add:
| Secret Name | Value |
|---|---|
GOOGLE_CREDENTIALS_B64 |
Base64-encoded credentials.json |
GOOGLE_TOKEN_B64 |
Base64-encoded token.json |
The workflow file (.github/workflows/contest-sync.yml) is already configured:
name: Sync Contests
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch: # Manual trigger
permissions:
contents: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install
- name: Create credentials
run: |
echo "${{ secrets.GOOGLE_CREDENTIALS_B64 }}" | base64 -d > src/config/credentials.json
echo "${{ secrets.GOOGLE_TOKEN_B64 }}" | base64 -d > src/config/token.json
- run: node src/syncContests.js
- name: Commit database
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if [ -n "$(git status --porcelain data.db)" ]; then
git add data.db
git commit -m "database persistence added"
git push
fiYou can manually trigger the workflow:
- Go to Actions tab in your repository
- Select "Sync Contests" workflow
- Click Run workflow
When running the Express server, these endpoints are available:
Fetches upcoming Codeforces contests.
Response:
[
{
"id": 1234,
"name": "Codeforces Round #900 (Div. 2)",
"startTimeSeconds": 1704067200,
"durationSeconds": 7200,
"phase": "BEFORE"
}
]Adds the next upcoming Codeforces contest to Google Calendar.
Response:
Added: Codeforces Round #900 (Div. 2)
Adds all upcoming Codeforces contests to Google Calendar.
Response:
Added 5 Codeforces contests to calendar
Adds all upcoming AtCoder contests to Google Calendar.
Response:
Added 3 AtCoder contests to calendar
OAuth2 callback endpoint. Used during initial authorization.
┌─────────────────┐ ┌─────────────────┐
│ Codeforces │ │ AtCoder │
│ API │ │ API │
└────────┬────────┘ └────────┬────────┘
│ │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ Service Layer │
│ (Fetch & Transform) │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ SQLite Database │
│ (Duplicate Check) │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ Google Calendar │
│ (Create Events) │
└───────────────────────┘
- Fetch: Retrieve upcoming contests from APIs
- Filter: Only process contests that haven't started yet
- Check: Query database to see if contest was already added
- Create: Build calendar event with:
- Title:
[Platform] Contest Name - Description: Contest URL
- Start/End times based on duration
- 30-minute popup reminder
- Title:
- Track: Save contest ID to database
Each event is created with:
{
summary: "[Codeforces] Round #900 (Div. 2)",
description: "https://codeforces.com/contest/1234",
start: {
dateTime: "2024-01-01T17:35:00",
timeZone: "Asia/Kolkata"
},
end: {
dateTime: "2024-01-01T19:35:00",
timeZone: "Asia/Kolkata"
},
reminders: {
useDefault: false,
overrides: [
{ method: "popup", minutes: 30 }
]
}
}The SQLite database (data.db) uses a single table:
CREATE TABLE added_contests (
id TEXT PRIMARY KEY, -- "cf-1234" or "ac-abc123"
platform TEXT, -- "Codeforces" or "AtCoder"
name TEXT, -- Contest name
start_time TEXT -- ISO 8601 timestamp
);| id | platform | name | start_time |
|---|---|---|---|
| cf-1234 | Codeforces | Round #900 (Div. 2) | 2024-01-01T17:35:00Z |
| ac-abc340 | AtCoder | ABC 340 | 2024-01-06T12:00:00Z |
- Ensure
credentials.jsonis insrc/config/ - Verify the file contains valid OAuth credentials
- Re-download credentials from Google Cloud Console
- Delete
src/config/token.json - Run
node src/config/auth.jsto re-authenticate - Update GitHub secrets with new base64-encoded token
- Go to Google Cloud Console
- Navigate to APIs & Services > Library
- Search for "Google Calendar API"
- Click Enable
- Check that secrets are properly set
- Verify base64 encoding is correct (no newlines)
- Check Actions logs for specific error messages
If duplicates appear in your calendar:
- Check
data.dbfor existing entries - Clear the database if needed:
rm data.db - Run sync again (will re-add all upcoming contests)
- Verify there are upcoming contests on Codeforces/AtCoder
- Check API responses in logs
- Ensure your calendar access is properly authorized
This project is open source and available under the MIT License.
Contributions are welcome! Feel free to:
- Fork the repository
- Create a feature branch
- Submit a pull request
If you encounter issues:
- Check the Troubleshooting section
- Open an issue on GitHub
- Include relevant error logs and configuration details