diff --git a/docs/bbs.md b/docs/bbs.md
index 0ffe8eb2b..2783a08f5 100644
--- a/docs/bbs.md
+++ b/docs/bbs.md
@@ -384,6 +384,16 @@ pageClass: routes
+## Zuvio
+
+### 校園話題
+
+
+
+### 看板列表
+
+
+
## 巴哈姆特電玩資訊站
### 熱門推薦
diff --git a/lib/v2/zuvio/boards.js b/lib/v2/zuvio/boards.js
new file mode 100644
index 000000000..acb22c908
--- /dev/null
+++ b/lib/v2/zuvio/boards.js
@@ -0,0 +1,14 @@
+const { getBoards, rootUrl } = require('./utils');
+
+module.exports = async (ctx) => {
+ const items = await getBoards(ctx.cache.tryGet);
+
+ ctx.state.data = {
+ title: 'Zuvio 校園話題列表 - 大學生論壇',
+ description: 'Zuvio 校園話題,Zuvio 校園話題千種動物頭像交流心得。最萌的匿名論壇,上千種逗趣動物頭像隨你變換,點頭像立即私訊功能,讓你找到共同話題的小夥伴!多人分享配對心得、聊天交友心情在此,快加入17分享!',
+ image: 'https://s3.hicloud.net.tw/zuvio.public/public/system/images/irs_v4/chicken/shared/webshare.png',
+ link: `${rootUrl}/articles`,
+ item: items,
+ language: 'zh-Hant',
+ };
+};
diff --git a/lib/v2/zuvio/radar.js b/lib/v2/zuvio/radar.js
new file mode 100644
index 000000000..edccab155
--- /dev/null
+++ b/lib/v2/zuvio/radar.js
@@ -0,0 +1,13 @@
+module.exports = {
+ 'zuvio.com.tw': {
+ _name: 'Zuvio',
+ irs: [
+ {
+ title: '校園話題',
+ docs: 'https://docs.rsshub.app/bbs.html#zuvio',
+ source: ['/student5/chickenM/articles/:board', '/student5/chickenM/articles'],
+ target: (params) => `/zuvio/student5${params.board ? `/${params.board}` : ''}`,
+ },
+ ],
+ },
+};
diff --git a/lib/v2/zuvio/router.js b/lib/v2/zuvio/router.js
new file mode 100644
index 000000000..e0a3eb579
--- /dev/null
+++ b/lib/v2/zuvio/router.js
@@ -0,0 +1,4 @@
+module.exports = (router) => {
+ router.get('/student5/boards', require('./boards'));
+ router.get('/student5/:board?', require('./student5'));
+};
diff --git a/lib/v2/zuvio/student5.js b/lib/v2/zuvio/student5.js
new file mode 100644
index 000000000..a21efea6e
--- /dev/null
+++ b/lib/v2/zuvio/student5.js
@@ -0,0 +1,58 @@
+const got = require('@/utils/got');
+const { parseDate } = require('@/utils/parse-date');
+const timezone = require('@/utils/timezone');
+const { token, apiUrl, rootUrl, renderDesc, getBoards } = require('./utils');
+
+module.exports = async (ctx) => {
+ const { board = '' } = ctx.params;
+ const title = board ? (await getBoards(ctx.cache.tryGet)).find((i) => i.boardId === board).title : '全部';
+
+ const { data } = await got(`${apiUrl}/article`, {
+ searchParams: {
+ api_token: token,
+ user_id: '0',
+ board_id: board,
+ sort: 'time',
+ page: '1',
+ device: 'web',
+ my_school_opinion: '1',
+ },
+ });
+
+ const items = data.articles.map((item) => ({
+ title: item.title,
+ description: item.abstract,
+ pubDate: timezone(parseDate(item.created_at), +8),
+ link: `${rootUrl}/article/${item.id}`,
+ api: `${apiUrl}/article/${item.id}`,
+ author: `${item.university} ${item.user_name}`,
+ category: item.board_name,
+ }));
+
+ await Promise.all(
+ items.map((item) =>
+ ctx.cache.tryGet(item.link, async () => {
+ const { data } = await got(item.api, {
+ searchParams: {
+ api_token: token,
+ user_id: '0',
+ device: 'web',
+ ref: '',
+ },
+ });
+ item.description = renderDesc(data);
+ delete item.api;
+ return item;
+ })
+ )
+ );
+
+ ctx.state.data = {
+ title: `Zuvio 校園${title}話題 - 大學生論壇`,
+ description: 'Zuvio 校園話題千種動物頭像交流心得。最萌的匿名論壇,上千種逗趣動物頭像隨你變換,點頭像立即私訊功能,讓你找到共同話題的小夥伴!多人分享配對心得、聊天交友心情在此,快加入17分享!',
+ image: 'https://s3.hicloud.net.tw/zuvio.public/public/system/images/irs_v4/chicken/shared/webshare.png',
+ link: `${rootUrl}/articles`,
+ item: items,
+ language: 'zh-Hant',
+ };
+};
diff --git a/lib/v2/zuvio/templates/img.art b/lib/v2/zuvio/templates/img.art
new file mode 100644
index 000000000..cabd91bee
--- /dev/null
+++ b/lib/v2/zuvio/templates/img.art
@@ -0,0 +1,2 @@
+
+
diff --git a/lib/v2/zuvio/templates/link.art b/lib/v2/zuvio/templates/link.art
new file mode 100644
index 000000000..602771735
--- /dev/null
+++ b/lib/v2/zuvio/templates/link.art
@@ -0,0 +1,7 @@
+
+
+
+ {{ section.title}}
+ {{ section.description}}
+
+
diff --git a/lib/v2/zuvio/templates/ref_article.art b/lib/v2/zuvio/templates/ref_article.art
new file mode 100644
index 000000000..42e39281a
--- /dev/null
+++ b/lib/v2/zuvio/templates/ref_article.art
@@ -0,0 +1,8 @@
+引用自:
+
+
+ {{ ref_article.university }} {{ ref_article.user_name }} {{ ref_article.board_name }}
+ {{ ref_article.title }}
+ {{ ref_article.abstract }}
+
+
diff --git a/lib/v2/zuvio/templates/rss.art b/lib/v2/zuvio/templates/rss.art
new file mode 100644
index 000000000..862f00342
--- /dev/null
+++ b/lib/v2/zuvio/templates/rss.art
@@ -0,0 +1 @@
+{{ id }}
diff --git a/lib/v2/zuvio/templates/youtube.art b/lib/v2/zuvio/templates/youtube.art
new file mode 100644
index 000000000..5c37a4a2c
--- /dev/null
+++ b/lib/v2/zuvio/templates/youtube.art
@@ -0,0 +1,2 @@
+
+
diff --git a/lib/v2/zuvio/utils.js b/lib/v2/zuvio/utils.js
new file mode 100644
index 000000000..751f6e1d4
--- /dev/null
+++ b/lib/v2/zuvio/utils.js
@@ -0,0 +1,61 @@
+const { art } = require('@/utils/render');
+const path = require('path');
+const got = require('@/utils/got');
+
+const token =
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0OTcyMzI1MjMsInN5c3RlbV9uYW1lIjoiZm9ydW0iLCJ6dXZpb19pZCI6LTk5OSwiZW1haWwiOm51bGwsIm5hbWUiOm51bGwsInVuaXZlcnNpdHlfaWQiOm51bGwsInVuaXZlcnNpdHlfbmFtZSI6bnVsbH0.0KoJiSnyazsJxLCNEnqnuNUdKsJFhBdCn3R2BJpoUtk';
+const apiUrl = 'https://forum.zuvio.com.tw/api';
+const rootUrl = 'https://irs.zuvio.com.tw/student5/chickenM';
+
+const renderDesc = (data) => {
+ let output = '';
+ if (data.ref_article) {
+ output += art(path.join(__dirname, 'templates/ref_article.art'), {
+ ref_article: data.ref_article,
+ });
+ }
+ output += renderSections(data.sections);
+ return output;
+};
+
+const renderSections = (sections) => {
+ let output = '';
+ for (const section of sections) {
+ if (section.type === 'text') {
+ output += section.content.replace(/\n/g, '
');
+ } else if (section.type === 'img') {
+ output += art(path.join(__dirname, 'templates/img.art'), { section });
+ } else if (section.type === 'youtube') {
+ output += art(path.join(__dirname, 'templates/youtube.art'), { section });
+ } else if (section.type === 'link') {
+ output += art(path.join(__dirname, 'templates/link.art'), { section });
+ }
+ }
+ return output;
+};
+
+const getBoards = (tryGet) =>
+ tryGet('zuvio:boards', async () => {
+ const { data } = await got(`${apiUrl}/board`, {
+ searchParams: {
+ api_token: token,
+ user_id: '0',
+ },
+ });
+
+ return data.map((item) => ({
+ title: item.name,
+ description: art(path.join(__dirname, 'templates/rss.art'), { id: item.id }),
+ boardId: item.id,
+ link: `${rootUrl}/articles/${item.id}`,
+ }));
+ });
+
+module.exports = {
+ token,
+ apiUrl,
+ rootUrl,
+ renderDesc,
+ renderSections,
+ getBoards,
+};