From c1198e3420f41c375e4c54c7874267fb9370ffca Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Sun, 30 Jun 2024 12:36:30 +0900
Subject: [PATCH] docs: list available Mastodon API endpoints/methods

---
 docs/api-change.md                            | 123 ++++++++++++++++++
 .../server/api/mastodon/endpoints/account.ts  |   2 +-
 .../src/server/api/mastodon/endpoints/list.ts |   8 +-
 .../api/mastodon/endpoints/notifications.ts   |   2 +-
 .../server/api/mastodon/endpoints/timeline.ts |  14 +-
 5 files changed, 136 insertions(+), 13 deletions(-)

diff --git a/docs/api-change.md b/docs/api-change.md
index cc70b9dcf9..c9bc7fde37 100644
--- a/docs/api-change.md
+++ b/docs/api-change.md
@@ -8,6 +8,129 @@ Breaking changes are indicated by the :warning: icon.
   - :warning: The new API uses a new format to manage Mastodon sessions in the database, whereas old implementation uses Misskey sessions. All previous client app and token registrations will not work with the new API. All clients need to be re-registered and all users need to re-authenticate.
   - :warning: All IDs (of statuses/notes, notifications, users, etc.) will be using the alphanumerical format, aligning with the Firefish/Misskey API. The old numerical IDs will not work when queried against the new API.
 
