diff --git a/docs/en/programming.md b/docs/en/programming.md
index b7294ef3fd..f3c78fdf90 100644
--- a/docs/en/programming.md
+++ b/docs/en/programming.md
@@ -10,6 +10,50 @@ pageClass: routes
+## AtCoder
+
+### Present Contests
+
+
+
+Status
+
+| Active Contests | Upcoming Contests | Recent Contests |
+| --------------- | ----------------- | --------------- |
+| active | upcoming | recent |
+
+
+
+### Contests Archive
+
+
+
+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 |
+
+
+
+### Posts
+
+
+
## cve.mitre.org
### Search Result
diff --git a/docs/programming.md b/docs/programming.md
index 1175479d5c..736c52add9 100644
--- a/docs/programming.md
+++ b/docs/programming.md
@@ -24,6 +24,50 @@ pageClass: routes
> AlgoCasts 需要付费订阅,RSS 仅做更新提醒,不含付费内容.
+## AtCoder
+
+### Present Contests
+
+
+
+状态
+
+| Active Contests | Upcoming Contests | Recent Contests |
+| --------------- | ----------------- | --------------- |
+| active | upcoming | recent |
+
+
+
+### Contests Archive
+
+
+
+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 |
+
+
+
+### Posts
+
+
+
## cve.mitre.org
### 搜索结果
diff --git a/lib/v2/atcoder/contest.js b/lib/v2/atcoder/contest.js
new file mode 100644
index 0000000000..446425eba3
--- /dev/null
+++ b/lib/v2/atcoder/contest.js
@@ -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,
+ };
+};
diff --git a/lib/v2/atcoder/maintainer.js b/lib/v2/atcoder/maintainer.js
new file mode 100644
index 0000000000..9cc4dbd5ba
--- /dev/null
+++ b/lib/v2/atcoder/maintainer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/post/:language?/:keyword?': ['nczitzk'],
+ '/contest/:language?/:rated?/:category?/:keyword?': ['nczitzk'],
+};
diff --git a/lib/v2/atcoder/post.js b/lib/v2/atcoder/post.js
new file mode 100644
index 0000000000..db6732d50c
--- /dev/null
+++ b/lib/v2/atcoder/post.js
@@ -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,
+ };
+};
diff --git a/lib/v2/atcoder/radar.js b/lib/v2/atcoder/radar.js
new file mode 100644
index 0000000000..5581fc9762
--- /dev/null
+++ b/lib/v2/atcoder/radar.js
@@ -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') ?? ''
+ }`,
+ },
+ ],
+ },
+};
diff --git a/lib/v2/atcoder/router.js b/lib/v2/atcoder/router.js
new file mode 100644
index 0000000000..faa8e13386
--- /dev/null
+++ b/lib/v2/atcoder/router.js
@@ -0,0 +1,4 @@
+module.exports = function (router) {
+ router.get('/post/:language?/:keyword?', require('./post'));
+ router.get('/contest/:language?/:rated?/:category?/:keyword?', require('./contest'));
+};