remove contests.json
This commit is contained in:
@@ -4,10 +4,10 @@ Guidance for AI coding agents working in this repository.
|
|||||||
|
|
||||||
## Project Shape
|
## Project Shape
|
||||||
|
|
||||||
- `fetch_contests.py` is the data generator. It fetches contests, normalizes records with `platform`, `title`, `start_time`, `end_time`, `status`, and `url`, then writes JSON files in the repository root.
|
- `fetch_contests.py` is the data generator. It fetches contests, normalizes records with `platform`, `title`, `start_time`, `end_time`, `status`, and `url`, then writes `contests_all.json` in the repository root.
|
||||||
- Current contest providers are Codeforces, AtCoder, UOJ, and Nowcoder.
|
- Current contest providers are Codeforces, AtCoder, UOJ, and Nowcoder.
|
||||||
- `.github/workflows/deploy.yml` is the GitHub Actions automation. It installs Python dependencies, runs `fetch_contests.py`, and commits generated JSON changes.
|
- `.github/workflows/deploy.yml` is the GitHub Actions automation. It installs Python dependencies, runs `fetch_contests.py`, and commits generated JSON changes.
|
||||||
- `server/index.php` is the serving/display layer. It reads the public raw `contests.json` and `contests_all.json`, caches them locally, and renders upcoming plus recently finished contests.
|
- `server/index.php` is the serving/display layer. It reads the public raw `contests_all.json`, caches it locally, and renders upcoming plus recently finished contests.
|
||||||
- User-facing documentation lives in [README.md](README.md) and [README_zh.md](README_zh.md). Keep both languages in sync.
|
- User-facing documentation lives in [README.md](README.md) and [README_zh.md](README_zh.md). Keep both languages in sync.
|
||||||
|
|
||||||
## Actions vs Server Responsibilities
|
## Actions vs Server Responsibilities
|
||||||
@@ -23,7 +23,7 @@ Guidance for AI coding agents working in this repository.
|
|||||||
When adding a new contest provider, check all relevant surfaces:
|
When adding a new contest provider, check all relevant surfaces:
|
||||||
|
|
||||||
- `fetch_contests.py`: add a `fetch_<provider>()` function, normalize records through `make_contest()`, add it to both output flows in `main()`, and preserve `status` semantics.
|
- `fetch_contests.py`: add a `fetch_<provider>()` function, normalize records through `make_contest()`, add it to both output flows in `main()`, and preserve `status` semantics.
|
||||||
- `server/index.php`: add the platform name to `$platform_class` and define a matching `.platform.<class>` CSS rule if the provider appears in `contests.json`.
|
- `server/index.php`: add the platform name to `$platform_class` and define a matching `.platform.<class>` CSS rule if the provider appears in `contests_all.json`.
|
||||||
- `README.md` and `README_zh.md`: update supported platforms, data-source notes, and any changed usage/output details.
|
- `README.md` and `README_zh.md`: update supported platforms, data-source notes, and any changed usage/output details.
|
||||||
- `AGENTS.md`: update this guidance if the provider introduces a new pattern, dependency, output contract, or operational caveat.
|
- `AGENTS.md`: update this guidance if the provider introduces a new pattern, dependency, output contract, or operational caveat.
|
||||||
- `.github/workflows/deploy.yml`: update only if the provider needs new Python dependencies, generated files, secrets, or schedule behavior.
|
- `.github/workflows/deploy.yml`: update only if the provider needs new Python dependencies, generated files, secrets, or schedule behavior.
|
||||||
@@ -32,8 +32,7 @@ When removing a provider, remove or adjust the same surfaces so stale platform l
|
|||||||
|
|
||||||
## Output Contracts
|
## Output Contracts
|
||||||
|
|
||||||
- `contests.json` is an object with `generated_at` and `contests`; `contests` contains contests that have not ended yet: `upcoming` and `running`.
|
- `contests_all.json` is an object with `generated_at` and `contests`; `contests` contains contests that have not ended yet (`upcoming` and `running`) plus contests that finished within the last 30 days.
|
||||||
- `contests_all.json` is an object with `generated_at` and `contests`; `contests` contains contests that finished within the last 30 days.
|
|
||||||
- JSON timestamps are Unix timestamps in seconds.
|
- JSON timestamps are Unix timestamps in seconds.
|
||||||
- Keep record keys stable unless the README files and server rendering are updated in the same change.
|
- Keep record keys stable unless the README files and server rendering are updated in the same change.
|
||||||
|
|
||||||
@@ -50,11 +49,10 @@ python fetch_contests.py
|
|||||||
Then inspect the generated files with `jq` when available, for example:
|
Then inspect the generated files with `jq` when available, for example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
jq '.contests | group_by(.platform) | map({platform: .[0].platform, count: length})' contests.json
|
|
||||||
jq '.contests | group_by(.status) | map({status: .[0].status, count: length})' contests_all.json
|
jq '.contests | group_by(.status) | map({status: .[0].status, count: length})' contests_all.json
|
||||||
```
|
```
|
||||||
|
|
||||||
For PHP-only presentation changes, also check that `server/index.php` still reads `contests.json` and handles unknown platforms with the default styling.
|
For PHP-only presentation changes, also check that `server/index.php` still reads `contests_all.json` and handles unknown platforms with the default styling.
|
||||||
|
|
||||||
## Documentation Rule
|
## Documentation Rule
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
en/[zh](README_zh.md)
|
en/[zh](README_zh.md)
|
||||||
|
|
||||||
Fetch programming contest schedules from several online judge platforms and export them as JSON files. The repository also includes a small PHP page that can read the generated JSON and display upcoming contests.
|
Fetch programming contest schedules from several online judge platforms and export them as JSON. The repository also includes a small PHP page that can read the generated JSON and display upcoming contests.
|
||||||
|
|
||||||
## Supported Platforms
|
## Supported Platforms
|
||||||
|
|
||||||
@@ -13,12 +13,11 @@ Fetch programming contest schedules from several online judge platforms and expo
|
|||||||
|
|
||||||
## Output Files
|
## Output Files
|
||||||
|
|
||||||
Running the fetch script writes JSON files to the repository root:
|
Running the fetch script writes one JSON file to the repository root:
|
||||||
|
|
||||||
- `contests.json`: contests that have not ended yet, including upcoming and currently running contests.
|
- `contests_all.json`: contests that have not ended yet, including upcoming and currently running contests, plus contests that finished within the last 30 days.
|
||||||
- `contests_all.json`: contests that finished within the last 30 days.
|
|
||||||
|
|
||||||
Each JSON file has this top-level shape. `generated_at` is the Unix timestamp in seconds when the file was written.
|
The JSON file has this top-level shape. `generated_at` is the Unix timestamp in seconds when the file was written.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -64,30 +63,30 @@ python fetch_contests.py
|
|||||||
After a successful run, the script prints counts similar to:
|
After a successful run, the script prints counts similar to:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Total upcoming contests: 13
|
Total active contests: 13
|
||||||
Total contests finished in last 30 days: 139
|
Total contests finished in last 30 days: 139
|
||||||
```
|
```
|
||||||
|
|
||||||
## Web Page
|
## Web Page
|
||||||
|
|
||||||
`server/index.php` reads the public raw GitHub URLs for `contests.json` and `contests_all.json`, caches them locally for 5 minutes, and renders upcoming contests with a client-side countdown plus recently finished contests. It also shows the JSON `generated_at` value as the last update time in the detected time zone. The page detects the browser/system language and time zone, syncs them to cookies, and uses them for localized text and contest times on later page loads. Unsupported languages fall back to English.
|
`server/index.php` reads the public raw GitHub URL for `contests_all.json`, caches it locally for 5 minutes, and renders upcoming contests with a client-side countdown plus recently finished contests. It also shows the JSON `generated_at` value as the last update time in the detected time zone. The page detects the browser/system language and time zone, syncs them to cookies, and uses them for localized text and contest times on later page loads. Unsupported languages fall back to English.
|
||||||
|
|
||||||
If you deploy it yourself, update this constant in `server/index.php` if your repository path or branch changes:
|
If you deploy it yourself, update this constant in `server/index.php` if your repository path or branch changes:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests.json');
|
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests_all.json');
|
||||||
```
|
```
|
||||||
|
|
||||||
The cache files are written next to `index.php` as `contests_cache.json` and `contests_all_cache.json`, so the web server process needs write permission for the `server/` directory.
|
The cache file is written next to `index.php` as `contests_all_cache.json`, so the web server process needs write permission for the `server/` directory.
|
||||||
|
|
||||||
## Automation
|
## Automation
|
||||||
|
|
||||||
A typical automation flow is:
|
A typical automation flow is:
|
||||||
|
|
||||||
1. Run `python fetch_contests.py` on a schedule.
|
1. Run `python fetch_contests.py` on a schedule.
|
||||||
2. Commit the updated JSON files.
|
2. Commit the updated JSON file.
|
||||||
3. Push them to GitHub.
|
3. Push them to GitHub.
|
||||||
4. Let `server/index.php` read the latest `contests.json` from GitHub raw content.
|
4. Let `server/index.php` read the latest `contests_all.json` from GitHub raw content.
|
||||||
|
|
||||||
For GitHub Actions, install the Python dependencies before running the script.
|
For GitHub Actions, install the Python dependencies before running the script.
|
||||||
|
|
||||||
|
|||||||
+9
-10
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[en](README.md)/zh
|
[en](README.md)/zh
|
||||||
|
|
||||||
从多个在线评测平台抓取编程竞赛日程,并导出为 JSON 文件。仓库中还包含一个简单的 PHP 页面,可以读取生成的 JSON 并展示即将到来的比赛。
|
从多个在线评测平台抓取编程竞赛日程,并导出为 JSON。仓库中还包含一个简单的 PHP 页面,可以读取生成的 JSON 并展示即将到来的比赛。
|
||||||
|
|
||||||
## 支持平台
|
## 支持平台
|
||||||
|
|
||||||
@@ -13,12 +13,11 @@
|
|||||||
|
|
||||||
## 输出文件
|
## 输出文件
|
||||||
|
|
||||||
运行抓取脚本后,会在仓库根目录写入以下 JSON 文件:
|
运行抓取脚本后,会在仓库根目录写入一个 JSON 文件:
|
||||||
|
|
||||||
- `contests.json`:尚未结束的比赛,包括尚未开始和正在进行中的比赛。
|
- `contests_all.json`:尚未结束的比赛,包括尚未开始和正在进行中的比赛,以及最近 30 天内已经结束的比赛。
|
||||||
- `contests_all.json`:最近 30 天内已经结束的比赛。
|
|
||||||
|
|
||||||
每个 JSON 文件的顶层结构如下。`generated_at` 是文件写入时的秒级 Unix 时间戳。
|
JSON 文件的顶层结构如下。`generated_at` 是文件写入时的秒级 Unix 时间戳。
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -64,21 +63,21 @@ python fetch_contests.py
|
|||||||
成功运行后,脚本会输出类似下面的统计信息:
|
成功运行后,脚本会输出类似下面的统计信息:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Total upcoming contests: 13
|
Total active contests: 13
|
||||||
Total contests finished in last 30 days: 139
|
Total contests finished in last 30 days: 139
|
||||||
```
|
```
|
||||||
|
|
||||||
## 网页展示
|
## 网页展示
|
||||||
|
|
||||||
`server/index.php` 会读取公开的 GitHub Raw 地址中的 `contests.json` 和 `contests_all.json`,在本地缓存 5 分钟,并渲染带客户端倒计时的即将开始比赛,以及最近已结束的比赛。页面还会读取 JSON 中的 `generated_at`,按检测到的时区显示最后更新时间。页面会识别浏览器/系统语言和时区,将其同步到 cookie,并在后续加载时用于本地化文案和比赛时间显示。不支持的语言会回退到英语。
|
`server/index.php` 会读取公开 GitHub Raw 地址中的 `contests_all.json`,在本地缓存 5 分钟,并渲染带客户端倒计时的即将开始比赛,以及最近已结束的比赛。页面还会读取 JSON 中的 `generated_at`,按检测到的时区显示最后更新时间。页面会识别浏览器/系统语言和时区,将其同步到 cookie,并在后续加载时用于本地化文案和比赛时间显示。不支持的语言会回退到英语。
|
||||||
|
|
||||||
如果你自行部署,并且仓库路径或分支发生变化,需要修改 `server/index.php` 中的这个常量:
|
如果你自行部署,并且仓库路径或分支发生变化,需要修改 `server/index.php` 中的这个常量:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests.json');
|
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests_all.json');
|
||||||
```
|
```
|
||||||
|
|
||||||
缓存文件会以 `contests_cache.json` 和 `contests_all_cache.json` 的名字写在 `index.php` 同目录下,因此 Web 服务器进程需要拥有 `server/` 目录的写入权限。
|
缓存文件会以 `contests_all_cache.json` 的名字写在 `index.php` 同目录下,因此 Web 服务器进程需要拥有 `server/` 目录的写入权限。
|
||||||
|
|
||||||
## 自动化
|
## 自动化
|
||||||
|
|
||||||
@@ -87,7 +86,7 @@ define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fe
|
|||||||
1. 定时运行 `python fetch_contests.py`。
|
1. 定时运行 `python fetch_contests.py`。
|
||||||
2. 提交更新后的 JSON 文件。
|
2. 提交更新后的 JSON 文件。
|
||||||
3. 推送到 GitHub。
|
3. 推送到 GitHub。
|
||||||
4. 让 `server/index.php` 从 GitHub Raw 读取最新的 `contests.json`。
|
4. 让 `server/index.php` 从 GitHub Raw 读取最新的 `contests_all.json`。
|
||||||
|
|
||||||
如果使用 GitHub Actions,请在运行脚本前安装 Python 依赖。
|
如果使用 GitHub Actions,请在运行脚本前安装 Python 依赖。
|
||||||
|
|
||||||
|
|||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
{
|
|
||||||
"generated_at": 1784551373,
|
|
||||||
"contests": [
|
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "2026牛客暑期多校训练营2",
|
|
||||||
"start_time": 1784692800,
|
|
||||||
"end_time": 1784710800,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/133877"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "2026牛客暑期多校训练营3",
|
|
||||||
"start_time": 1784865600,
|
|
||||||
"end_time": 1784883600,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/133878"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "AtCoder Beginner Contest 468",
|
|
||||||
"start_time": 1784980800,
|
|
||||||
"end_time": 1784986800,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://atcoder.jp/contests/abc468"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "牛客周赛 Round 154",
|
|
||||||
"start_time": 1785063600,
|
|
||||||
"end_time": 1785070800,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/137840"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Codeforces",
|
|
||||||
"title": "Codeforces Round (Div. 1)",
|
|
||||||
"start_time": 1785076500,
|
|
||||||
"end_time": 1785085500,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://codeforces.com/contests/2249"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Codeforces",
|
|
||||||
"title": "Codeforces Round (Div. 2)",
|
|
||||||
"start_time": 1785076500,
|
|
||||||
"end_time": 1785085500,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://codeforces.com/contests/2250"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "2026牛客暑期多校训练营4",
|
|
||||||
"start_time": 1785297600,
|
|
||||||
"end_time": 1785315600,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/133879"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "2026牛客暑期多校训练营5",
|
|
||||||
"start_time": 1785470400,
|
|
||||||
"end_time": 1785488400,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/133880"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "RECRUIT Nihonbashi Half Marathon 2026 Summer (AtCoder Heuristic Contest 069)",
|
|
||||||
"start_time": 1785492000,
|
|
||||||
"end_time": 1786356000,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://atcoder.jp/contests/ahc069"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Codeforces",
|
|
||||||
"title": "Codeforces Round (Div. 2)",
|
|
||||||
"start_time": 1785584100,
|
|
||||||
"end_time": 1785591300,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://codeforces.com/contests/2248"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "AtCoder Beginner Contest 469",
|
|
||||||
"start_time": 1785585600,
|
|
||||||
"end_time": 1785591600,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://atcoder.jp/contests/abc469"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "牛客周赛 Round 155",
|
|
||||||
"start_time": 1785668400,
|
|
||||||
"end_time": 1785675600,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/138240"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "UNIQUE VISION Programming Contest 2026 Summer (AtCoder Regular Contest 226)",
|
|
||||||
"start_time": 1786276800,
|
|
||||||
"end_time": 1786284000,
|
|
||||||
"status": "upcoming",
|
|
||||||
"url": "https://atcoder.jp/contests/arc226"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+153
-49
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generated_at": 1784551373,
|
"generated_at": 1784551685,
|
||||||
"contests": [
|
"contests": [
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
@@ -9,14 +9,6 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/abc463"
|
"url": "https://atcoder.jp/contests/abc463"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "牛客周赛 Round 149",
|
|
||||||
"start_time": 1782039600,
|
|
||||||
"end_time": 1782046800,
|
|
||||||
"status": "finished",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/136720"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Heuristic Contest 067",
|
"title": "AtCoder Heuristic Contest 067",
|
||||||
@@ -25,6 +17,14 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/ahc067"
|
"url": "https://atcoder.jp/contests/ahc067"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "牛客周赛 Round 149",
|
||||||
|
"start_time": 1782039600,
|
||||||
|
"end_time": 1782046800,
|
||||||
|
"status": "finished",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/136720"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Daily Training ALL 2026/06/22 16:00start",
|
"title": "AtCoder Daily Training ALL 2026/06/22 16:00start",
|
||||||
@@ -505,6 +505,14 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/abc465"
|
"url": "https://atcoder.jp/contests/abc465"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"platform": "UOJ",
|
||||||
|
"title": "UOJ Long Round #4",
|
||||||
|
"start_time": 1783216800,
|
||||||
|
"end_time": 1783432800,
|
||||||
|
"status": "finished",
|
||||||
|
"url": "https://uoj.ac/contest/108"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"platform": "Nowcoder",
|
"platform": "Nowcoder",
|
||||||
"title": "牛客周赛 Round 151",
|
"title": "牛客周赛 Round 151",
|
||||||
@@ -561,6 +569,14 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://codeforces.com/contests/2242"
|
"url": "https://codeforces.com/contests/2242"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "World Tour Finals 2026 Heuristic",
|
||||||
|
"start_time": 1783382400,
|
||||||
|
"end_time": 1783504800,
|
||||||
|
"status": "finished",
|
||||||
|
"url": "https://atcoder.jp/contests/awtf2026heuristic"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Daily Training ALL 2026/07/07 16:00start",
|
"title": "AtCoder Daily Training ALL 2026/07/07 16:00start",
|
||||||
@@ -601,14 +617,6 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/awc0107"
|
"url": "https://atcoder.jp/contests/awc0107"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"platform": "UOJ",
|
|
||||||
"title": "UOJ Long Round #4",
|
|
||||||
"start_time": 1783216800,
|
|
||||||
"end_time": 1783432800,
|
|
||||||
"status": "finished",
|
|
||||||
"url": "https://uoj.ac/contest/108"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Daily Training ALL 2026/07/08 16:00start",
|
"title": "AtCoder Daily Training ALL 2026/07/08 16:00start",
|
||||||
@@ -641,14 +649,6 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/adt_easy_20260708_2"
|
"url": "https://atcoder.jp/contests/adt_easy_20260708_2"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "World Tour Finals 2026 Heuristic",
|
|
||||||
"start_time": 1783382400,
|
|
||||||
"end_time": 1783504800,
|
|
||||||
"status": "finished",
|
|
||||||
"url": "https://atcoder.jp/contests/awtf2026heuristic"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Weekday Contest 0108 Beta",
|
"title": "AtCoder Weekday Contest 0108 Beta",
|
||||||
@@ -657,22 +657,6 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/awc0108"
|
"url": "https://atcoder.jp/contests/awc0108"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "AtCoder Daily Training ALL 2026/07/09 16:00start",
|
|
||||||
"start_time": 1783580400,
|
|
||||||
"end_time": 1783584000,
|
|
||||||
"status": "finished",
|
|
||||||
"url": "https://atcoder.jp/contests/adt_all_20260709_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"platform": "AtCoder",
|
|
||||||
"title": "AtCoder Daily Training EASY 2026/07/09 16:00start",
|
|
||||||
"start_time": 1783580400,
|
|
||||||
"end_time": 1783584000,
|
|
||||||
"status": "finished",
|
|
||||||
"url": "https://atcoder.jp/contests/adt_easy_20260709_1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "World Tour Finals 2026 Algorithm",
|
"title": "World Tour Finals 2026 Algorithm",
|
||||||
@@ -689,6 +673,22 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/awtf2026algo-open"
|
"url": "https://atcoder.jp/contests/awtf2026algo-open"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "AtCoder Daily Training ALL 2026/07/09 16:00start",
|
||||||
|
"start_time": 1783580400,
|
||||||
|
"end_time": 1783584000,
|
||||||
|
"status": "finished",
|
||||||
|
"url": "https://atcoder.jp/contests/adt_all_20260709_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "AtCoder Daily Training EASY 2026/07/09 16:00start",
|
||||||
|
"start_time": 1783580400,
|
||||||
|
"end_time": 1783584000,
|
||||||
|
"status": "finished",
|
||||||
|
"url": "https://atcoder.jp/contests/adt_easy_20260709_1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Daily Training ALL 2026/07/09 18:00start",
|
"title": "AtCoder Daily Training ALL 2026/07/09 18:00start",
|
||||||
@@ -993,6 +993,14 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://codeforces.com/contests/2245"
|
"url": "https://codeforces.com/contests/2245"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "2026牛客暑期多校训练营1",
|
||||||
|
"start_time": 1784260800,
|
||||||
|
"end_time": 1784278800,
|
||||||
|
"status": "finished",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/133876"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Daily Training ALL 2026/07/17 16:00start",
|
"title": "AtCoder Daily Training ALL 2026/07/17 16:00start",
|
||||||
@@ -1009,14 +1017,6 @@
|
|||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/adt_easy_20260717_1"
|
"url": "https://atcoder.jp/contests/adt_easy_20260717_1"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"platform": "Nowcoder",
|
|
||||||
"title": "2026牛客暑期多校训练营1",
|
|
||||||
"start_time": 1784260800,
|
|
||||||
"end_time": 1784278800,
|
|
||||||
"status": "finished",
|
|
||||||
"url": "https://ac.nowcoder.com/acm/contest/133876"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"platform": "AtCoder",
|
"platform": "AtCoder",
|
||||||
"title": "AtCoder Daily Training ALL 2026/07/17 18:00start",
|
"title": "AtCoder Daily Training ALL 2026/07/17 18:00start",
|
||||||
@@ -1120,6 +1120,110 @@
|
|||||||
"end_time": 1784548800,
|
"end_time": 1784548800,
|
||||||
"status": "finished",
|
"status": "finished",
|
||||||
"url": "https://atcoder.jp/contests/awc0116"
|
"url": "https://atcoder.jp/contests/awc0116"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "2026牛客暑期多校训练营2",
|
||||||
|
"start_time": 1784692800,
|
||||||
|
"end_time": 1784710800,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/133877"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "2026牛客暑期多校训练营3",
|
||||||
|
"start_time": 1784865600,
|
||||||
|
"end_time": 1784883600,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/133878"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "AtCoder Beginner Contest 468",
|
||||||
|
"start_time": 1784980800,
|
||||||
|
"end_time": 1784986800,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://atcoder.jp/contests/abc468"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "牛客周赛 Round 154",
|
||||||
|
"start_time": 1785063600,
|
||||||
|
"end_time": 1785070800,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/137840"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Codeforces",
|
||||||
|
"title": "Codeforces Round (Div. 1)",
|
||||||
|
"start_time": 1785076500,
|
||||||
|
"end_time": 1785085500,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://codeforces.com/contests/2249"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Codeforces",
|
||||||
|
"title": "Codeforces Round (Div. 2)",
|
||||||
|
"start_time": 1785076500,
|
||||||
|
"end_time": 1785085500,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://codeforces.com/contests/2250"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "2026牛客暑期多校训练营4",
|
||||||
|
"start_time": 1785297600,
|
||||||
|
"end_time": 1785315600,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/133879"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "2026牛客暑期多校训练营5",
|
||||||
|
"start_time": 1785470400,
|
||||||
|
"end_time": 1785488400,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/133880"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "RECRUIT Nihonbashi Half Marathon 2026 Summer (AtCoder Heuristic Contest 069)",
|
||||||
|
"start_time": 1785492000,
|
||||||
|
"end_time": 1786356000,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://atcoder.jp/contests/ahc069"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Codeforces",
|
||||||
|
"title": "Codeforces Round (Div. 2)",
|
||||||
|
"start_time": 1785584100,
|
||||||
|
"end_time": 1785591300,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://codeforces.com/contests/2248"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "AtCoder Beginner Contest 469",
|
||||||
|
"start_time": 1785585600,
|
||||||
|
"end_time": 1785591600,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://atcoder.jp/contests/abc469"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "Nowcoder",
|
||||||
|
"title": "牛客周赛 Round 155",
|
||||||
|
"start_time": 1785668400,
|
||||||
|
"end_time": 1785675600,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://ac.nowcoder.com/acm/contest/138240"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"platform": "AtCoder",
|
||||||
|
"title": "UNIQUE VISION Programming Contest 2026 Summer (AtCoder Regular Contest 226)",
|
||||||
|
"start_time": 1786276800,
|
||||||
|
"end_time": 1786284000,
|
||||||
|
"status": "upcoming",
|
||||||
|
"url": "https://atcoder.jp/contests/arc226"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+11
-13
@@ -95,13 +95,16 @@ def fetch_codeforces(include_all=False):
|
|||||||
if data["status"] != "OK":
|
if data["status"] != "OK":
|
||||||
return []
|
return []
|
||||||
contests = []
|
contests = []
|
||||||
|
now = now_ts()
|
||||||
for c in data["result"]:
|
for c in data["result"]:
|
||||||
if include_all or c["phase"] == "BEFORE":
|
start = c["startTimeSeconds"]
|
||||||
|
end = start + c["durationSeconds"]
|
||||||
|
if include_all or end > now:
|
||||||
contests.append(make_contest(
|
contests.append(make_contest(
|
||||||
"Codeforces",
|
"Codeforces",
|
||||||
c["name"],
|
c["name"],
|
||||||
c["startTimeSeconds"],
|
start,
|
||||||
c["startTimeSeconds"] + c["durationSeconds"],
|
end,
|
||||||
f"https://codeforces.com/contests/{c['id']}"
|
f"https://codeforces.com/contests/{c['id']}"
|
||||||
))
|
))
|
||||||
return contests
|
return contests
|
||||||
@@ -121,7 +124,7 @@ def fetch_atcoder(include_all=False):
|
|||||||
for c in data:
|
for c in data:
|
||||||
start = c["start_epoch_second"]
|
start = c["start_epoch_second"]
|
||||||
end = start + c["duration_second"]
|
end = start + c["duration_second"]
|
||||||
if start > 0 and (include_all or start > now):
|
if start > 0 and (include_all or end > now):
|
||||||
contests.append(make_contest(
|
contests.append(make_contest(
|
||||||
"AtCoder",
|
"AtCoder",
|
||||||
c["title"],
|
c["title"],
|
||||||
@@ -237,12 +240,6 @@ def main():
|
|||||||
all_contests.extend(fetch_uoj())
|
all_contests.extend(fetch_uoj())
|
||||||
all_contests.extend(fetch_nowcoder())
|
all_contests.extend(fetch_nowcoder())
|
||||||
|
|
||||||
all_contests.sort(key=lambda x: x["start_time"])
|
|
||||||
|
|
||||||
# 直接写入仓库根目录
|
|
||||||
with open("contests.json", "w", encoding="utf-8") as f:
|
|
||||||
json.dump(contest_payload(all_contests, generated_at), f, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
recent_finished_contests = []
|
recent_finished_contests = []
|
||||||
recent_finished_min_end_time = recent_finished_cutoff()
|
recent_finished_min_end_time = recent_finished_cutoff()
|
||||||
recent_finished_contests.extend(fetch_codeforces(include_all=True))
|
recent_finished_contests.extend(fetch_codeforces(include_all=True))
|
||||||
@@ -251,12 +248,13 @@ def main():
|
|||||||
recent_finished_contests.extend(fetch_nowcoder(include_all=True, min_end_time=recent_finished_min_end_time))
|
recent_finished_contests.extend(fetch_nowcoder(include_all=True, min_end_time=recent_finished_min_end_time))
|
||||||
|
|
||||||
recent_finished_contests = filter_recent_finished(deduplicate_contests(recent_finished_contests))
|
recent_finished_contests = filter_recent_finished(deduplicate_contests(recent_finished_contests))
|
||||||
recent_finished_contests.sort(key=lambda x: x["end_time"])
|
all_contests = deduplicate_contests(all_contests + recent_finished_contests)
|
||||||
|
all_contests.sort(key=lambda x: (x["start_time"], x["end_time"]))
|
||||||
|
|
||||||
with open("contests_all.json", "w", encoding="utf-8") as f:
|
with open("contests_all.json", "w", encoding="utf-8") as f:
|
||||||
json.dump(contest_payload(recent_finished_contests, generated_at), f, ensure_ascii=False, indent=2)
|
json.dump(contest_payload(all_contests, generated_at), f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
print(f"Total upcoming contests: {len(all_contests)}")
|
print(f"Total active contests: {sum(1 for contest in all_contests if contest['status'] != 'finished')}")
|
||||||
print(f"Total contests finished in last 30 days: {len(recent_finished_contests)}")
|
print(f"Total contests finished in last 30 days: {len(recent_finished_contests)}")
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
+26
-28
@@ -1,11 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
// ========== 配置 ==========
|
// ========== 配置 ==========
|
||||||
// 你的 GitHub 仓库 Raw 文件地址(必须公开仓库)
|
// 你的 GitHub 仓库 Raw 文件地址(必须公开仓库)
|
||||||
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests.json');
|
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests_all.json');
|
||||||
define('FINISHED_JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests_all.json');
|
|
||||||
// 缓存文件路径(与 index.php 同目录,需可写)
|
// 缓存文件路径(与 index.php 同目录,需可写)
|
||||||
define('CACHE_FILE', __DIR__ . '/contests_cache.json');
|
define('CACHE_FILE', __DIR__ . '/contests_all_cache.json');
|
||||||
define('FINISHED_CACHE_FILE', __DIR__ . '/contests_all_cache.json');
|
|
||||||
// 缓存有效期(秒),这里设 5 分钟
|
// 缓存有效期(秒),这里设 5 分钟
|
||||||
define('CACHE_TTL', 300);
|
define('CACHE_TTL', 300);
|
||||||
|
|
||||||
@@ -156,41 +154,41 @@ function parse_contest_payload($json) {
|
|||||||
$contests_json = fetch_and_cache(JSON_URL, CACHE_FILE, CACHE_TTL);
|
$contests_json = fetch_and_cache(JSON_URL, CACHE_FILE, CACHE_TTL);
|
||||||
if ($contests_json === false) {
|
if ($contests_json === false) {
|
||||||
$contests = [];
|
$contests = [];
|
||||||
$contests_generated_at = 0;
|
$finished_contests = [];
|
||||||
|
$last_generated_at = 0;
|
||||||
$error_msg = $t['load_error'];
|
$error_msg = $t['load_error'];
|
||||||
|
$finished_error_msg = $t['finished_load_error'];
|
||||||
} else {
|
} else {
|
||||||
$contests_payload = parse_contest_payload($contests_json);
|
$contests_payload = parse_contest_payload($contests_json);
|
||||||
if ($contests_payload === false) {
|
if ($contests_payload === false) {
|
||||||
$contests = [];
|
$contests = [];
|
||||||
$contests_generated_at = 0;
|
|
||||||
$error_msg = $t['load_error'];
|
|
||||||
} else {
|
|
||||||
$contests = $contests_payload['contests'];
|
|
||||||
$contests_generated_at = $contests_payload['generated_at'];
|
|
||||||
$error_msg = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$finished_contests_json = fetch_and_cache(FINISHED_JSON_URL, FINISHED_CACHE_FILE, CACHE_TTL);
|
|
||||||
if ($finished_contests_json === false) {
|
|
||||||
$finished_contests = [];
|
|
||||||
$finished_contests_generated_at = 0;
|
|
||||||
$finished_error_msg = $t['finished_load_error'];
|
|
||||||
} else {
|
|
||||||
$finished_contests_payload = parse_contest_payload($finished_contests_json);
|
|
||||||
if ($finished_contests_payload === false) {
|
|
||||||
$finished_contests = [];
|
$finished_contests = [];
|
||||||
$finished_contests_generated_at = 0;
|
$last_generated_at = 0;
|
||||||
|
$error_msg = $t['load_error'];
|
||||||
$finished_error_msg = $t['finished_load_error'];
|
$finished_error_msg = $t['finished_load_error'];
|
||||||
} else {
|
} else {
|
||||||
$finished_contests = $finished_contests_payload['contests'];
|
$all_contests = $contests_payload['contests'];
|
||||||
$finished_contests_generated_at = $finished_contests_payload['generated_at'];
|
$contests = [];
|
||||||
|
$finished_contests = [];
|
||||||
|
foreach ($all_contests as $contest) {
|
||||||
|
if (contest_value($contest, 'status', '') === 'finished') {
|
||||||
|
$finished_contests[] = $contest;
|
||||||
|
} else {
|
||||||
|
$contests[] = $contest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usort($contests, function($left, $right) {
|
||||||
|
return (int)contest_value($left, 'start_time', 0) - (int)contest_value($right, 'start_time', 0);
|
||||||
|
});
|
||||||
|
usort($finished_contests, function($left, $right) {
|
||||||
|
return (int)contest_value($left, 'end_time', 0) - (int)contest_value($right, 'end_time', 0);
|
||||||
|
});
|
||||||
|
$last_generated_at = $contests_payload['generated_at'];
|
||||||
|
$error_msg = '';
|
||||||
$finished_error_msg = '';
|
$finished_error_msg = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$last_generated_at = max($contests_generated_at, $finished_contests_generated_at);
|
|
||||||
|
|
||||||
// ========== 平台样式映射 ==========
|
// ========== 平台样式映射 ==========
|
||||||
$platform_class = [
|
$platform_class = [
|
||||||
'Codeforces' => 'cf',
|
'Codeforces' => 'cf',
|
||||||
|
|||||||
Reference in New Issue
Block a user