diff --git a/docs/en/social-media.md b/docs/en/social-media.md
index fe64739a3..152b04a6b 100644
--- a/docs/en/social-media.md
+++ b/docs/en/social-media.md
@@ -34,6 +34,16 @@ pageClass: routes
+## Gab
+
+### User's Posts
+
+
+
+### Popular Posts
+
+
+
## Instagram
::: warning
diff --git a/docs/social-media.md b/docs/social-media.md
index 02a01239a..71bdca940 100644
--- a/docs/social-media.md
+++ b/docs/social-media.md
@@ -320,6 +320,16 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
+## Gab
+
+### 用戶時間線
+
+
+
+### 熱門
+
+
+
## iCity
### 用户动态
diff --git a/lib/router.js b/lib/router.js
index ef8444c5f..53dc5ce41 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -3800,6 +3800,7 @@ router.get('/semiconductors/latest-news', require('./routes/semiconductors/lates
// VOA News
router.get('/voa/day-photos', require('./routes/voa/day-photos'));
+
// Voice of America
router.get('/voa/:language/:channel?', require('./routes/voa/index'));
@@ -3861,4 +3862,8 @@ router.get('/booth.pm/shop/:subdomain', require('./routes/booth-pm/shop'));
// Minecraft feed the beast
router.get('/feed-the-beast/modpack/:modpackEntry', require('./routes/feed-the-beast/modpack'));
+// Gab
+router.get('/gab/user/:username', require('./routes/gab/user'));
+router.get('/gab/popular/:sort?', require('./routes/gab/explore'));
+
module.exports = router;
diff --git a/lib/routes/gab/explore.js b/lib/routes/gab/explore.js
new file mode 100644
index 000000000..5c9a66454
--- /dev/null
+++ b/lib/routes/gab/explore.js
@@ -0,0 +1,31 @@
+const cheerio = require('cheerio');
+const got = require('@/utils/got');
+
+module.exports = async (ctx) => {
+ const sort = ctx.params.sort || 'hot';
+
+ let baseURL = 'https://gab.com/api/v1/timelines/explore?page=1&sort_by=';
+ baseURL += sort === 'hot' ? 'hot' : 'top_today';
+
+ const response = await got.get(baseURL);
+
+ const result = await Promise.all(
+ response.data.map((item) => {
+ const media = item.media_attachments.map((item) => `
`).join('
');
+ const $ = cheerio.load(item.content);
+
+ return {
+ title: $.text() || 'Untitled',
+ description: item.content + (media && '
' + media),
+ link: item.url || baseURL,
+ pubDate: new Date(item.created_at).toISOString(),
+ };
+ })
+ );
+
+ ctx.state.data = {
+ title: `Gab - Popular posts`,
+ link: baseURL,
+ item: result,
+ };
+};
diff --git a/lib/routes/gab/user.js b/lib/routes/gab/user.js
new file mode 100644
index 000000000..8cb350ff7
--- /dev/null
+++ b/lib/routes/gab/user.js
@@ -0,0 +1,32 @@
+const cheerio = require('cheerio');
+const got = require('@/utils/got');
+
+module.exports = async (ctx) => {
+ const username = ctx.params.username;
+
+ const response = await got.get('https://gab.com/api/v1/account_by_username/' + username);
+ const id = response.data.id;
+ const gabs = await got.get(`https://gab.com/api/v1/accounts/${id}/statuses?exclude_replies=true`);
+
+ const result = await Promise.all(
+ gabs.data
+ .filter((item) => !item.reblog)
+ .map((item) => {
+ const media = item.media_attachments.map((item) => `
`).join('
');
+ const $ = cheerio.load(item.content);
+
+ return {
+ title: $.text() || 'Untitled',
+ description: item.content + (media && '
' + media),
+ link: item.url || `https://gab.com/${username}`,
+ pubDate: new Date(item.created_at).toISOString(),
+ };
+ })
+ );
+
+ ctx.state.data = {
+ title: `Gab - ${username}`,
+ link: `https://gab.com/${username}`,
+ item: result,
+ };
+};