diff --git a/docs/en/install/README.md b/docs/en/install/README.md
index 0c85c3c24..42f6962c9 100644
--- a/docs/en/install/README.md
+++ b/docs/en/install/README.md
@@ -769,9 +769,9 @@ See docs of the specified route and `lib/config.js` for detailed information.
- Mastodon user timeline: apply API here `https://mastodon.example/settings/applications`(repalce `mastodon.example`), please check scope `read:search`
- - `MASTODON_API_HOST`: API instance domain
+ - `MASTODON_API_HOST`: API instance domain, only domain, no `http://` or `https://` protocol header
- `MASTODON_API_ACCESS_TOKEN`: user access token
- - `MASTODON_API_ACCT_DOMAIN`: acct domain for particular instance
+ - `MASTODON_API_ACCT_DOMAIN`: acct domain for particular instance, Webfinger account URI, like `user@host`
- Medium related routes: Open the console, copy the cookie (in theory, only uid and sid are required)
diff --git a/docs/en/social-media.md b/docs/en/social-media.md
index 6163d82e4..a95eafe0c 100644
--- a/docs/en/social-media.md
+++ b/docs/en/social-media.md
@@ -217,26 +217,36 @@ Official user RSS:
- RSS: `https://**:instance**/users/**:username**.rss` ([Example](https://pawoo.net/users/pawoo_support.rss))
- Atom: ~~`https://**:instance**/users/**:username**.atom`~~ (Only for pawoo.net, [example](https://pawoo.net/users/pawoo_support.atom))
-These feed do not include boosts (a.k.a. reblogs). RSSHub provides a feed for user timeline based on the Mastodon API, but to use that, you will need to create application on a Mastodon instance, and configure your RSSHub instance. Check the [Deploy Guide](/en/install/#route-specific-configurations) for route-specific configurations.
+These feed do not include boosts (a.k.a. reblogs). RSSHub provides a feed for user timeline based on the Mastodon API, but to use that, you may need to create application on a Mastodon instance, and configure your RSSHub instance. Check the [Deploy Guide](/en/install/#route-specific-configurations) for route-specific configurations.
:::
### User timeline
-
+
+
+Started from Mastodon v4.0.0, the use of the `search` API in the route no longer requires a user token.
+If the domain of your Webfinger account URI is the same as the API host of the instance (i.e., no delegation called in some other protocols), then no configuration is required and the route is available out of the box.
+However, you can still specify these route-specific configurations if you need to override them.
### Instance timeline (local)
+If the instance address is not `mastodon.social` or `pawoo.net`, then the route requires `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN` to be `true`.
+
### Instance timeline (federated)
+If the instance address is not `mastodon.social` or `pawoo.net`, then the route requires `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN` to be `true`.
+
### User timeline (backup)
+If the instance address is not `mastodon.social` or `pawoo.net`, then the route requires `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN` to be `true`.
+
## Misskey
### Featured Notes
diff --git a/docs/install/README.md b/docs/install/README.md
index ca9cadcde..eba179ffb 100644
--- a/docs/install/README.md
+++ b/docs/install/README.md
@@ -799,9 +799,9 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
- Mastodon 用户时间线路由:访问 `https://mastodon.example/settings/applications` 申请(替换掉 `mastodon.example`)。需要 `read:search` 权限
- - `MASTODON_API_HOST`: API 请求的实例
+ - `MASTODON_API_HOST`: API 请求的实例,仅域名,不包括 `http://` 或 `https://` 协议头
- `MASTODON_API_ACCESS_TOKEN`: 用户 access token, 申请应用后,在应用配置页可以看到申请者的 access token
- - `MASTODON_API_ACCT_DOMAIN`: 该实例本地用户 acct 标识的域名
+ - `MASTODON_API_ACCT_DOMAIN`: 该实例本地用户 acct 标识的域名,Webfinger account URI,形如 `user@host`
- Medium 相关路由:打开控制台,复制 Cookie(理论上只需要 uid 和 sid 即可)
diff --git a/docs/social-media.md b/docs/social-media.md
index 537e1d27e..504134dde 100644
--- a/docs/social-media.md
+++ b/docs/social-media.md
@@ -549,7 +549,7 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
例如: 或
-上述订阅源的内容不包括用户的转嘟。RSSHub 提供基于 Mastodon API 的订阅源,但需要您在某个 Mastodon 实例申请 API,并对 RSSHub 实例进行配置。详情见部署页面的配置模块。
+上述订阅源的内容不包括用户的转嘟。RSSHub 提供基于 Mastodon API 的订阅源,但可能需要您在某个 Mastodon 实例申请 API,并对 RSSHub 实例进行配置。详情见部署页面的[配置模块](/install/#route-specific-configurations)。
:::
@@ -557,18 +557,28 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
+自 Mastodon v4.0.0 起,本路由中对于 `search` API 的使用不再需要访问令牌。
+如果你的 Webfinger account URI 域和实例的 API 服务器域名是一样的(即没有一些其他协议称呼的 deletation),那么此路由不需要额外配置且开箱即用。
+不过,你依然可以提供这些路由特定的配置来覆盖它们。
+
### 实例公共时间线(本站)
+实例地址不为 `mastodon.social` 或 `pawoo.net` 的情况下均需要 `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN` 为 `true`。
+
### 实例公共时间线(跨站)
+实例地址不为 `mastodon.social` 或 `pawoo.net` 的情况下均需要 `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN` 为 `true`。
+
### 用户公共时间线(备用)
+实例地址不为 `mastodon.social` 或 `pawoo.net` 的情况下均需要 `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN` 为 `true`。
+
## Misskey
### 精选笔记
diff --git a/lib/router.js b/lib/router.js
index aa4b45e75..118071e41 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -2281,10 +2281,10 @@ router.get('/755/user/:username', lazyloadRouteHandler('./routes/755/user'));
// router.get('/ikea/uk/offer', lazyloadRouteHandler('./routes/ikea/uk/offer'));
// Mastodon
-router.get('/mastodon/timeline/:site/:only_media?', lazyloadRouteHandler('./routes/mastodon/timeline_local'));
-router.get('/mastodon/remote/:site/:only_media?', lazyloadRouteHandler('./routes/mastodon/timeline_remote'));
-router.get('/mastodon/account_id/:site/:account_id/statuses/:only_media?', lazyloadRouteHandler('./routes/mastodon/account_id'));
-router.get('/mastodon/acct/:acct/statuses/:only_media?', lazyloadRouteHandler('./routes/mastodon/acct'));
+// router.get('/mastodon/timeline/:site/:only_media?', lazyloadRouteHandler('./routes/mastodon/timeline_local'));
+// router.get('/mastodon/remote/:site/:only_media?', lazyloadRouteHandler('./routes/mastodon/timeline_remote'));
+// router.get('/mastodon/account_id/:site/:account_id/statuses/:only_media?', lazyloadRouteHandler('./routes/mastodon/account_id'));
+// router.get('/mastodon/acct/:acct/statuses/:only_media?', lazyloadRouteHandler('./routes/mastodon/acct'));
// Kernel Aliyun
router.get('/aliyun-kernel/index', lazyloadRouteHandler('./routes/aliyun-kernel/index'));
diff --git a/lib/routes/mastodon/account_id.js b/lib/v2/mastodon/account_id.js
similarity index 100%
rename from lib/routes/mastodon/account_id.js
rename to lib/v2/mastodon/account_id.js
diff --git a/lib/routes/mastodon/acct.js b/lib/v2/mastodon/acct.js
similarity index 100%
rename from lib/routes/mastodon/acct.js
rename to lib/v2/mastodon/acct.js
diff --git a/lib/v2/mastodon/maintainer.js b/lib/v2/mastodon/maintainer.js
new file mode 100644
index 000000000..222617d97
--- /dev/null
+++ b/lib/v2/mastodon/maintainer.js
@@ -0,0 +1,6 @@
+module.exports = {
+ '/account_id/:site/:account_id/statuses/:only_media?': ['notofoe'],
+ '/acct/:acct/statuses/:only_media?': ['notofoe'],
+ '/remote/:site/:only_media?': ['hoilc'],
+ '/timeline/:site/:only_media?': ['hoilc'],
+};
diff --git a/lib/v2/mastodon/radar.js b/lib/v2/mastodon/radar.js
new file mode 100644
index 000000000..59cdc5fbd
--- /dev/null
+++ b/lib/v2/mastodon/radar.js
@@ -0,0 +1,30 @@
+module.exports = {
+ 'mastodon.social': {
+ _name: 'Mastodon',
+ '.': [
+ {
+ title: '用户公共时间线',
+ docs: 'https://docs.rsshub.app/social-media.html#mastodon',
+ source: ['/:acct'],
+ target: (params, url) => (params.acct.startsWith('@') ? `/mastodon/acct/${params.substring(1)}@${new URL(url).host}/statuses` : ''),
+ },
+ {
+ title: '实例公共时间线(本站)',
+ docs: 'https://docs.rsshub.app/social-media.html#mastodon',
+ source: ['/'],
+ target: (_, url) => `/mastodon/timeline/${new URL(url).host}`,
+ },
+ {
+ title: '实例公共时间线(跨站)',
+ docs: 'https://docs.rsshub.app/social-media.html#mastodon',
+ source: ['/'],
+ target: (_, url) => `/mastodon/remote/${new URL(url).host}`,
+ },
+ {
+ title: '用户公共时间线(备用)',
+ docs: 'https://docs.rsshub.app/social-media.html#mastodon',
+ source: ['/:acct'],
+ },
+ ],
+ },
+};
diff --git a/lib/v2/mastodon/router.js b/lib/v2/mastodon/router.js
new file mode 100644
index 000000000..20fee58c1
--- /dev/null
+++ b/lib/v2/mastodon/router.js
@@ -0,0 +1,6 @@
+module.exports = (router) => {
+ router.get('/account_id/:site/:account_id/statuses/:only_media?', require('./account_id'));
+ router.get('/acct/:acct/statuses/:only_media?', require('./acct'));
+ router.get('/remote/:site/:only_media?', require('./timeline_remote'));
+ router.get('/timeline/:site/:only_media?', require('./timeline_local'));
+};
diff --git a/lib/routes/mastodon/timeline_local.js b/lib/v2/mastodon/timeline_local.js
similarity index 96%
rename from lib/routes/mastodon/timeline_local.js
rename to lib/v2/mastodon/timeline_local.js
index 7a8b9ed2f..abbdd458b 100644
--- a/lib/routes/mastodon/timeline_local.js
+++ b/lib/v2/mastodon/timeline_local.js
@@ -16,7 +16,7 @@ module.exports = async (ctx) => {
ctx.state.data = {
title: `Local Public${ctx.params.only_media ? ' Media' : ''} Timeline on ${site}`,
- link: `http://${site}`,
+ link: `https://${site}`,
item: utils.parseStatuses(list),
};
};
diff --git a/lib/routes/mastodon/timeline_remote.js b/lib/v2/mastodon/timeline_remote.js
similarity index 96%
rename from lib/routes/mastodon/timeline_remote.js
rename to lib/v2/mastodon/timeline_remote.js
index 96d431fd2..67ae65543 100644
--- a/lib/routes/mastodon/timeline_remote.js
+++ b/lib/v2/mastodon/timeline_remote.js
@@ -16,7 +16,7 @@ module.exports = async (ctx) => {
ctx.state.data = {
title: `Federated Public${ctx.params.only_media ? ' Media' : ''} Timeline on ${site}`,
- link: `http://${site}`,
+ link: `https://${site}`,
item: utils.parseStatuses(list),
};
};
diff --git a/lib/routes/mastodon/utils.js b/lib/v2/mastodon/utils.js
similarity index 82%
rename from lib/routes/mastodon/utils.js
rename to lib/v2/mastodon/utils.js
index 4983a103c..8e11191b0 100644
--- a/lib/routes/mastodon/utils.js
+++ b/lib/v2/mastodon/utils.js
@@ -53,7 +53,7 @@ const parseStatuses = (data) =>
});
async function getAccountStatuses(site, account_id, only_media) {
- const statuses_url = `http://${site}/api/v1/accounts/${account_id}/statuses?only_media=${only_media}`;
+ const statuses_url = `https://${site}/api/v1/accounts/${account_id}/statuses?only_media=${only_media}`;
const statuses_response = await got({
method: 'get',
url: statuses_url,
@@ -64,7 +64,7 @@ async function getAccountStatuses(site, account_id, only_media) {
if (data.length !== 0 && data[0].account !== null) {
account_data = data[0].account;
} else {
- const account_url = `http://${site}/api/v1/accounts/${account_id}`;
+ const account_url = `https://${site}/api/v1/accounts/${account_id}`;
const account_response = await got({
method: 'get',
url: account_url,
@@ -79,13 +79,15 @@ async function getAccountIdByAcct(acct, ctx) {
const config = require('@/config').value;
const mastodonConfig = config.mastodon;
- if (!(mastodonConfig.apiHost && mastodonConfig.accessToken && mastodonConfig.acctDomain)) {
- throw 'this route require api configuration, please check documentation';
+ // acctHost is from the acct param of the request, and acctDomain is from either acctHost or the config
+ const acctHost = acct.split('@').filter((e) => e)[1];
+ const site = mastodonConfig.apiHost || acctHost;
+ const acctDomain = mastodonConfig.acctDomain || acctHost;
+ if (!(site && acctDomain)) {
+ throw Error('Mastodon RSS is disabled due to the lack of relevant config');
}
- const site = mastodonConfig.apiHost;
-
- const search_url = `http://${site}/api/v2/search`;
+ const search_url = `https://${site}/api/v2/search`;
const cacheUid = `mastodon_acct_id/${site}/${acct}`;
const account_id = await ctx.cache.tryGet(cacheUid, async () => {
@@ -93,7 +95,7 @@ async function getAccountIdByAcct(acct, ctx) {
method: 'get',
url: search_url,
headers: {
- Authorization: `Bearer ${mastodonConfig.accessToken}`,
+ ...(mastodonConfig.accessToken ? { Authorization: `Bearer ${mastodonConfig.accessToken}` } : {}),
},
searchParams: {
q: acct,
@@ -104,7 +106,7 @@ async function getAccountIdByAcct(acct, ctx) {
let acctOnServer;
if (acctHost) {
- if (acctHost === mastodonConfig.acctDomain) {
+ if (acctHost === acctDomain) {
acctOnServer = acctUser;
} else {
acctOnServer = acctUser + '@' + acctHost;