remove contests.json
This commit is contained in:
@@ -4,10 +4,10 @@ Guidance for AI coding agents working in this repository.
|
||||
|
||||
## 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.
|
||||
- `.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.
|
||||
|
||||
## 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:
|
||||
|
||||
- `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.
|
||||
- `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.
|
||||
@@ -32,8 +32,7 @@ When removing a provider, remove or adjust the same surfaces so stale platform l
|
||||
|
||||
## 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 finished within the last 30 days.
|
||||
- `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.
|
||||
- JSON timestamps are Unix timestamps in seconds.
|
||||
- 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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -13,12 +13,11 @@ Fetch programming contest schedules from several online judge platforms and expo
|
||||
|
||||
## 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 finished within the last 30 days.
|
||||
- `contests_all.json`: contests that have not ended yet, including upcoming and currently running contests, plus 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
|
||||
{
|
||||
@@ -64,30 +63,30 @@ python fetch_contests.py
|
||||
After a successful run, the script prints counts similar to:
|
||||
|
||||
```text
|
||||
Total upcoming contests: 13
|
||||
Total active contests: 13
|
||||
Total contests finished in last 30 days: 139
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
```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
|
||||
|
||||
A typical automation flow is:
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
+9
-10
@@ -2,7 +2,7 @@
|
||||
|
||||
[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
|
||||
{
|
||||
@@ -64,21 +63,21 @@ python fetch_contests.py
|
||||
成功运行后,脚本会输出类似下面的统计信息:
|
||||
|
||||
```text
|
||||
Total upcoming contests: 13
|
||||
Total active contests: 13
|
||||
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` 中的这个常量:
|
||||
|
||||
```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`。
|
||||
2. 提交更新后的 JSON 文件。
|
||||
3. 推送到 GitHub。
|
||||
4. 让 `server/index.php` 从 GitHub Raw 读取最新的 `contests.json`。
|
||||
4. 让 `server/index.php` 从 GitHub Raw 读取最新的 `contests_all.json`。
|
||||
|
||||
如果使用 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": [
|
||||
{
|
||||
"platform": "AtCoder",
|
||||
@@ -9,14 +9,6 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Heuristic Contest 067",
|
||||
@@ -25,6 +17,14 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Daily Training ALL 2026/06/22 16:00start",
|
||||
@@ -505,6 +505,14 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "牛客周赛 Round 151",
|
||||
@@ -561,6 +569,14 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Daily Training ALL 2026/07/07 16:00start",
|
||||
@@ -601,14 +617,6 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Daily Training ALL 2026/07/08 16:00start",
|
||||
@@ -641,14 +649,6 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Weekday Contest 0108 Beta",
|
||||
@@ -657,22 +657,6 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "World Tour Finals 2026 Algorithm",
|
||||
@@ -689,6 +673,22 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Daily Training ALL 2026/07/09 18:00start",
|
||||
@@ -993,6 +993,14 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Daily Training ALL 2026/07/17 16:00start",
|
||||
@@ -1009,14 +1017,6 @@
|
||||
"status": "finished",
|
||||
"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",
|
||||
"title": "AtCoder Daily Training ALL 2026/07/17 18:00start",
|
||||
@@ -1120,6 +1120,110 @@
|
||||
"end_time": 1784548800,
|
||||
"status": "finished",
|
||||
"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":
|
||||
return []
|
||||
contests = []
|
||||
now = now_ts()
|
||||
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(
|
||||
"Codeforces",
|
||||
c["name"],
|
||||
c["startTimeSeconds"],
|
||||
c["startTimeSeconds"] + c["durationSeconds"],
|
||||
start,
|
||||
end,
|
||||
f"https://codeforces.com/contests/{c['id']}"
|
||||
))
|
||||
return contests
|
||||
@@ -121,7 +124,7 @@ def fetch_atcoder(include_all=False):
|
||||
for c in data:
|
||||
start = c["start_epoch_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(
|
||||
"AtCoder",
|
||||
c["title"],
|
||||
@@ -237,12 +240,6 @@ def main():
|
||||
all_contests.extend(fetch_uoj())
|
||||
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_min_end_time = recent_finished_cutoff()
|
||||
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 = 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:
|
||||
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)}")
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+25
-27
@@ -1,11 +1,9 @@
|
||||
<?php
|
||||
// ========== 配置 ==========
|
||||
// 你的 GitHub 仓库 Raw 文件地址(必须公开仓库)
|
||||
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests.json');
|
||||
define('FINISHED_JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests_all.json');
|
||||
define('JSON_URL', 'https://raw.githubusercontent.com/hanyixuanten/OI-contest-fetch/master/contests_all.json');
|
||||
// 缓存文件路径(与 index.php 同目录,需可写)
|
||||
define('CACHE_FILE', __DIR__ . '/contests_cache.json');
|
||||
define('FINISHED_CACHE_FILE', __DIR__ . '/contests_all_cache.json');
|
||||
define('CACHE_FILE', __DIR__ . '/contests_all_cache.json');
|
||||
// 缓存有效期(秒),这里设 5 分钟
|
||||
define('CACHE_TTL', 300);
|
||||
|
||||
@@ -156,41 +154,41 @@ function parse_contest_payload($json) {
|
||||
$contests_json = fetch_and_cache(JSON_URL, CACHE_FILE, CACHE_TTL);
|
||||
if ($contests_json === false) {
|
||||
$contests = [];
|
||||
$contests_generated_at = 0;
|
||||
$finished_contests = [];
|
||||
$last_generated_at = 0;
|
||||
$error_msg = $t['load_error'];
|
||||
$finished_error_msg = $t['finished_load_error'];
|
||||
} else {
|
||||
$contests_payload = parse_contest_payload($contests_json);
|
||||
if ($contests_payload === false) {
|
||||
$contests = [];
|
||||
$contests_generated_at = 0;
|
||||
$finished_contests = [];
|
||||
$last_generated_at = 0;
|
||||
$error_msg = $t['load_error'];
|
||||
$finished_error_msg = $t['finished_load_error'];
|
||||
} else {
|
||||
$contests = $contests_payload['contests'];
|
||||
$contests_generated_at = $contests_payload['generated_at'];
|
||||
$all_contests = $contests_payload['contests'];
|
||||
$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_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_generated_at = 0;
|
||||
$finished_error_msg = $t['finished_load_error'];
|
||||
} else {
|
||||
$finished_contests = $finished_contests_payload['contests'];
|
||||
$finished_contests_generated_at = $finished_contests_payload['generated_at'];
|
||||
$finished_error_msg = '';
|
||||
}
|
||||
}
|
||||
|
||||
$last_generated_at = max($contests_generated_at, $finished_contests_generated_at);
|
||||
|
||||
// ========== 平台样式映射 ==========
|
||||
$platform_class = [
|
||||
'Codeforces' => 'cf',
|
||||
|
||||
Reference in New Issue
Block a user