diff --git a/docs/anime.md b/docs/anime.md
index 3a89b5030c..c16af579c0 100644
--- a/docs/anime.md
+++ b/docs/anime.md
@@ -253,6 +253,26 @@ pageClass: routes
+## Kemono
+
+### Posts
+
+
+
+Sources
+
+| Posts | Patreon | Pixiv Fanbox | Gumroad | SubscribeStar | DLsite | Discord | Fantia |
+| ----- | ------- | ------------ | ------- | ------------- | ------ | ------- | ------ |
+| posts | patreon | fanbox | gumroad | subscribestar | dlsite | discord | fantia |
+
+::: tip 提示
+
+当选择 `posts` 作为参数 **source** 的值时,参数 **id** 不生效。
+
+:::
+
+
+
## Mox.moe
### 首頁
diff --git a/docs/en/anime.md b/docs/en/anime.md
index de298e77c6..18ae2c4198 100644
--- a/docs/en/anime.md
+++ b/docs/en/anime.md
@@ -10,6 +10,26 @@ pageClass: routes
+## Kemono
+
+### Posts
+
+
+
+Sources
+
+| Posts | Patreon | Pixiv Fanbox | Gumroad | SubscribeStar | DLsite | Discord | Fantia |
+| ----- | ------- | ------------ | ------- | ------------- | ------ | ------- | ------ |
+| posts | patreon | fanbox | gumroad | subscribestar | dlsite | discord | fantia |
+
+::: tip Tip
+
+When `posts` is selected as the value of the parameter **source**, the parameter **id** does not take effect.
+
+:::
+
+
+
## Touhougarakuta
### Articles
diff --git a/lib/v2/kemono/index.js b/lib/v2/kemono/index.js
new file mode 100644
index 0000000000..6650c221f8
--- /dev/null
+++ b/lib/v2/kemono/index.js
@@ -0,0 +1,99 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+
+module.exports = async (ctx) => {
+ const source = ctx.params.source ?? '';
+ const id = ctx.params.id;
+
+ const rootUrl = 'https://kemono.party';
+ const apiUrl = `${rootUrl}/api/discord/channels/lookup?q=${id}`;
+ const currentUrl = `${rootUrl}/${source ? `${source}/${source === 'discord' ? `server/${id}` : `user/${id}`}` : 'posts'}`;
+
+ const headers = {
+ cookie: '__ddg2=sBQ4uaaGecmfEUk7',
+ };
+
+ const response = await got({
+ method: 'get',
+ url: source === 'discord' ? apiUrl : currentUrl,
+ headers,
+ });
+
+ let items = [],
+ title = '';
+
+ if (source === 'discord') {
+ title = `Posts of ${id} from Discord | Kemono`;
+
+ await Promise.all(
+ response.data.map(async (channel) => {
+ await ctx.cache.tryGet(channel.id, async () => {
+ const channelResponse = await got({
+ method: 'get',
+ url: `${rootUrl}/api/discord/channel/${channel.id}?skip=0`,
+ headers,
+ });
+
+ items.push(
+ ...channelResponse.data
+ .filter((i) => i.content || i.attachments)
+ .map((i) => ({
+ title: i.content,
+ description: (i.content ? `
${i.content}
` : '') + (i.attachments ? i.attachments.map((a) => `
`).join('') : ''),
+ author: i.author.username,
+ pubDate: parseDate(i.published),
+ category: channel.name,
+ }))
+ );
+ });
+ })
+ );
+ } else {
+ const $ = cheerio.load(response.data);
+
+ title = $('title').text();
+
+ items = await Promise.all(
+ $('.post-card')
+ .find('.fancy-link')
+ .slice(0, ctx.query.limit ? parseInt(ctx.query.limit) : 25)
+ .toArray()
+ .map((item) => {
+ item = $(item);
+
+ return {
+ link: `${rootUrl}${item.attr('href')}`,
+ };
+ })
+ .map((item) =>
+ ctx.cache.tryGet(item.link, async () => {
+ const detailResponse = await got({
+ method: 'get',
+ url: item.link,
+ headers,
+ });
+
+ const content = cheerio.load(detailResponse.data);
+
+ content('.post__thumbnail').each(function () {
+ content(this).html(`
`);
+ });
+
+ item.description = content('.post__body').html();
+ item.author = content('.post__user-name').text();
+ item.title = content('.post__title span').first().text();
+ item.pubDate = parseDate(content('.timestamp').attr('datetime'));
+
+ return item;
+ })
+ )
+ );
+ }
+
+ ctx.state.data = {
+ title,
+ link: currentUrl,
+ item: items,
+ };
+};
diff --git a/lib/v2/kemono/maintainer.js b/lib/v2/kemono/maintainer.js
new file mode 100644
index 0000000000..9f495da076
--- /dev/null
+++ b/lib/v2/kemono/maintainer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/:source?/:id?': ['nczitzk'],
+ '/posts': ['nczitzk'],
+};
diff --git a/lib/v2/kemono/radar.js b/lib/v2/kemono/radar.js
new file mode 100644
index 0000000000..f603d66e51
--- /dev/null
+++ b/lib/v2/kemono/radar.js
@@ -0,0 +1,13 @@
+module.exports = {
+ 'kemono.party': {
+ _name: 'Kemono',
+ '.': [
+ {
+ title: 'Posts',
+ docs: 'https://docs.rsshub.app/anime.html#kemono-posts',
+ source: ['/:source/user/:id', '/'],
+ target: '/kemono/:source?/:id?',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/kemono/router.js b/lib/v2/kemono/router.js
new file mode 100644
index 0000000000..a9e750fd89
--- /dev/null
+++ b/lib/v2/kemono/router.js
@@ -0,0 +1,3 @@
+module.exports = function (router) {
+ router.get('/:source?/:id?', require('./index'));
+};