+<details>
+
+<summary>Available endpoints (under <code>https://instance-domain/api/</code>)</summary>
+
+- `POST oauth/token`
+- `POST oauth/revoke`
+- `POST v1/apps`
+- `GET  v1/apps/verify_credentials`
+- `POST v1/firefish/apps/info` (Firefish extension, uses MiAuth)
+- `POST v1/firefish/auth/code` (Firefish extension, uses MiAuth)
+
+- `GET   v1/accounts/verify_credentials`
+- `PATCH v1/accounts/update_credentials`
+- `GET   v1/accounts/lookup`
+- `GET   v1/accounts/relationships`
+- `GET   v1/accounts/search`
+- `GET   v1/accounts/:id`
+- `GET   v1/accounts/:id/statuses`
+- `GET   v1/accounts/:id/featured_tags`
+- `GET   v1/accounts/:id/followers`
+- `GET   v1/accounts/:id/following`
+- `GET   v1/accounts/:id/lists`
+- `POST  v1/accounts/:id/follow`
+- `POST  v1/accounts/:id/unfollow`
+- `POST  v1/accounts/:id/block`
+- `POST  v1/accounts/:id/unblock`
+- `POST  v1/accounts/:id/mute`
+- `POST  v1/accounts/:id/unmute`
+
+- `GET v1/featured_tags` (returns an empty list)
+- `GET v1/followed_tags` (returns an empty list)
+- `GET v1/bookmarks`
+- `GET v1/favourites`
+
+- `GET  v1/mutes`
+- `GET  v1/blocks`
+- `GET  v1/follow_requests`
+- `POST v1/follow_requests/:id/authorize`
+- `POST v1/follow_requests/:id/reject`
+
+- `GET  v1/filters`
+- `POST v1/filters`
+- `GET  v2/filters`
+- `POST v2/filters`
+
+- `GET    v1/lists`
+- `POST   v1/lists`
+- `GET    v1/lists/:id`
+- `PUT    v1/lists/:id`
+- `DELETE v1/lists/:id`
+- `GET    v1/lists/:id/accounts`
+- `POST   v1/lists/:id/accounts`
+- `DELETE v1/lists/:id/accounts`
+
+- `GET  v1/media/:id`
+- `PUT  v1/media/:id`
+- `POST v1/media`
+- `POST v2/media`
+
+- `GET  v1/custom_emojis`
+- `GET  v1/instance`
+- `GET  v2/instance`
+- `GET  v1/announcements`
+- `POST v1/announcements/:id/dismiss`
+- `GET  v1/trends` (pagination is unimplemented)
+- `GET  v1/trends/tags` (pagination is unimplemented)
+- `GET  v1/trends/statuses`
+- `GET  v1/trends/links` (returns an empty list)
+- `GET  v1/preferences`
+- `GET  v2/suggestions`
+
+- `GET    v1/notifications`
+- `GET    v1/notifications/:id`
+- `POST   v1/notifications/clear`
+- `POST   v1/notifications/:id/dismiss`
+- `POST   v1/conversations/:id/read`
+- `GET    v1/push/subscription`
+- `POST   v1/push/subscription`
+- `DELETE v1/push/subscription`
+
+- `GET v1/search`
+- `GET v2/search`
+
+- `POST   v1/statuses`
+- `PUT    v1/statuses/:id`
+- `GET    v1/statuses/:id`
+- `DELETE v1/statuses/:id`
+- `GET    v1/statuses/:id/context`
+- `GET    v1/statuses/:id/history`
+- `GET    v1/statuses/:id/source`
+- `GET    v1/statuses/:id/reblogged_by`
+- `GET    v1/statuses/:id/favourited_by`
+- `POST   v1/statuses/:id/favourite`
+- `POST   v1/statuses/:id/unfavourite`
+- `POST   v1/statuses/:id/reblog`
+- `POST   v1/statuses/:id/unreblog`
+- `POST   v1/statuses/:id/bookmark`
+- `POST   v1/statuses/:id/unbookmark`
+- `POST   v1/statuses/:id/pin`
+- `POST   v1/statuses/:id/unpin`
+- `POST   v1/statuses/:id/react/:name`
+- `POST   v1/statuses/:id/unreact/:name`
+- `POST   v1/statuses/:id/translate`
+
+- `GET  v1/polls/:id`
+- `POST v1/polls/:id/votes`
+
+- `GET    v1/scheduled_statuses`
+- `GET    v1/scheduled_statuses/:id` (reschedule (`PUT` method) is unimplemented)
+- `DELETE v1/scheduled_statuses/:id`
+
+- `GET v1/streaming/health`
+
+- `GET  v1/timelines/public`
+- `GET  v1/timelines/tag/:hashtag`
+- `GET  v1/timelines/home`
+- `GET  v1/timelines/list/:listId`
+- `GET  v1/conversations`
+- `GET  v1/markers`
+- `POST v1/markers`
+
+</details>
+
 ## v20240607
 
 - `GET` request is now allowed for the `latest-version` endpoint.
diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts
index 25cf3ac1da..c835830db3 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/account.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts
@@ -34,7 +34,7 @@ export function setupEndpointsAccount(router: Router): void {
 		async (ctx) => {
 			const ids =
 				normalizeUrlQuery(ctx.query, ["id[]"])["id[]"] ??
-				normalizeUrlQuery(ctx.query, ["id"])["id"] ??
+				normalizeUrlQuery(ctx.query, ["id"]).id ??
 				[];
 			ctx.body = await UserHelpers.getUserRelationhipToMany(ids, ctx.user.id);
 		},
diff --git a/packages/backend/src/server/api/mastodon/endpoints/list.ts b/packages/backend/src/server/api/mastodon/endpoints/list.ts
index e35c9fe316..ea06b10ad5 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/list.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/list.ts
@@ -12,17 +12,17 @@ import { auth } from "@/server/api/mastodon/middleware/auth.js";
 import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
 
 export function setupEndpointsList(router: Router): void {
-	router.get("/v1/lists", auth(true, ["read:lists"]), async (ctx, reply) => {
+	router.get("/v1/lists", auth(true, ["read:lists"]), async (ctx, _reply) => {
 		ctx.body = await ListHelpers.getLists(ctx);
 	});
 	router.get<{ Params: { id: string } }>(
 		"/v1/lists/:id",
 		auth(true, ["read:lists"]),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			ctx.body = await ListHelpers.getListOr404(ctx.params.id, ctx);
 		},
 	);
-	router.post("/v1/lists", auth(true, ["write:lists"]), async (ctx, reply) => {
+	router.post("/v1/lists", auth(true, ["write:lists"]), async (ctx, _reply) => {
 		const body = ctx.request.body as any;
 		const title = (body.title ?? "").trim();
 		ctx.body = await ListHelpers.createList(title, ctx);
@@ -46,7 +46,7 @@ export function setupEndpointsList(router: Router): void {
 	router.delete<{ Params: { id: string } }>(
 		"/v1/lists/:id",
 		auth(true, ["write:lists"]),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const list = await UserLists.findOneBy({
 				userId: ctx.user.id,
 				id: ctx.params.id,
diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
index 8a6f079af0..2164c4f04b 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
@@ -67,7 +67,7 @@ export function setupEndpointsNotifications(router: Router): void {
 	router.post(
 		"/v1/conversations/:id/read",
 		auth(true, ["write:conversations"]),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			await NotificationHelpers.markConversationAsRead(ctx.params.id, ctx);
 			ctx.body = {};
 		},
diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
index 05373c3802..b651731a04 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
@@ -67,7 +67,7 @@ export function setupEndpointsTimeline(router: Router): void {
 		"/v1/timelines/public",
 		auth(true, ["read:statuses"]),
 		filterContext("public"),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)));
 			const res = await TimelineHelpers.getPublicTimeline(
 				args.max_id,
@@ -87,7 +87,7 @@ export function setupEndpointsTimeline(router: Router): void {
 		"/v1/timelines/tag/:hashtag",
 		auth(false, ["read:statuses"]),
 		filterContext("public"),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const tag = (ctx.params.hashtag ?? "").trim().toLowerCase();
 			const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query)), [
 				"any[]",
@@ -116,7 +116,7 @@ export function setupEndpointsTimeline(router: Router): void {
 		"/v1/timelines/home",
 		auth(true, ["read:statuses"]),
 		filterContext("home"),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const args = normalizeUrlQuery(limitToInt(ctx.query));
 			const res = await TimelineHelpers.getHomeTimeline(
 				args.max_id,
@@ -133,7 +133,7 @@ export function setupEndpointsTimeline(router: Router): void {
 		"/v1/timelines/list/:listId",
 		auth(true, ["read:lists"]),
 		filterContext("home"),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const list = await UserLists.findOneBy({
 				userId: ctx.user.id,
 				id: ctx.params.listId,
@@ -156,7 +156,7 @@ export function setupEndpointsTimeline(router: Router): void {
 	router.get(
 		"/v1/conversations",
 		auth(true, ["read:statuses"]),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const args = normalizeUrlQuery(limitToInt(ctx.query));
 			ctx.body = await TimelineHelpers.getConversations(
 				args.max_id,
@@ -171,7 +171,7 @@ export function setupEndpointsTimeline(router: Router): void {
 	router.get(
 		"/v1/markers",
 		auth(true, ["read:statuses"]),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const args = normalizeUrlQuery(ctx.query, ["timeline[]"]);
 			ctx.body = await TimelineHelpers.getMarkers(args["timeline[]"], ctx);
 		},
@@ -180,7 +180,7 @@ export function setupEndpointsTimeline(router: Router): void {
 	router.post(
 		"/v1/markers",
 		auth(true, ["write:statuses"]),
-		async (ctx, reply) => {
+		async (ctx, _reply) => {
 			const body = ctx.request.body;
 			ctx.body = await TimelineHelpers.setMarkers(body, ctx);
 		},