diff --git a/docs/bbs.md b/docs/bbs.md
index d39c3f170..49b97abe6 100644
--- a/docs/bbs.md
+++ b/docs/bbs.md
@@ -244,6 +244,28 @@ pageClass: routes
+## PLAYNO.1 玩樂達人
+
+### AV
+
+
+
+| 全部文章 | AV 新聞 | AV 導覽 |
+| ---- | ----- | ----- |
+| 78 | 3 | 5 |
+
+
+
+### 情趣
+
+
+
+| 全部文章 | 情趣體驗報告 | 情趣新聞 | 情趣研究所 |
+| ---- | ---------- | ---- | -------- |
+| all | experience | news | graduate |
+
+
+
## RF 技术社区
### 文章
diff --git a/lib/v2/playno1/av.js b/lib/v2/playno1/av.js
new file mode 100644
index 000000000..b8331c995
--- /dev/null
+++ b/lib/v2/playno1/av.js
@@ -0,0 +1,41 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+const timezone = require('@/utils/timezone');
+const { cookieJar, processArticle } = require('./utils');
+const baseUrl = 'http://www.playno1.com';
+
+module.exports = async (ctx) => {
+ const { catid = '78' } = ctx.params;
+ const url = `${baseUrl}/portal.php?mod=list&catid=${catid}`;
+ const response = await got(url, {
+ cookieJar,
+ });
+ const $ = cheerio.load(response.data);
+
+ let items = $('.fire_float')
+ .toArray()
+ .filter((i) => $(i).text().length)
+ .map((item) => {
+ item = $(item);
+ return {
+ title: item.find('h3 a').attr('title'),
+ link: item.find('h3 a').attr('href'),
+ pubDate: timezone(parseDate(item.find('.fire_left').text()), 8),
+ author: item
+ .find('.fire_right')
+ .text()
+ .match(/作者:(.*)\s*\|/)[1]
+ .trim(),
+ };
+ });
+
+ items = await processArticle(items, ctx.cache);
+
+ ctx.state.data = {
+ title: $('head title').text(),
+ link: url,
+ item: items,
+ language: 'zh-TW',
+ };
+};
diff --git a/lib/v2/playno1/maintainer.js b/lib/v2/playno1/maintainer.js
new file mode 100644
index 000000000..97902c79a
--- /dev/null
+++ b/lib/v2/playno1/maintainer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/av/:catid?': ['TonyRL'],
+ '/st/:catid?': ['TonyRL'],
+};
diff --git a/lib/v2/playno1/radar.js b/lib/v2/playno1/radar.js
new file mode 100644
index 000000000..3a444c451
--- /dev/null
+++ b/lib/v2/playno1/radar.js
@@ -0,0 +1,21 @@
+module.exports = {
+ 'playno1.com': {
+ _name: 'PLAYNO.1玩樂達人',
+ stno1: [
+ {
+ title: '情趣',
+ docs: 'https://docs.rsshub.app/bbs.html#playno-1-wan-le-da-ren',
+ source: ['/stno1/:catid/'],
+ target: '/playno1/st/:catid',
+ },
+ ],
+ www: [
+ {
+ title: 'AV',
+ docs: 'https://docs.rsshub.app/bbs.html#playno-1-wan-le-da-ren',
+ source: ['/portal.php'],
+ target: (_params, url) => `/playno1/av/${new URL(url).searchParams.get('catid')}`,
+ },
+ ],
+ },
+};
diff --git a/lib/v2/playno1/router.js b/lib/v2/playno1/router.js
new file mode 100644
index 000000000..4c016c7c8
--- /dev/null
+++ b/lib/v2/playno1/router.js
@@ -0,0 +1,4 @@
+module.exports = (router) => {
+ router.get('/av/:catid?', require('./av'));
+ router.get('/st/:catid?', require('./st'));
+};
diff --git a/lib/v2/playno1/st.js b/lib/v2/playno1/st.js
new file mode 100644
index 000000000..3e9df5126
--- /dev/null
+++ b/lib/v2/playno1/st.js
@@ -0,0 +1,36 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+const timezone = require('@/utils/timezone');
+const { cookieJar, processArticle } = require('./utils');
+const baseUrl = 'http://stno1.playno1.com';
+
+module.exports = async (ctx) => {
+ const { catid = 'all' } = ctx.params;
+ const url = `${baseUrl}/stno1/${catid}/`;
+ const response = await got(url, {
+ cookieJar,
+ });
+ const $ = cheerio.load(response.data);
+
+ let items = $('.fallsBox')
+ .toArray()
+ .map((item) => {
+ item = $(item);
+ return {
+ title: item.find('.ftitle a').attr('title'),
+ link: item.find('.ftitle a').attr('href'),
+ pubDate: timezone(parseDate(item.find('.dateBox').text(), 'YYYY-MM-DD HH:mm'), 8),
+ author: item.find('.dateBox span a').eq(0).text().trim(),
+ };
+ });
+
+ items = await processArticle(items, ctx.cache);
+
+ ctx.state.data = {
+ title: $('head title').text(),
+ link: url,
+ item: items,
+ language: 'zh-TW',
+ };
+};
diff --git a/lib/v2/playno1/utils.js b/lib/v2/playno1/utils.js
new file mode 100644
index 000000000..18c0de2b9
--- /dev/null
+++ b/lib/v2/playno1/utils.js
@@ -0,0 +1,32 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { CookieJar, Cookie } = require('tough-cookie');
+const cookieJar = new CookieJar();
+const cookie = Cookie.fromJSON({
+ key: 'playno1',
+ value: 'playno1Cookie',
+ domain: 'playno1.com',
+ path: '/',
+});
+(async () => {
+ await cookieJar.setCookie(cookie, 'http://www.playno1.com/');
+})();
+
+const processArticle = (items, cache) =>
+ Promise.all(
+ items.map((item) =>
+ cache.tryGet(item.link, async () => {
+ const detailResponse = await got(item.link, {
+ cookieJar,
+ });
+ const content = cheerio.load(detailResponse.data);
+ item.description = content('#article_content').html();
+ return item;
+ })
+ )
+ );
+
+module.exports = {
+ cookieJar,
+ processArticle,
+};