mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-03 02:28:23 +08:00
feat(route): add AtCoder (#8825)
This commit is contained in:
@@ -10,6 +10,50 @@ pageClass: routes
|
|||||||
|
|
||||||
<Route author="nczitzk" example="/acm/amturingaward" path="/acm/amturingaward"/>
|
<Route author="nczitzk" example="/acm/amturingaward" path="/acm/amturingaward"/>
|
||||||
|
|
||||||
|
## AtCoder
|
||||||
|
|
||||||
|
### Present Contests
|
||||||
|
|
||||||
|
<RouteEn author="nczitzk" example="/atcoder/contest/en/upcoming" path="/atcoder/contest/:language?/:status?" :paramsDesc="['Language, `jp` as Japanese or `en` as English, English by default', 'Status, see below, Recent Contests by default']">
|
||||||
|
|
||||||
|
Status
|
||||||
|
|
||||||
|
| Active Contests | Upcoming Contests | Recent Contests |
|
||||||
|
| --------------- | ----------------- | --------------- |
|
||||||
|
| active | upcoming | recent |
|
||||||
|
|
||||||
|
</RouteEn>
|
||||||
|
|
||||||
|
### Contests Archive
|
||||||
|
|
||||||
|
<RouteEn author="nczitzk" example="/atcoder/contest" path="/atcoder/contest/:language?/:rated?/:category?/:keyword?" :paramsDesc="['Language, `jp` as Japanese or `en` as English, English by default', 'Rated Range, see below, all by default', 'Category, see below, all by default', 'Keyword']">
|
||||||
|
|
||||||
|
Rated Range
|
||||||
|
|
||||||
|
| ABC Class (Rated for ~1999) | ARC Class (Rated for ~2799) | AGC Class (Rated for ~9999) |
|
||||||
|
| --------------------------- | --------------------------- | --------------------------- |
|
||||||
|
| 1 | 2 | 3 |
|
||||||
|
|
||||||
|
Category
|
||||||
|
|
||||||
|
| All | AtCoder Typical Contest | PAST Archive | Unofficial(unrated) |
|
||||||
|
| --- | ----------------------- | ------------ | ------------------- |
|
||||||
|
| 0 | 6 | 50 | 101 |
|
||||||
|
|
||||||
|
| JOI Archive | Sponsored Tournament | Sponsored Parallel(rated) |
|
||||||
|
| ----------- | -------------------- | ------------------------- |
|
||||||
|
| 200 | 1000 | 1001 |
|
||||||
|
|
||||||
|
| Sponsored Parallel(unrated) | Optimization Contest |
|
||||||
|
| --------------------------- | -------------------- |
|
||||||
|
| 1002 | 1200 |
|
||||||
|
|
||||||
|
</RouteEn>
|
||||||
|
|
||||||
|
### Posts
|
||||||
|
|
||||||
|
<RouteEn author="nczitzk" example="/atcoder/post" path="/atcoder/post/:language?/:keyword?" :paramsDesc="['Language, `jp` as Japanese or `en` as English, English by default', 'Keyword']"/>
|
||||||
|
|
||||||
## cve.mitre.org
|
## cve.mitre.org
|
||||||
|
|
||||||
### Search Result
|
### Search Result
|
||||||
|
|||||||
@@ -24,6 +24,50 @@ pageClass: routes
|
|||||||
|
|
||||||
> AlgoCasts 需要付费订阅,RSS 仅做更新提醒,不含付费内容.
|
> AlgoCasts 需要付费订阅,RSS 仅做更新提醒,不含付费内容.
|
||||||
|
|
||||||
|
## AtCoder
|
||||||
|
|
||||||
|
### Present Contests
|
||||||
|
|
||||||
|
<Route author="nczitzk" example="/atcoder/contest/en/upcoming" path="/atcoder/contest/:language?/:status?" :paramsDesc="['语言,可选 `jp` 即日语 或 `en` 即英语,默认为英语', '状态,见下表,默认为 Recent Contests']">
|
||||||
|
|
||||||
|
状态
|
||||||
|
|
||||||
|
| Active Contests | Upcoming Contests | Recent Contests |
|
||||||
|
| --------------- | ----------------- | --------------- |
|
||||||
|
| active | upcoming | recent |
|
||||||
|
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
### Contests Archive
|
||||||
|
|
||||||
|
<Route author="nczitzk" example="/atcoder/contest" path="/atcoder/contest/:language?/:rated?/:category?/:keyword?" :paramsDesc="['语言,可选 `jp` 即日语 或 `en` 即英语,默认为英语', 'Rated 对象,见下表,默认为全部', '分类,见下表,默认为全部', '关键字,默认为空']">
|
||||||
|
|
||||||
|
Rated 对象
|
||||||
|
|
||||||
|
| ABC Class (Rated for ~1999) | ARC Class (Rated for ~2799) | AGC Class (Rated for ~9999) |
|
||||||
|
| --------------------------- | --------------------------- | --------------------------- |
|
||||||
|
| 1 | 2 | 3 |
|
||||||
|
|
||||||
|
分类
|
||||||
|
|
||||||
|
| All | AtCoder Typical Contest | PAST Archive | Unofficial(unrated) |
|
||||||
|
| --- | ----------------------- | ------------ | ------------------- |
|
||||||
|
| 0 | 6 | 50 | 101 |
|
||||||
|
|
||||||
|
| JOI Archive | Sponsored Tournament | Sponsored Parallel(rated) |
|
||||||
|
| ----------- | -------------------- | ------------------------- |
|
||||||
|
| 200 | 1000 | 1001 |
|
||||||
|
|
||||||
|
| Sponsored Parallel(unrated) | Optimization Contest |
|
||||||
|
| --------------------------- | -------------------- |
|
||||||
|
| 1002 | 1200 |
|
||||||
|
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
### Posts
|
||||||
|
|
||||||
|
<Route author="nczitzk" example="/atcoder/post" path="/atcoder/post/:language?/:keyword?" :paramsDesc="['语言,可选 `jp` 即日语 或 `en` 即英语,默认为英语', '关键字,默认为空']"/>
|
||||||
|
|
||||||
## cve.mitre.org
|
## cve.mitre.org
|
||||||
|
|
||||||
### 搜索结果
|
### 搜索结果
|
||||||
|
|||||||
64
lib/v2/atcoder/contest.js
Normal file
64
lib/v2/atcoder/contest.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
const got = require('@/utils/got');
|
||||||
|
const cheerio = require('cheerio');
|
||||||
|
const { parseDate } = require('@/utils/parse-date');
|
||||||
|
|
||||||
|
module.exports = async (ctx) => {
|
||||||
|
const status = ['action', 'upcoming', 'recent'];
|
||||||
|
|
||||||
|
const language = ctx.params.language ?? 'en';
|
||||||
|
|
||||||
|
let rated = ctx.params.rated ?? '0';
|
||||||
|
const category = ctx.params.category ?? '0';
|
||||||
|
const keyword = ctx.params.keyword ?? '';
|
||||||
|
|
||||||
|
rated = rated === 'active' ? 'action' : rated;
|
||||||
|
const isStatus = status.includes(rated);
|
||||||
|
|
||||||
|
const rootUrl = 'https://atcoder.jp';
|
||||||
|
const currentUrl = `${rootUrl}/contests${isStatus ? `?lang=${language}` : `/archive?lang=${language}&ratedType=${rated}&category=${category}${keyword ? `&keyword=${keyword}` : ''}`}`;
|
||||||
|
|
||||||
|
const response = await got({
|
||||||
|
method: 'get',
|
||||||
|
url: currentUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const $ = cheerio.load(response.data);
|
||||||
|
|
||||||
|
let items = $(isStatus ? `#contest-table-${rated}` : '.row')
|
||||||
|
.find('tr')
|
||||||
|
.slice(1, ctx.query.limit ? parseInt(ctx.query.limit) : 20)
|
||||||
|
.toArray()
|
||||||
|
.map((item) => {
|
||||||
|
item = $(item).find('td a').eq(1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: item.text(),
|
||||||
|
link: `${rootUrl}${item.attr('href')}?lang=${language}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
items = await Promise.all(
|
||||||
|
items.map((item) =>
|
||||||
|
ctx.cache.tryGet(item.link, async () => {
|
||||||
|
const detailResponse = await got({
|
||||||
|
method: 'get',
|
||||||
|
url: item.link,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = cheerio.load(detailResponse.data);
|
||||||
|
|
||||||
|
item.description = content(`.lang-${language}`).html();
|
||||||
|
item.pubDate = parseDate(content('.fixtime-full').first().text());
|
||||||
|
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.state.data = {
|
||||||
|
title: String(isStatus ? `${$(`#contest-table-${rated} h3`).text()} - AtCoder` : $('title').text()),
|
||||||
|
link: currentUrl,
|
||||||
|
item: items,
|
||||||
|
allowEmpty: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
4
lib/v2/atcoder/maintainer.js
Normal file
4
lib/v2/atcoder/maintainer.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
'/post/:language?/:keyword?': ['nczitzk'],
|
||||||
|
'/contest/:language?/:rated?/:category?/:keyword?': ['nczitzk'],
|
||||||
|
};
|
||||||
38
lib/v2/atcoder/post.js
Normal file
38
lib/v2/atcoder/post.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const got = require('@/utils/got');
|
||||||
|
const cheerio = require('cheerio');
|
||||||
|
const timezone = require('@/utils/timezone');
|
||||||
|
const { parseDate } = require('@/utils/parse-date');
|
||||||
|
|
||||||
|
module.exports = async (ctx) => {
|
||||||
|
const language = ctx.params.language ?? 'en';
|
||||||
|
const keyword = ctx.params.keyword ?? '';
|
||||||
|
|
||||||
|
const rootUrl = 'https://atcoder.jp';
|
||||||
|
const currentUrl = `${rootUrl}/posts?lang=${language}${keyword ? `&keyword=${keyword}` : ''}`;
|
||||||
|
|
||||||
|
const response = await got({
|
||||||
|
method: 'get',
|
||||||
|
url: currentUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const $ = cheerio.load(response.data);
|
||||||
|
|
||||||
|
const items = $('.panel')
|
||||||
|
.toArray()
|
||||||
|
.map((item) => {
|
||||||
|
item = $(item);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: item.find('.panel-title').text(),
|
||||||
|
description: item.find('.panel-body').html(),
|
||||||
|
link: `${rootUrl}${item.find('.panel-title a').attr('href')}`,
|
||||||
|
pubDate: timezone(parseDate(item.find('.timeago').attr('datetime')), +9),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.state.data = {
|
||||||
|
title: `${keyword ? `[${keyword}] - ` : ''}${$('title').text()}`,
|
||||||
|
link: currentUrl,
|
||||||
|
item: items,
|
||||||
|
};
|
||||||
|
};
|
||||||
22
lib/v2/atcoder/radar.js
Normal file
22
lib/v2/atcoder/radar.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
module.exports = {
|
||||||
|
'atcoder.jp': {
|
||||||
|
_name: 'AtCoder',
|
||||||
|
'.': [
|
||||||
|
{
|
||||||
|
title: 'Posts',
|
||||||
|
docs: 'https://docs.rsshub.app/programming.html#atcoder-posts',
|
||||||
|
source: ['/posts', '/'],
|
||||||
|
target: (params, url) => `/atcoder/post/${new URL(url).searchParams.get('lang') ?? 'en'}/${new URL(url).searchParams.get('keyword') ?? ''}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Contests',
|
||||||
|
docs: 'https://docs.rsshub.app/programming.html#atcoder-contests',
|
||||||
|
source: ['/contests/archive', '/contests', ''],
|
||||||
|
target: (params, url) =>
|
||||||
|
`/atcoder/content/${new URL(url).searchParams.get('lang') ?? 'en'}/${new URL(url).searchParams.get('ratedType') ?? '0'}/${new URL(url).searchParams.get('category') ?? '0'}/${
|
||||||
|
new URL(url).searchParams.get('keyword') ?? ''
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
4
lib/v2/atcoder/router.js
Normal file
4
lib/v2/atcoder/router.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = function (router) {
|
||||||
|
router.get('/post/:language?/:keyword?', require('./post'));
|
||||||
|
router.get('/contest/:language?/:rated?/:category?/:keyword?', require('./contest'));
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user