From e917dc6be368078055960e29750a97263ff0c39d Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Feb 2023 09:10:01 +0900
Subject: [PATCH 01/35] fix(client): validate urls to improve security

---
 packages/client/src/components/MkUrlPreview.vue | 1 +
 packages/client/src/components/global/MkUrl.vue | 1 +
 packages/client/src/pages/miauth.vue            | 2 ++
 3 files changed, 4 insertions(+)

diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue
index ef65cb796..865d4bcbe 100644
--- a/packages/client/src/components/MkUrlPreview.vue
+++ b/packages/client/src/components/MkUrlPreview.vue
@@ -67,6 +67,7 @@ const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
 let tweetHeight = $ref(150);
 
 const requestUrl = new URL(props.url);
+if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
 
 if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
 	const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
diff --git a/packages/client/src/components/global/MkUrl.vue b/packages/client/src/components/global/MkUrl.vue
index 2d328211d..d22c0b299 100644
--- a/packages/client/src/components/global/MkUrl.vue
+++ b/packages/client/src/components/global/MkUrl.vue
@@ -33,6 +33,7 @@ const props = defineProps<{
 
 const self = props.url.startsWith(local);
 const url = new URL(props.url);
+if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
 const el = ref();
 
 useTooltip(el, (showing) => {
diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue
index 6352dc329..eaf96d60f 100644
--- a/packages/client/src/pages/miauth.vue
+++ b/packages/client/src/pages/miauth.vue
@@ -70,6 +70,8 @@ async function accept(): Promise<void> {
 
 	state = 'accepted';
 	if (props.callback) {
+		const cbUrl = new URL(props.callback);
+		if (!['http:', 'https:'].includes(cbUrl.protocol)) throw new Error('invalid url');
 		location.href = appendQuery(props.callback, query({
 			session: props.session,
 		}));

From 76011a3f28e38d0561efcc4ca1d3997554717950 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 10 Feb 2023 11:14:33 -0800
Subject: [PATCH 02/35] fix: :lock: prevent issues

---
 .../src/remote/activitypub/models/note.ts        | 12 +++++++++++-
 .../src/remote/activitypub/models/person.ts      | 16 ++++++++++++++--
 packages/backend/src/server/web/url-preview.ts   |  8 ++++++++
 packages/client/src/pages/auth.vue               |  2 ++
 packages/client/src/pages/miauth.vue             |  3 +--
 packages/client/src/scripts/aiscript/api.ts      |  6 +++++-
 6 files changed, 41 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts
index afb3af6cb..34d8d0ba1 100644
--- a/packages/backend/src/remote/activitypub/models/note.ts
+++ b/packages/backend/src/remote/activitypub/models/note.ts
@@ -111,6 +111,16 @@ export async function createNote(
 
 	const note: IPost = object;
 
+	if (note.id && !note.id.startsWith('https://')) {
+		throw new Error(`unexpected shcema of note.id: ${note.id}`);
+	}
+
+	const url = getOneApHrefNullable(note.url);
+
+	if (url && !url.startsWith('https://')) {
+		throw new Error(`unexpected shcema of note url: ${url}`);
+	}
+
 	logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
 
 	logger.info(`Creating the Note: ${note.id}`);
@@ -345,7 +355,7 @@ export async function createNote(
 			apEmojis,
 			poll,
 			uri: note.id,
-			url: getOneApHrefNullable(note.url),
+			url: url,
 		},
 		silent,
 	);
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 0ec671f0a..88bbca5c4 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -195,6 +195,12 @@ export async function createPerson(
 
 	const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
 
+	const url = getOneApHrefNullable(person.url);
+
+	if (url && !url.startsWith('https://')) {
+		throw new Error(`unexpected shcema of person url: ${url}`);
+	}
+
 	// Create user
 	let user: IRemoteUser;
 	try {
@@ -237,7 +243,7 @@ export async function createPerson(
 					description: person.summary
 						? htmlToMfm(truncate(person.summary, summaryLength), person.tag)
 						: null,
-					url: getOneApHrefNullable(person.url),
+					url: url,
 					fields,
 					birthday: bday ? bday[0] : null,
 					location: person["vcard:Address"] || null,
@@ -387,6 +393,12 @@ export async function updatePerson(
 
 	const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
 
+	const url = getOneApHrefNullable(person.url);
+
+	if (url && !url.startsWith('https://')) {
+		throw new Error(`unexpected shcema of person url: ${url}`);
+	}
+
 	const updates = {
 		lastFetchedAt: new Date(),
 		inbox: person.inbox,
@@ -430,7 +442,7 @@ export async function updatePerson(
 	await UserProfiles.update(
 		{ userId: exist.id },
 		{
-			url: getOneApHrefNullable(person.url),
+			url: url,
 			fields,
 			description: person.summary
 				? htmlToMfm(truncate(person.summary, summaryLength), person.tag)
diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts
index d7da4e72c..cb58efa81 100644
--- a/packages/backend/src/server/web/url-preview.ts
+++ b/packages/backend/src/server/web/url-preview.ts
@@ -44,6 +44,14 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
 
 		logger.succ(`Got preview of ${url}: ${summary.title}`);
 
+		if (summary.url && !(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) {
+			throw new Error('unsupported schema included');
+		}
+
+		if (summary.player?.url && !(summary.player.url.startsWith('http://') || summary.player.url.startsWith('https://'))) {
+			throw new Error('unsupported schema included');
+		}
+
 		summary.icon = wrap(summary.icon);
 		summary.thumbnail = wrap(summary.thumbnail);
 
diff --git a/packages/client/src/pages/auth.vue b/packages/client/src/pages/auth.vue
index bb3c54bd3..9fc04d4f4 100644
--- a/packages/client/src/pages/auth.vue
+++ b/packages/client/src/pages/auth.vue
@@ -80,6 +80,8 @@ export default defineComponent({
 			this.state = 'accepted';
 			const getUrlParams = () => window.location.search.substring(1).split('&').reduce((result, query) => { const [k, v] = query.split('='); result[k] = decodeURI(v); return result; }, {});
 			if (this.session.app.callbackUrl) {
+				const url = new URL(this.session.app.callbackUrl);
+				if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
 				location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}&state=${getUrlParams().state || ''}`;
 			}
 		}, onLogin(res) {
diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue
index eaf96d60f..292b47338 100644
--- a/packages/client/src/pages/miauth.vue
+++ b/packages/client/src/pages/miauth.vue
@@ -71,14 +71,13 @@ async function accept(): Promise<void> {
 	state = 'accepted';
 	if (props.callback) {
 		const cbUrl = new URL(props.callback);
-		if (!['http:', 'https:'].includes(cbUrl.protocol)) throw new Error('invalid url');
+		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
 		location.href = appendQuery(props.callback, query({
 			session: props.session,
 		}));
 	}
 }
 
-function deny(): void {
 	state = 'denied';
 }
 
diff --git a/packages/client/src/scripts/aiscript/api.ts b/packages/client/src/scripts/aiscript/api.ts
index b37eca8ab..32560b4ab 100644
--- a/packages/client/src/scripts/aiscript/api.ts
+++ b/packages/client/src/scripts/aiscript/api.ts
@@ -24,7 +24,11 @@ export function createAiScriptEnv(opts) {
 			return confirm.canceled ? values.FALSE : values.TRUE;
 		}),
 		"Mk:api": values.FN_NATIVE(async ([ep, param, token]) => {
-			if (token) utils.assertString(token);
+			if (token) {
+				utils.assertString(token);
+				// バグがあればundefinedもあり得るため念のため
+				if (typeof token.value !== 'string') throw new Error('invalid token');
+			}
 			apiRequests++;
 			if (apiRequests > 16) return values.NULL;
 			const res = await os.api(

From 8ddfd9630c1b1f194acebf563ee659567a42ef13 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 20:40:54 +0100
Subject: [PATCH 03/35] Revert "yeet koabody"

This reverts commit d5eb131f582ad1900392eafbf3e1f6d3e55f1d5f.
---
 .../src/server/api/mastodon/endpoints/account.ts       |  4 ++--
 .../backend/src/server/api/mastodon/endpoints/auth.ts  |  2 +-
 .../src/server/api/mastodon/endpoints/filter.ts        | 10 +++++-----
 .../src/server/api/mastodon/endpoints/notifications.ts |  8 ++++----
 .../src/server/api/mastodon/endpoints/search.ts        |  2 +-
 .../src/server/api/mastodon/endpoints/status.ts        |  6 +++---
 packages/backend/src/server/index.ts                   |  2 +-
 7 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts
index 61d4da8a8..1b55a5fbd 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/account.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts
@@ -33,7 +33,7 @@ export function apiAccountMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.patch('/v1/accounts/update_credentials', async (ctx) => {
+    router.patch('/v1/accounts/update_credentials', koaBody(), async (ctx) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -177,7 +177,7 @@ export function apiAccountMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute',  async (ctx) => {
+    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', koaBody(), async (ctx) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
index ff8b8a518..5f5756077 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
@@ -42,7 +42,7 @@ const writeScope = [
 
 export function apiAuthMastodon(router: Router): void {
 
-	router.post('/v1/apps',  async (ctx) => {
+	router.post('/v1/apps', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
index 810b8be11..3c66362dd 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
@@ -5,7 +5,7 @@ import { getClient } from '../ApiMastodonCompatibleService.js';
 
 export function apiFilterMastodon(router: Router): void {
 
-	router.get('/v1/filters',  async (ctx) => {
+	router.get('/v1/filters', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -20,7 +20,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.get('/v1/filters/:id',  async (ctx) => {
+	router.get('/v1/filters/:id', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -35,7 +35,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/filters',  async (ctx) => {
+	router.post('/v1/filters', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -50,7 +50,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/filters/:id',  async (ctx) => {
+	router.post('/v1/filters/:id', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -65,7 +65,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.delete('/v1/filters/:id',  async (ctx) => {
+	router.delete('/v1/filters/:id', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
index 625ff386c..59869da06 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
@@ -10,7 +10,7 @@ function toLimitToInt(q: any) {
 
 export function apiNotificationsMastodon(router: Router): void {
 
-	router.get('/v1/notifications',  async (ctx) => {
+	router.get('/v1/notifications', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -35,7 +35,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.get('/v1/notification/:id',  async (ctx) => {
+	router.get('/v1/notification/:id', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -56,7 +56,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/notifications/clear',  async (ctx) => {
+	router.post('/v1/notifications/clear', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -71,7 +71,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/notification/:id/dismiss',  async (ctx) => {
+	router.post('/v1/notification/:id/dismiss', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts
index dce3ff57c..f87e199f5 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/search.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts
@@ -5,7 +5,7 @@ import { getClient } from '../ApiMastodonCompatibleService.js';
 
 export function apiSearchMastodon(router: Router): void {
 
-	router.get('/v1/search',  async (ctx) => {
+	router.get('/v1/search', koaBody(), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts
index 8dc4ba5f7..593be10f9 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/status.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts
@@ -11,7 +11,7 @@ import axios from 'axios';
 const pump = promisify(pipeline);
 
 export function apiStatusMastodon(router: Router): void {
-    router.post('/v1/statuses',  async (ctx, reply) => {
+    router.post('/v1/statuses', koaBody(), async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -284,7 +284,7 @@ export function apiStatusMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.put<{ Params: { id: string } }>('/v1/media/:id',  async (ctx, reply) => {
+    router.put<{ Params: { id: string } }>('/v1/media/:id', koaBody(), async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -310,7 +310,7 @@ export function apiStatusMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes',  async (ctx, reply) => {
+    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', koaBody(), async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 6ade50d18..ea4740cba 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -141,7 +141,7 @@ router.get("/oauth/authorize", async (ctx) => {
 	ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
 });
 
-router.post("/oauth/token", async (ctx) => {
+router.get("/oauth/token", koaBody(), async (ctx) => {
 	const body: any = ctx.request.body;
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;

From 3f73e2ff849d626768a997220dadad4b21ab1ba0 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 20:45:29 +0100
Subject: [PATCH 04/35] Merge Masto Api changes

Co-authored-by Natty <natty.sh.git@gmail.com>
---
 .../src/server/api/endpoints/i/registry/get-unsecure.ts    | 2 +-
 .../server/api/mastodon/ApiMastodonCompatibleService.ts    | 4 ++--
 packages/backend/src/server/api/mastodon/endpoints/auth.ts | 6 +++++-
 packages/backend/src/server/index.ts                       | 7 +++++--
 4 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts b/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts
index 40065c83e..a8169aa95 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts
@@ -1,6 +1,6 @@
 import { ApiError } from "../../../error.js";
 import define from "../../../define.js";
-import { RegistryItems } from "../../../../../models/index.js";
+import {RegistryItems} from "@/models/index.js";
 
 export const meta = {
 	requireCredential: true,
diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
index 57a86c96d..cb00f9f38 100644
--- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
+++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
@@ -5,7 +5,7 @@ import { apiAccountMastodon } from './endpoints/account.js';
 import { apiStatusMastodon } from './endpoints/status.js';
 import { apiFilterMastodon } from './endpoints/filter.js';
 import { apiTimelineMastodon } from './endpoints/timeline.js';
-import { apiNotificationsMastodon } from './endpoints/notifications.js';
+import { apiNotificationMastodon } from './endpoints/notifications.js';
 import { apiSearchMastodon } from './endpoints/search.js';
 import { getInstance } from './endpoints/meta.js';
 
@@ -23,7 +23,7 @@ export function apiMastodonCompatible(router: Router): void {
 	apiStatusMastodon(router)
 	apiFilterMastodon(router)
 	apiTimelineMastodon(router)
-	apiNotificationsMastodon(router)
+	apiNotificationMastodon(router)
 	apiSearchMastodon(router)
 
 	router.get('/v1/custom_emojis', async (ctx) => {
diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
index 5f5756077..63dbcc364 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
@@ -2,6 +2,7 @@ import megalodon, { MegalodonInterface } from '@cutls/megalodon';
 import Router from "@koa/router";
 import { koaBody } from 'koa-body';
 import { getClient } from '../ApiMastodonCompatibleService.js';
+import bodyParser from "koa-bodyparser";
 
 const readScope = [
 	'read:account',
@@ -42,7 +43,10 @@ const writeScope = [
 
 export function apiAuthMastodon(router: Router): void {
 
-	router.post('/v1/apps', koaBody(), async (ctx) => {
+	router.post('/v1/apps', koaBody({
+		json: false,
+		multipart: true
+	}), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index ea4740cba..14c127e8b 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -20,7 +20,7 @@ import { createTemp } from "@/misc/create-temp.js";
 import { publishMainStream } from "@/services/stream.js";
 import * as Acct from "@/misc/acct.js";
 import { envOption } from "@/env.js";
-import { koaBody } from 'koa-body';
+import {koaBody} from "koa-body";
 import megalodon, { MegalodonInterface } from '@cutls/megalodon';
 import activityPub from "./activitypub.js";
 import nodeinfo from "./nodeinfo.js";
@@ -141,7 +141,10 @@ router.get("/oauth/authorize", async (ctx) => {
 	ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
 });
 
-router.get("/oauth/token", koaBody(), async (ctx) => {
+router.post("/oauth/token", koaBody({
+	json: false,
+	multipart: true
+}), async (ctx) => {
 	const body: any = ctx.request.body;
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;

From c19b0758456fe2c788a5c965a63292012cfcbc02 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 20:50:42 +0100
Subject: [PATCH 05/35] make build work after calcks merge

---
 packages/client/src/pages/miauth.vue | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue
index 292b47338..a71c7b9a5 100644
--- a/packages/client/src/pages/miauth.vue
+++ b/packages/client/src/pages/miauth.vue
@@ -78,6 +78,7 @@ async function accept(): Promise<void> {
 	}
 }
 
+function deny(): void {
 	state = 'denied';
 }
 

From ebd53e1f90613efad3d4d46ade806eda6c51bb69 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 20:52:58 +0100
Subject: [PATCH 06/35] weird merge error

---
 .../src/server/api/mastodon/ApiMastodonCompatibleService.ts     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
index cb00f9f38..65f8130a6 100644
--- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
+++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
@@ -5,7 +5,7 @@ import { apiAccountMastodon } from './endpoints/account.js';
 import { apiStatusMastodon } from './endpoints/status.js';
 import { apiFilterMastodon } from './endpoints/filter.js';
 import { apiTimelineMastodon } from './endpoints/timeline.js';
-import { apiNotificationMastodon } from './endpoints/notifications.js';
+import { apiNotificationsMastodon } from './endpoints/notifications.js';
 import { apiSearchMastodon } from './endpoints/search.js';
 import { getInstance } from './endpoints/meta.js';
 

From 78463f5f36f4de6d0b1f191246d14287134fd773 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 20:53:27 +0100
Subject: [PATCH 07/35] ree

---
 .../src/server/api/mastodon/ApiMastodonCompatibleService.ts     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
index 65f8130a6..57a86c96d 100644
--- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
+++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
@@ -23,7 +23,7 @@ export function apiMastodonCompatible(router: Router): void {
 	apiStatusMastodon(router)
 	apiFilterMastodon(router)
 	apiTimelineMastodon(router)
-	apiNotificationMastodon(router)
+	apiNotificationsMastodon(router)
 	apiSearchMastodon(router)
 
 	router.get('/v1/custom_emojis', async (ctx) => {

From 1024e4d02707a090ae300339c6b86610e970eac6 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 21:16:55 +0100
Subject: [PATCH 08/35] merge more multipart stuff

---
 .../src/server/api/mastodon/endpoints/account.ts       |  4 ++--
 .../src/server/api/mastodon/endpoints/filter.ts        | 10 +++++-----
 .../src/server/api/mastodon/endpoints/notifications.ts |  8 ++++----
 .../src/server/api/mastodon/endpoints/search.ts        |  3 +--
 .../src/server/api/mastodon/endpoints/status.ts        |  6 +++---
 5 files changed, 15 insertions(+), 16 deletions(-)

diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts
index 1b55a5fbd..65caf7168 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/account.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts
@@ -33,7 +33,7 @@ export function apiAccountMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.patch('/v1/accounts/update_credentials', koaBody(), async (ctx) => {
+    router.patch('/v1/accounts/update_credentials', koaBody({ multipart: true }), async (ctx) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -177,7 +177,7 @@ export function apiAccountMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', koaBody(), async (ctx) => {
+    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', koaBody({ multipart: true }), async (ctx) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
index 3c66362dd..b4f67cf1e 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
@@ -5,7 +5,7 @@ import { getClient } from '../ApiMastodonCompatibleService.js';
 
 export function apiFilterMastodon(router: Router): void {
 
-	router.get('/v1/filters', koaBody(), async (ctx) => {
+	router.get('/v1/filters', koaBody({ multipart: true }), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -20,7 +20,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.get('/v1/filters/:id', koaBody(), async (ctx) => {
+	router.get('/v1/filters/:id', koaBody({ multipart: true }), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -35,7 +35,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/filters', koaBody(), async (ctx) => {
+	router.post('/v1/filters', koaBody({ multipart: true }), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -50,7 +50,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/filters/:id', koaBody(), async (ctx) => {
+	router.post('/v1/filters/:id', koaBody({ multipart: true }), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -65,7 +65,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.delete('/v1/filters/:id', koaBody(), async (ctx) => {
+	router.delete('/v1/filters/:id', koaBody({ multipart: true }), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
index 59869da06..b4599de80 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
@@ -10,7 +10,7 @@ function toLimitToInt(q: any) {
 
 export function apiNotificationsMastodon(router: Router): void {
 
-	router.get('/v1/notifications', koaBody(), async (ctx) => {
+	router.get('/v1/notifications', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -35,7 +35,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.get('/v1/notification/:id', koaBody(), async (ctx) => {
+	router.get('/v1/notification/:id', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -56,7 +56,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/notifications/clear', koaBody(), async (ctx) => {
+	router.post('/v1/notifications/clear', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -71,7 +71,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/notification/:id/dismiss', koaBody(), async (ctx) => {
+	router.post('/v1/notification/:id/dismiss', koaBody({ multipart: true }), async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts
index f87e199f5..dcd5be461 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/search.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts
@@ -1,11 +1,10 @@
 import megalodon, { MegalodonInterface } from '@cutls/megalodon';
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
 import { getClient } from '../ApiMastodonCompatibleService.js';
 
 export function apiSearchMastodon(router: Router): void {
 
-	router.get('/v1/search', koaBody(), async (ctx) => {
+	router.get('/v1/search', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts
index 593be10f9..cef966e47 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/status.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts
@@ -11,7 +11,7 @@ import axios from 'axios';
 const pump = promisify(pipeline);
 
 export function apiStatusMastodon(router: Router): void {
-    router.post('/v1/statuses', koaBody(), async (ctx, reply) => {
+    router.post('/v1/statuses', koaBody({ multipart: true }), async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -284,7 +284,7 @@ export function apiStatusMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.put<{ Params: { id: string } }>('/v1/media/:id', koaBody(), async (ctx, reply) => {
+    router.put<{ Params: { id: string } }>('/v1/media/:id', koaBody({ multipart: true }), async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -310,7 +310,7 @@ export function apiStatusMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', koaBody(), async (ctx, reply) => {
+    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', koaBody({ multipart: true }), async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);

From 9703b2496f6a4f79dbbd1c90979de680a0186ff7 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 22:30:19 +0100
Subject: [PATCH 09/35] temp test

---
 packages/backend/src/server/index.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 14c127e8b..34ffa3c0e 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -146,7 +146,8 @@ router.post("/oauth/token", koaBody({
 	multipart: true
 }), async (ctx) => {
 	const body: any = ctx.request.body;
-	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
+	//const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
+	const BASE_URL = "http://localhost:3000";
 	const generator = (megalodon as any).default;
 	const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
 	const m = body.code.match(/^[a-zA-Z0-9-]+/);

From 9b2cd8f2e842ef11acbea3352c3c28edab3243c4 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 23:00:15 +0100
Subject: [PATCH 10/35] this is super cursed

---
 packages/backend/src/server/api/index.ts | 8 ++++++++
 packages/backend/src/server/index.ts     | 3 +--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index da98a9df1..40ecd10ec 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -12,6 +12,7 @@ import { Instances, AccessTokens, Users } from "@/models/index.js";
 import config from "@/config/index.js";
 import endpoints from "./endpoints.js";
 import compatibility from "./compatibility.js";
+import {koaBody} from "koa-body";
 import handler from "./api-handler.js";
 import signup from "./private/signup.js";
 import signin from "./private/signin.js";
@@ -35,6 +36,13 @@ app.use(async (ctx, next) => {
 	await next();
 });
 
+app.use(
+	koaBody({
+		json: false,
+		multipart: true
+	})
+);
+
 app.use(
 	bodyParser({
 		// リクエストが multipart/form-data でない限りはJSONだと見なす
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 34ffa3c0e..14c127e8b 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -146,8 +146,7 @@ router.post("/oauth/token", koaBody({
 	multipart: true
 }), async (ctx) => {
 	const body: any = ctx.request.body;
-	//const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
-	const BASE_URL = "http://localhost:3000";
+	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;
 	const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
 	const m = body.code.match(/^[a-zA-Z0-9-]+/);

From 23aa51102bb65a3d0ff1f6d5135b918effc19e92 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 23:15:34 +0100
Subject: [PATCH 11/35] migrate middleware usage

Co-authored-by Natty <natty.sh.git@gmail.com>
---
 .../src/server/api/mastodon/endpoints/account.ts      |  4 ++--
 .../src/server/api/mastodon/endpoints/filter.ts       | 11 +++++------
 .../server/api/mastodon/endpoints/notifications.ts    |  2 +-
 .../src/server/api/mastodon/endpoints/status.ts       |  7 +++----
 .../src/server/api/mastodon/endpoints/timeline.ts     |  1 -
 packages/backend/src/server/index.ts                  | 11 +++++------
 6 files changed, 16 insertions(+), 20 deletions(-)

diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts
index 65caf7168..4fda37a48 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/account.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts
@@ -33,7 +33,7 @@ export function apiAccountMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.patch('/v1/accounts/update_credentials', koaBody({ multipart: true }), async (ctx) => {
+    router.patch('/v1/accounts/update_credentials', async (ctx) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -177,7 +177,7 @@ export function apiAccountMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', koaBody({ multipart: true }), async (ctx) => {
+    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', async (ctx) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
index b4f67cf1e..f665f8d23 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
@@ -1,11 +1,10 @@
 import megalodon, { MegalodonInterface } from '@cutls/megalodon';
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
 import { getClient } from '../ApiMastodonCompatibleService.js';
 
 export function apiFilterMastodon(router: Router): void {
 
-	router.get('/v1/filters', koaBody({ multipart: true }), async (ctx) => {
+	router.get('/v1/filters', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -20,7 +19,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.get('/v1/filters/:id', koaBody({ multipart: true }), async (ctx) => {
+	router.get('/v1/filters/:id', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -35,7 +34,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/filters', koaBody({ multipart: true }), async (ctx) => {
+	router.post('/v1/filters', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -50,7 +49,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/filters/:id', koaBody({ multipart: true }), async (ctx) => {
+	router.post('/v1/filters/:id', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -65,7 +64,7 @@ export function apiFilterMastodon(router: Router): void {
 		}
 	});
 
-	router.delete('/v1/filters/:id', koaBody({ multipart: true }), async (ctx) => {
+	router.delete('/v1/filters/:id', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
index b4599de80..e65b47f02 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
@@ -71,7 +71,7 @@ export function apiNotificationsMastodon(router: Router): void {
 		}
 	});
 
-	router.post('/v1/notification/:id/dismiss', koaBody({ multipart: true }), async (ctx) => {
+	router.post('/v1/notification/:id/dismiss', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts
index cef966e47..f01665537 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/status.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts
@@ -1,5 +1,4 @@
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
 import megalodon, { MegalodonInterface } from '@cutls/megalodon';
 import { getClient } from '../ApiMastodonCompatibleService.js';
 import fs from 'fs'
@@ -11,7 +10,7 @@ import axios from 'axios';
 const pump = promisify(pipeline);
 
 export function apiStatusMastodon(router: Router): void {
-    router.post('/v1/statuses', koaBody({ multipart: true }), async (ctx, reply) => {
+    router.post('/v1/statuses', async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -284,7 +283,7 @@ export function apiStatusMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.put<{ Params: { id: string } }>('/v1/media/:id', koaBody({ multipart: true }), async (ctx, reply) => {
+    router.put<{ Params: { id: string } }>('/v1/media/:id',async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
@@ -310,7 +309,7 @@ export function apiStatusMastodon(router: Router): void {
             ctx.body = e.response.data;
         }
     });
-    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', koaBody({ multipart: true }), async (ctx, reply) => {
+    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (ctx, reply) => {
         const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
         const accessTokens = ctx.headers.authorization;
         const client = getClient(BASE_URL, accessTokens);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
index 3fdb6ce88..2cbdf5a0e 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
@@ -1,5 +1,4 @@
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
 import megalodon, { Entity, MegalodonInterface } from '@cutls/megalodon';
 import { getClient } from '../ApiMastodonCompatibleService.js'
 import { statusModel } from './status.js';
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 14c127e8b..6609627fe 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -20,7 +20,6 @@ import { createTemp } from "@/misc/create-temp.js";
 import { publishMainStream } from "@/services/stream.js";
 import * as Acct from "@/misc/acct.js";
 import { envOption } from "@/env.js";
-import {koaBody} from "koa-body";
 import megalodon, { MegalodonInterface } from '@cutls/megalodon';
 import activityPub from "./activitypub.js";
 import nodeinfo from "./nodeinfo.js";
@@ -141,16 +140,16 @@ router.get("/oauth/authorize", async (ctx) => {
 	ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
 });
 
-router.post("/oauth/token", koaBody({
-	json: false,
-	multipart: true
-}), async (ctx) => {
+router.post("/oauth/token", async (ctx) => {
 	const body: any = ctx.request.body;
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;
 	const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
 	const m = body.code.match(/^[a-zA-Z0-9-]+/);
-	if (!m.length) return { error: 'Invalid code' }
+	if (!m.length) {
+		ctx.body = {error: 'Invalid code'}
+		return
+	}
 	try {
 		const atData = await client.fetchAccessToken(null, body.client_secret, m[0]);
 		ctx.body = {

From 2ff3e68d9463a88a4b13d8d8c67af8a49287648e Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 23:17:29 +0100
Subject: [PATCH 12/35] me forgorr

---
 packages/backend/src/server/api/mastodon/endpoints/auth.ts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
index 63dbcc364..6425aac09 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
@@ -43,10 +43,7 @@ const writeScope = [
 
 export function apiAuthMastodon(router: Router): void {
 
-	router.post('/v1/apps', koaBody({
-		json: false,
-		multipart: true
-	}), async (ctx) => {
+	router.post('/v1/apps', async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);

From 465cb5a5702375c024595f0cf02fca70b5f40699 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 23:29:29 +0100
Subject: [PATCH 13/35] use multer instead

---
 packages/backend/src/server/api/index.ts | 29 ++++++++++--------------
 pnpm-lock.yaml                           | 12 ++++++----
 2 files changed, 19 insertions(+), 22 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index 40ecd10ec..593c7a284 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -12,7 +12,6 @@ import { Instances, AccessTokens, Users } from "@/models/index.js";
 import config from "@/config/index.js";
 import endpoints from "./endpoints.js";
 import compatibility from "./compatibility.js";
-import {koaBody} from "koa-body";
 import handler from "./api-handler.js";
 import signup from "./private/signup.js";
 import signin from "./private/signin.js";
@@ -24,6 +23,15 @@ import twitter from "./service/twitter.js";
 // Init app
 const app = new Koa();
 
+// Init multer instance
+const upload = multer({
+	storage: multer.diskStorage({}),
+	limits: {
+		fileSize: config.maxFileSize || 262144000,
+		files: 1,
+	},
+});
+
 app.use(
 	cors({
 		origin: "*",
@@ -36,13 +44,6 @@ app.use(async (ctx, next) => {
 	await next();
 });
 
-app.use(
-	koaBody({
-		json: false,
-		multipart: true
-	})
-);
-
 app.use(
 	bodyParser({
 		// リクエストが multipart/form-data でない限りはJSONだと見なす
@@ -54,14 +55,9 @@ app.use(
 	}),
 );
 
-// Init multer instance
-const upload = multer({
-	storage: multer.diskStorage({}),
-	limits: {
-		fileSize: config.maxFileSize || 262144000,
-		files: 1,
-	},
-});
+app.use(
+	upload.any()
+);
 
 // Init router
 const router = new Router();
@@ -75,7 +71,6 @@ for (const endpoint of [...endpoints, ...compatibility]) {
 	if (endpoint.meta.requireFile) {
 		router.post(
 			`/${endpoint.name}`,
-			upload.single("file"),
 			handler.bind(null, endpoint),
 		);
 	} else {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 476fd2fbb..54880d9c9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3335,7 +3335,7 @@ packages:
   /axios/0.24.0:
     resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
     dependencies:
-      follow-redirects: 1.15.2
+      follow-redirects: 1.15.2_debug@4.3.4
     transitivePeerDependencies:
       - debug
     dev: false
@@ -3343,7 +3343,7 @@ packages:
   /axios/0.25.0_debug@4.3.4:
     resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
     dependencies:
-      follow-redirects: 1.15.2
+      follow-redirects: 1.15.2_debug@4.3.4
     transitivePeerDependencies:
       - debug
     dev: true
@@ -3351,7 +3351,7 @@ packages:
   /axios/1.2.2:
     resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==}
     dependencies:
-      follow-redirects: 1.15.2
+      follow-redirects: 1.15.2_debug@4.3.4
       form-data: 4.0.0
       proxy-from-env: 1.1.0
     transitivePeerDependencies:
@@ -3361,7 +3361,7 @@ packages:
   /axios/1.3.2:
     resolution: {integrity: sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==}
     dependencies:
-      follow-redirects: 1.15.2
+      follow-redirects: 1.15.2_debug@4.3.4
       form-data: 4.0.0
       proxy-from-env: 1.1.0
     transitivePeerDependencies:
@@ -6301,7 +6301,7 @@ packages:
       readable-stream: 2.3.7
     dev: true
 
-  /follow-redirects/1.15.2:
+  /follow-redirects/1.15.2_debug@4.3.4:
     resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
     engines: {node: '>=4.0'}
     peerDependencies:
@@ -6309,6 +6309,8 @@ packages:
     peerDependenciesMeta:
       debug:
         optional: true
+    dependencies:
+      debug: 4.3.4
 
   /for-each/0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}

From 2ca1717f2a4019e934af56f090e2940d8c37e56f Mon Sep 17 00:00:00 2001
From: Cleo <cutestnekoaqua@noreply.codeberg.org>
Date: Fri, 10 Feb 2023 22:46:08 +0000
Subject: [PATCH 14/35] fix(client): use proxied image for instance icon

---
 packages/client/assets/dummy.png                         | 3 +++
 packages/client/src/ui/_common_/statusbar-federation.vue | 7 ++++++-
 packages/client/src/widgets/federation.vue               | 7 ++++++-
 packages/client/src/widgets/instance-cloud.vue           | 7 ++++++-
 4 files changed, 21 insertions(+), 3 deletions(-)
 create mode 100644 packages/client/assets/dummy.png

diff --git a/packages/client/assets/dummy.png b/packages/client/assets/dummy.png
new file mode 100644
index 000000000..1703badd7
--- /dev/null
+++ b/packages/client/assets/dummy.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fe0f4c44a5e63ac228fafc6fa3aba1fbe88188dea73ca39d2f8c950f17f74d47
+size 6285
diff --git a/packages/client/src/ui/_common_/statusbar-federation.vue b/packages/client/src/ui/_common_/statusbar-federation.vue
index f29c6a9ff..2896cc09c 100644
--- a/packages/client/src/ui/_common_/statusbar-federation.vue
+++ b/packages/client/src/ui/_common_/statusbar-federation.vue
@@ -4,7 +4,7 @@
 		<transition name="change" mode="default">
 			<MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
 				<span v-for="instance in instances" :key="instance.id" class="item" :class="{ colored }" :style="{ background: colored ? instance.themeColor : null }">
-					<img v-if="instance.iconUrl" class="icon" :src="instance.iconUrl" alt=""/>
+					<img class="icon" :src="getInstanceIcon(instance)" alt=""/>
 					<MkA :to="`/instance-info/${instance.host}`" class="host _monospace">
 						{{ instance.host }}
 					</MkA>
@@ -27,6 +27,7 @@ import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
 import { getNoteSummary } from '@/scripts/get-note-summary';
 import { notePage } from '@/filters/note';
+import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
 
 const props = defineProps<{
 	display?: 'marquee' | 'oneByOne';
@@ -56,6 +57,10 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), {
 	immediate: true,
 	afterMounted: true,
 });
+
+function getInstanceIcon(instance): string {
+	return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue
index cbbd3eb94..706712668 100644
--- a/packages/client/src/widgets/federation.vue
+++ b/packages/client/src/widgets/federation.vue
@@ -6,7 +6,7 @@
 		<MkLoading v-if="fetching"/>
 		<transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances">
 			<div v-for="(instance, i) in instances" :key="instance.id" class="instance">
-				<img v-if="instance.iconUrl" :src="instance.iconUrl" alt=""/>
+				<img :src="getInstanceIcon(instance)" alt=""/>
 				<div class="body">
 					<a class="a" :href="'https://' + instance.host" target="_blank" :title="instance.host">{{ instance.host }}</a>
 					<p>{{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</p>
@@ -27,6 +27,7 @@ import MkMiniChart from '@/components/MkMiniChart.vue';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
 import { i18n } from '@/i18n';
+import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
 
 const name = 'federation';
 
@@ -71,6 +72,10 @@ useInterval(fetch, 1000 * 60, {
 	afterMounted: true,
 });
 
+function getInstanceIcon(instance): string {
+	return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
+}
+
 defineExpose<WidgetComponentExpose>({
 	name,
 	configure,
diff --git a/packages/client/src/widgets/instance-cloud.vue b/packages/client/src/widgets/instance-cloud.vue
index 1358a63b6..3cb579c28 100644
--- a/packages/client/src/widgets/instance-cloud.vue
+++ b/packages/client/src/widgets/instance-cloud.vue
@@ -4,7 +4,7 @@
 		<MkTagCloud v-if="activeInstances">
 			<li v-for="instance in activeInstances" :key="instance.id">
 				<a @click.prevent="onInstanceClick(instance)">
-					<img style="width: 32px;" :src="instance.iconUrl">
+					<img style="width: 32px;" :src="getInstanceIcon(instance)">
 				</a>
 			</li>
 		</MkTagCloud>
@@ -21,6 +21,7 @@ import MkContainer from '@/components/MkContainer.vue';
 import MkTagCloud from '@/components/MkTagCloud.vue';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
+import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
 
 const name = 'instanceCloud';
 
@@ -65,6 +66,10 @@ useInterval(() => {
 	afterMounted: true,
 });
 
+function getInstanceIcon(instance): string {
+	return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
+}
+
 defineExpose<WidgetComponentExpose>({
 	name,
 	configure,

From 475b130eefb36ced97e547ede8ad10a9980d55de Mon Sep 17 00:00:00 2001
From: Cleo <cutestnekoaqua@noreply.codeberg.org>
Date: Fri, 10 Feb 2023 22:46:56 +0000
Subject: [PATCH 15/35] fix(client): use proxied image for instance icon

---
 packages/client/src/pages/instance-info.vue | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue
index 27f08bdb5..59547a3cb 100644
--- a/packages/client/src/pages/instance-info.vue
+++ b/packages/client/src/pages/instance-info.vue
@@ -13,7 +13,7 @@
 			<swiper-slide>
 				<div class="_formRoot">
 					<div class="fnfelxur">
-						<img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/>
+						<img :src="faviconUrl" alt="" class="icon"/>
 						<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
 					</div>
 					<MkKeyValue :copy="host" oneline style="margin: 1em 0;">
@@ -156,6 +156,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import 'swiper/scss';
 import 'swiper/scss/virtual';
+import { getProxiedImageUrlNullable } from '@/scripts/media-proxy';
 
 const props = defineProps<{
 	host: string;
@@ -171,6 +172,7 @@ let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null);
 let instance = $ref<misskey.entities.Instance | null>(null);
 let suspended = $ref(false);
 let isBlocked = $ref(false);
+let faviconUrl = $ref(null);
 
 const usersPagination = {
 	endpoint: iAmModerator ? 'admin/show-users' : 'users' as const,
@@ -189,6 +191,7 @@ async function fetch() {
 	});
 	suspended = instance.isSuspended;
 	isBlocked = instance.isBlocked;
+	faviconUrl = getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.iconUrl, 'preview');
 }
 
 async function toggleBlock(ev) {

From 1d3e01ee8eafb676ab718c35c60049bc1f6228cb Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Fri, 10 Feb 2023 23:50:22 +0100
Subject: [PATCH 16/35] use formidable instead

---
 packages/backend/package.json            |  7 ++++---
 packages/backend/src/server/api/index.ts | 24 ++++++++++++------------
 pnpm-lock.yaml                           | 13 +++++++++++++
 3 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 80484f95c..a629a96cc 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -25,6 +25,7 @@
 		"@bull-board/api": "^4.6.4",
 		"@bull-board/koa": "^4.6.4",
 		"@bull-board/ui": "^4.6.4",
+		"@cutls/megalodon": "5.1.15",
 		"@discordapp/twemoji": "14.0.2",
 		"@elastic/elasticsearch": "7.17.0",
 		"@koa/cors": "3.4.3",
@@ -37,12 +38,11 @@
 		"@tensorflow/tfjs": "^4.2.0",
 		"ajv": "8.11.2",
 		"archiver": "5.3.1",
-		"koa-body": "^6.0.1",
 		"autobind-decorator": "2.4.0",
 		"autolinker": "4.0.0",
-		"axios": "^1.3.2",
 		"autwh": "0.1.0",
 		"aws-sdk": "2.1277.0",
+		"axios": "^1.3.2",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
 		"bull": "4.10.2",
@@ -71,6 +71,7 @@
 		"jsonld": "6.0.0",
 		"jsrsasign": "10.6.1",
 		"koa": "2.13.4",
+		"koa-body": "^6.0.1",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
 		"koa-json-body": "5.3.0",
@@ -79,7 +80,7 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
-		"@cutls/megalodon": "5.1.15",
+		"koa2-formidable": "^1.0.3",
 		"mfm-js": "0.23.2",
 		"mime-types": "2.1.35",
 		"multer": "1.4.4-lts.1",
diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index 593c7a284..ddcb9a884 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -6,6 +6,7 @@ import Koa from "koa";
 import Router from "@koa/router";
 import multer from "@koa/multer";
 import bodyParser from "koa-bodyparser";
+const formidable = require('koa2-formidable')
 import cors from "@koa/cors";
 import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
 import { Instances, AccessTokens, Users } from "@/models/index.js";
@@ -23,15 +24,6 @@ import twitter from "./service/twitter.js";
 // Init app
 const app = new Koa();
 
-// Init multer instance
-const upload = multer({
-	storage: multer.diskStorage({}),
-	limits: {
-		fileSize: config.maxFileSize || 262144000,
-		files: 1,
-	},
-});
-
 app.use(
 	cors({
 		origin: "*",
@@ -44,6 +36,8 @@ app.use(async (ctx, next) => {
 	await next();
 });
 
+app.use(formidable());
+
 app.use(
 	bodyParser({
 		// リクエストが multipart/form-data でない限りはJSONだと見なす
@@ -55,9 +49,14 @@ app.use(
 	}),
 );
 
-app.use(
-	upload.any()
-);
+// Init multer instance
+const upload = multer({
+	storage: multer.diskStorage({}),
+	limits: {
+		fileSize: config.maxFileSize || 262144000,
+		files: 1,
+	},
+});
 
 // Init router
 const router = new Router();
@@ -71,6 +70,7 @@ for (const endpoint of [...endpoints, ...compatibility]) {
 	if (endpoint.meta.requireFile) {
 		router.post(
 			`/${endpoint.name}`,
+			upload.single("file"),
 			handler.bind(null, endpoint),
 		);
 	} else {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 54880d9c9..5c6765f2e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -164,6 +164,7 @@ importers:
       koa-send: 5.0.1
       koa-slow: 2.1.0
       koa-views: 7.0.2
+      koa2-formidable: ^1.0.3
       mfm-js: 0.23.2
       mime-types: 2.1.35
       mocha: 10.2.0
@@ -278,6 +279,7 @@ importers:
       koa-send: 5.0.1
       koa-slow: 2.1.0
       koa-views: 7.0.2_6tybghmia4wsnt33xeid7y4rby
+      koa2-formidable: 1.0.3
       mfm-js: 0.23.2
       mime-types: 2.1.35
       multer: 1.4.4-lts.1
@@ -6370,6 +6372,11 @@ packages:
     dependencies:
       fetch-blob: 3.2.0
 
+  /formidable/1.2.6:
+    resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==}
+    deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau'
+    dev: false
+
   /formidable/2.1.1:
     resolution: {integrity: sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==}
     dependencies:
@@ -8381,6 +8388,12 @@ packages:
       - supports-color
     dev: false
 
+  /koa2-formidable/1.0.3:
+    resolution: {integrity: sha512-m80sje/vb4mvhZn7HbZPwYHnsGjKL+oJ4OasNbH+jdvjIddVMPmugmmBwPnMYDGaMnL0tT9mJrfKRcp8MMdNyQ==}
+    dependencies:
+      formidable: 1.2.6
+    dev: false
+
   /ky-universal/0.10.1_ky@0.30.0:
     resolution: {integrity: sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g==}
     engines: {node: '>=14'}

From fd60486bca3201c412ea29b72c7e88cd84d56657 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:02:20 +0100
Subject: [PATCH 17/35] meow?

---
 packages/backend/src/server/api/index.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index ddcb9a884..bfbc6a3fe 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -6,7 +6,8 @@ import Koa from "koa";
 import Router from "@koa/router";
 import multer from "@koa/multer";
 import bodyParser from "koa-bodyparser";
-const formidable = require('koa2-formidable')
+// @ts-ignore
+import formidable from 'koa2-formidable'
 import cors from "@koa/cors";
 import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
 import { Instances, AccessTokens, Users } from "@/models/index.js";

From ed6736adf09fa1cb623c4ca2f390b99de8029c2b Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:08:26 +0100
Subject: [PATCH 18/35] remove body parser settings for now

---
 packages/backend/src/server/api/index.ts | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index bfbc6a3fe..fc6e0158d 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -40,14 +40,7 @@ app.use(async (ctx, next) => {
 app.use(formidable());
 
 app.use(
-	bodyParser({
-		// リクエストが multipart/form-data でない限りはJSONだと見なす
-		detectJSON: (ctx) =>
-			!(
-				ctx.is("multipart/form-data") ||
-				ctx.is("application/x-www-form-urlencoded")
-			),
-	}),
+	bodyParser(),
 );
 
 // Init multer instance

From 64c4aa4599276f3d42de692d05a04fbddedfa21a Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 10 Feb 2023 15:17:56 -0800
Subject: [PATCH 19/35] chore: :art: new dummy images

---
 packages/client/assets/dummy.png          | 4 ++--
 packages/client/assets/dummy_original.png | 3 +++
 2 files changed, 5 insertions(+), 2 deletions(-)
 create mode 100644 packages/client/assets/dummy_original.png

diff --git a/packages/client/assets/dummy.png b/packages/client/assets/dummy.png
index 1703badd7..c1a3501e7 100644
--- a/packages/client/assets/dummy.png
+++ b/packages/client/assets/dummy.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:fe0f4c44a5e63ac228fafc6fa3aba1fbe88188dea73ca39d2f8c950f17f74d47
-size 6285
+oid sha256:3e770a13738887f9fbb0b62f9881c7035a36c36832676ae10de531cd5c4c2cc8
+size 14059
diff --git a/packages/client/assets/dummy_original.png b/packages/client/assets/dummy_original.png
new file mode 100644
index 000000000..f9c1b2f05
--- /dev/null
+++ b/packages/client/assets/dummy_original.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b706b7bc2b77a01be166d8295b962263bce583bc4c24a8905ff52b090dddd766
+size 69675

From 5664405c3a3c09ca35ccfdf534c0feeddc755d86 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 10 Feb 2023 15:19:02 -0800
Subject: [PATCH 20/35] up ver

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c2f842a84..7cd7efc28 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "calckey",
-	"version": "13.1.3-rc",
+	"version": "13.2.0-dev",
 	"codename": "aqua",
 	"repository": {
 		"type": "git",

From fccf6f4de8d59be83cd32d458edd46214f23cb3e Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:15:28 +0100
Subject: [PATCH 21/35] Revert "remove body parser settings for now"

This reverts commit 41ce22aa1c718dde7585609da2addd75985394a0.
---
 packages/backend/src/server/api/index.ts | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index fc6e0158d..bfbc6a3fe 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -40,7 +40,14 @@ app.use(async (ctx, next) => {
 app.use(formidable());
 
 app.use(
-	bodyParser(),
+	bodyParser({
+		// リクエストが multipart/form-data でない限りはJSONだと見なす
+		detectJSON: (ctx) =>
+			!(
+				ctx.is("multipart/form-data") ||
+				ctx.is("application/x-www-form-urlencoded")
+			),
+	}),
 );
 
 // Init multer instance

From 73ad1f1a316a3011a68a09e4a69ace5a2a48e709 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:15:36 +0100
Subject: [PATCH 22/35] Revert "meow?"

This reverts commit 6e79148152cfa3cd3757b1545af6db804f4aa166.
---
 packages/backend/src/server/api/index.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index bfbc6a3fe..ddcb9a884 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -6,8 +6,7 @@ import Koa from "koa";
 import Router from "@koa/router";
 import multer from "@koa/multer";
 import bodyParser from "koa-bodyparser";
-// @ts-ignore
-import formidable from 'koa2-formidable'
+const formidable = require('koa2-formidable')
 import cors from "@koa/cors";
 import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
 import { Instances, AccessTokens, Users } from "@/models/index.js";

From 1b3308bd4b6c058847f44c42a90511ca56fc0d3a Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:15:40 +0100
Subject: [PATCH 23/35] Revert "use formidable instead"

This reverts commit b8406ecaa40c08576d2644b797e05363533b0cfb.
---
 packages/backend/package.json            |  7 +++----
 packages/backend/src/server/api/index.ts | 24 ++++++++++++------------
 pnpm-lock.yaml                           | 13 -------------
 3 files changed, 15 insertions(+), 29 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index a629a96cc..80484f95c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -25,7 +25,6 @@
 		"@bull-board/api": "^4.6.4",
 		"@bull-board/koa": "^4.6.4",
 		"@bull-board/ui": "^4.6.4",
-		"@cutls/megalodon": "5.1.15",
 		"@discordapp/twemoji": "14.0.2",
 		"@elastic/elasticsearch": "7.17.0",
 		"@koa/cors": "3.4.3",
@@ -38,11 +37,12 @@
 		"@tensorflow/tfjs": "^4.2.0",
 		"ajv": "8.11.2",
 		"archiver": "5.3.1",
+		"koa-body": "^6.0.1",
 		"autobind-decorator": "2.4.0",
 		"autolinker": "4.0.0",
+		"axios": "^1.3.2",
 		"autwh": "0.1.0",
 		"aws-sdk": "2.1277.0",
-		"axios": "^1.3.2",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
 		"bull": "4.10.2",
@@ -71,7 +71,6 @@
 		"jsonld": "6.0.0",
 		"jsrsasign": "10.6.1",
 		"koa": "2.13.4",
-		"koa-body": "^6.0.1",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
 		"koa-json-body": "5.3.0",
@@ -80,7 +79,7 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
-		"koa2-formidable": "^1.0.3",
+		"@cutls/megalodon": "5.1.15",
 		"mfm-js": "0.23.2",
 		"mime-types": "2.1.35",
 		"multer": "1.4.4-lts.1",
diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index ddcb9a884..593c7a284 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -6,7 +6,6 @@ import Koa from "koa";
 import Router from "@koa/router";
 import multer from "@koa/multer";
 import bodyParser from "koa-bodyparser";
-const formidable = require('koa2-formidable')
 import cors from "@koa/cors";
 import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
 import { Instances, AccessTokens, Users } from "@/models/index.js";
@@ -24,6 +23,15 @@ import twitter from "./service/twitter.js";
 // Init app
 const app = new Koa();
 
+// Init multer instance
+const upload = multer({
+	storage: multer.diskStorage({}),
+	limits: {
+		fileSize: config.maxFileSize || 262144000,
+		files: 1,
+	},
+});
+
 app.use(
 	cors({
 		origin: "*",
@@ -36,8 +44,6 @@ app.use(async (ctx, next) => {
 	await next();
 });
 
-app.use(formidable());
-
 app.use(
 	bodyParser({
 		// リクエストが multipart/form-data でない限りはJSONだと見なす
@@ -49,14 +55,9 @@ app.use(
 	}),
 );
 
-// Init multer instance
-const upload = multer({
-	storage: multer.diskStorage({}),
-	limits: {
-		fileSize: config.maxFileSize || 262144000,
-		files: 1,
-	},
-});
+app.use(
+	upload.any()
+);
 
 // Init router
 const router = new Router();
@@ -70,7 +71,6 @@ for (const endpoint of [...endpoints, ...compatibility]) {
 	if (endpoint.meta.requireFile) {
 		router.post(
 			`/${endpoint.name}`,
-			upload.single("file"),
 			handler.bind(null, endpoint),
 		);
 	} else {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5c6765f2e..54880d9c9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -164,7 +164,6 @@ importers:
       koa-send: 5.0.1
       koa-slow: 2.1.0
       koa-views: 7.0.2
-      koa2-formidable: ^1.0.3
       mfm-js: 0.23.2
       mime-types: 2.1.35
       mocha: 10.2.0
@@ -279,7 +278,6 @@ importers:
       koa-send: 5.0.1
       koa-slow: 2.1.0
       koa-views: 7.0.2_6tybghmia4wsnt33xeid7y4rby
-      koa2-formidable: 1.0.3
       mfm-js: 0.23.2
       mime-types: 2.1.35
       multer: 1.4.4-lts.1
@@ -6372,11 +6370,6 @@ packages:
     dependencies:
       fetch-blob: 3.2.0
 
-  /formidable/1.2.6:
-    resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==}
-    deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau'
-    dev: false
-
   /formidable/2.1.1:
     resolution: {integrity: sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==}
     dependencies:
@@ -8388,12 +8381,6 @@ packages:
       - supports-color
     dev: false
 
-  /koa2-formidable/1.0.3:
-    resolution: {integrity: sha512-m80sje/vb4mvhZn7HbZPwYHnsGjKL+oJ4OasNbH+jdvjIddVMPmugmmBwPnMYDGaMnL0tT9mJrfKRcp8MMdNyQ==}
-    dependencies:
-      formidable: 1.2.6
-    dev: false
-
   /ky-universal/0.10.1_ky@0.30.0:
     resolution: {integrity: sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g==}
     engines: {node: '>=14'}

From 0f928bfb23f8ce0d6162a6856f474853b97ff57c Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:19:43 +0100
Subject: [PATCH 24/35] revert it for now

---
 packages/backend/src/server/api/index.ts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index 593c7a284..1096d995b 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -55,10 +55,6 @@ app.use(
 	}),
 );
 
-app.use(
-	upload.any()
-);
-
 // Init router
 const router = new Router();
 
@@ -71,6 +67,7 @@ for (const endpoint of [...endpoints, ...compatibility]) {
 	if (endpoint.meta.requireFile) {
 		router.post(
 			`/${endpoint.name}`,
+			upload.single("file"),
 			handler.bind(null, endpoint),
 		);
 	} else {

From e58b839339c467cea43c51419fb2869939117088 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:33:01 +0100
Subject: [PATCH 25/35] split routers

---
 packages/backend/src/server/api/index.ts | 31 ++++++++++++++----------
 packages/backend/src/server/index.ts     |  9 +++++--
 2 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index 1096d995b..e2a0257e6 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -19,19 +19,11 @@ import signupPending from "./private/signup-pending.js";
 import discord from "./service/discord.js";
 import github from "./service/github.js";
 import twitter from "./service/twitter.js";
+import koaBody from "koa-body";
 
 // Init app
 const app = new Koa();
 
-// Init multer instance
-const upload = multer({
-	storage: multer.diskStorage({}),
-	limits: {
-		fileSize: config.maxFileSize || 262144000,
-		files: 1,
-	},
-});
-
 app.use(
 	cors({
 		origin: "*",
@@ -44,7 +36,20 @@ app.use(async (ctx, next) => {
 	await next();
 });
 
-app.use(
+// Init router
+const router = new Router();
+const mastoRouter = new Router();
+
+// Init multer instance
+const upload = multer({
+	storage: multer.diskStorage({}),
+	limits: {
+		fileSize: config.maxFileSize || 262144000,
+		files: 1,
+	},
+});
+
+router.use(
 	bodyParser({
 		// リクエストが multipart/form-data でない限りはJSONだと見なす
 		detectJSON: (ctx) =>
@@ -55,10 +60,9 @@ app.use(
 	}),
 );
 
-// Init router
-const router = new Router();
+mastoRouter.use(koaBody());
 
-apiMastodonCompatible(router);
+apiMastodonCompatible(mastoRouter);
 
 /**
  * Register endpoint handlers
@@ -150,5 +154,6 @@ router.all("(.*)", async (ctx) => {
 
 // Register router
 app.use(router.routes());
+app.use(mastoRouter.routes());
 
 export default app;
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 6609627fe..a3cdad438 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -29,6 +29,7 @@ import fileServer from "./file/index.js";
 import proxyServer from "./proxy/index.js";
 import webServer from "./web/index.js";
 import { initializeStreamingServer } from "./api/streaming.js";
+import koaBody from "koa-body";
 
 export const serverLogger = new Logger("server", "gray", false);
 
@@ -69,6 +70,9 @@ app.use(mount("/proxy", proxyServer));
 
 // Init router
 const router = new Router();
+const mastoRouter = new Router();
+
+mastoRouter.use(koaBody());
 
 // Routing
 router.use(activityPub.routes());
@@ -134,13 +138,13 @@ router.get("/verify-email/:code", async (ctx) => {
 	}
 });
 
-router.get("/oauth/authorize", async (ctx) => {
+mastoRouter.get("/oauth/authorize", async (ctx) => {
 	const client_id = ctx.request.query.client_id;
 	console.log(ctx.request.req);
 	ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
 });
 
-router.post("/oauth/token", async (ctx) => {
+mastoRouter.post("/oauth/token", async (ctx) => {
 	const body: any = ctx.request.body;
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;
@@ -167,6 +171,7 @@ router.post("/oauth/token", async (ctx) => {
 
 // Register router
 app.use(router.routes());
+app.use(mastoRouter.routes());
 
 app.use(mount(webServer));
 

From 657f40e7715cf8992ada81f43053c7e07f1e19a3 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:35:30 +0100
Subject: [PATCH 26/35] ree

---
 packages/backend/src/server/api/index.ts | 2 +-
 packages/backend/src/server/index.ts     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index e2a0257e6..f1889a8b6 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -60,7 +60,7 @@ router.use(
 	}),
 );
 
-mastoRouter.use(koaBody());
+mastoRouter.use(koaBody({ multipart: true }));
 
 apiMastodonCompatible(mastoRouter);
 
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index a3cdad438..521f3ddf6 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -72,7 +72,7 @@ app.use(mount("/proxy", proxyServer));
 const router = new Router();
 const mastoRouter = new Router();
 
-mastoRouter.use(koaBody());
+mastoRouter.use(koaBody({ multipart: true }));
 
 // Routing
 router.use(activityPub.routes());

From 955994c93d949130ee02c573d3d45fb9bcd96183 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:40:04 +0100
Subject: [PATCH 27/35] meow

---
 packages/backend/src/server/api/index.ts | 2 +-
 packages/backend/src/server/index.ts     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index f1889a8b6..a08d4e454 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -19,7 +19,7 @@ import signupPending from "./private/signup-pending.js";
 import discord from "./service/discord.js";
 import github from "./service/github.js";
 import twitter from "./service/twitter.js";
-import koaBody from "koa-body";
+import {koaBody} from "koa-body";
 
 // Init app
 const app = new Koa();
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 521f3ddf6..139e1bd9c 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -29,7 +29,7 @@ import fileServer from "./file/index.js";
 import proxyServer from "./proxy/index.js";
 import webServer from "./web/index.js";
 import { initializeStreamingServer } from "./api/streaming.js";
-import koaBody from "koa-body";
+import {koaBody} from "koa-body";
 
 export const serverLogger = new Logger("server", "gray", false);
 

From 13238973b61326f2dd8ff305cd80de0095289d01 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Fri, 10 Feb 2023 15:41:19 -0800
Subject: [PATCH 28/35] chore: Rome Formatting

---
 .../backend/src/models/repositories/note.ts   |   5 +-
 packages/backend/src/models/schema/note.ts    |   5 +-
 .../src/remote/activitypub/models/note.ts     |   8 +-
 .../src/remote/activitypub/models/person.ts   |   4 +-
 packages/backend/src/server/api/endpoints.ts  |  10 +-
 .../api/endpoints/i/registry/get-unsecure.ts  |   2 +-
 packages/backend/src/server/api/index.ts      |   4 +-
 .../mastodon/ApiMastodonCompatibleService.ts  |  64 +-
 .../server/api/mastodon/endpoints/account.ts  | 693 +++++++-------
 .../src/server/api/mastodon/endpoints/auth.ts | 110 +--
 .../server/api/mastodon/endpoints/filter.ts   |  32 +-
 .../api/mastodon/endpoints/notifications.ts   |  57 +-
 .../server/api/mastodon/endpoints/search.ts   |  14 +-
 .../server/api/mastodon/endpoints/status.ts   | 857 ++++++++++--------
 .../server/api/mastodon/endpoints/timeline.ts | 512 ++++++-----
 .../backend/src/server/api/stream/index.ts    |   2 +-
 packages/backend/src/server/api/streaming.ts  |  14 +-
 packages/backend/src/server/index.ts          |  24 +-
 .../backend/src/server/web/url-preview.ts     |  17 +-
 packages/client/src/components/mfm.ts         |   7 +-
 packages/client/src/navbar.ts                 | 114 ++-
 packages/client/src/scripts/aiscript/api.ts   |   2 +-
 packages/client/src/scripts/get-user-menu.ts  |  15 +-
 23 files changed, 1407 insertions(+), 1165 deletions(-)

diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index 37c5031c0..882cfdd31 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -197,7 +197,10 @@ export const NoteRepository = db.getRepository(Note).extend({
 			.map((x) => decodeReaction(x).reaction)
 			.map((x) => x.replace(/:/g, ""));
 
-		const noteEmoji = await populateEmojis(note.emojis.concat(reactionEmojiNames), host);
+		const noteEmoji = await populateEmojis(
+			note.emojis.concat(reactionEmojiNames),
+			host,
+		);
 		const reactionEmoji = await populateEmojis(reactionEmojiNames, host);
 		const packed: Packed<"Note"> = await awaitAll({
 			id: note.id,
diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts
index 6bc8443f0..e17f054e8 100644
--- a/packages/backend/src/models/schema/note.ts
+++ b/packages/backend/src/models/schema/note.ts
@@ -161,8 +161,9 @@ export const packedNoteSchema = {
 			nullable: false,
 		},
 		emojis: {
-			type: 'object',
-			optional: true, nullable: true,
+			type: "object",
+			optional: true,
+			nullable: true,
 		},
 		reactions: {
 			type: "object",
diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts
index 34d8d0ba1..28ce46e30 100644
--- a/packages/backend/src/remote/activitypub/models/note.ts
+++ b/packages/backend/src/remote/activitypub/models/note.ts
@@ -111,13 +111,13 @@ export async function createNote(
 
 	const note: IPost = object;
 
-	if (note.id && !note.id.startsWith('https://')) {
+	if (note.id && !note.id.startsWith("https://")) {
 		throw new Error(`unexpected shcema of note.id: ${note.id}`);
 	}
 
 	const url = getOneApHrefNullable(note.url);
 
-	if (url && !url.startsWith('https://')) {
+	if (url && !url.startsWith("https://")) {
 		throw new Error(`unexpected shcema of note url: ${url}`);
 	}
 
@@ -133,7 +133,9 @@ export async function createNote(
 
 	// Skip if author is suspended.
 	if (actor.isSuspended) {
-		logger.debug(`User ${actor.usernameLower}@${actor.host} suspended; discarding.`)
+		logger.debug(
+			`User ${actor.usernameLower}@${actor.host} suspended; discarding.`,
+		);
 		return null;
 	}
 
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 88bbca5c4..372ef99e1 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -197,7 +197,7 @@ export async function createPerson(
 
 	const url = getOneApHrefNullable(person.url);
 
-	if (url && !url.startsWith('https://')) {
+	if (url && !url.startsWith("https://")) {
 		throw new Error(`unexpected shcema of person url: ${url}`);
 	}
 
@@ -395,7 +395,7 @@ export async function updatePerson(
 
 	const url = getOneApHrefNullable(person.url);
 
-	if (url && !url.startsWith('https://')) {
+	if (url && !url.startsWith("https://")) {
 		throw new Error(`unexpected shcema of person url: ${url}`);
 	}
 
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 353f137a7..6ee1a977e 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -198,7 +198,7 @@ import * as ep___i_readAnnouncement from "./endpoints/i/read-announcement.js";
 import * as ep___i_regenerateToken from "./endpoints/i/regenerate-token.js";
 import * as ep___i_registry_getAll from "./endpoints/i/registry/get-all.js";
 import * as ep___i_registry_getDetail from "./endpoints/i/registry/get-detail.js";
-import * as ep___i_registry_getUnsecure from './endpoints/i/registry/get-unsecure.js';
+import * as ep___i_registry_getUnsecure from "./endpoints/i/registry/get-unsecure.js";
 import * as ep___i_registry_get from "./endpoints/i/registry/get.js";
 import * as ep___i_registry_keysWithType from "./endpoints/i/registry/keys-with-type.js";
 import * as ep___i_registry_keys from "./endpoints/i/registry/keys.js";
@@ -767,10 +767,10 @@ export interface IEndpointMeta {
 }
 
 export interface IEndpoint {
-	name: string,
-	exec: any, // TODO: may be obosolete @ThatOneCalculator
-	meta: IEndpointMeta,
-	params: Schema,
+	name: string;
+	exec: any; // TODO: may be obosolete @ThatOneCalculator
+	meta: IEndpointMeta;
+	params: Schema;
 }
 
 const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts b/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts
index a8169aa95..f98c6c929 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts
@@ -1,6 +1,6 @@
 import { ApiError } from "../../../error.js";
 import define from "../../../define.js";
-import {RegistryItems} from "@/models/index.js";
+import { RegistryItems } from "@/models/index.js";
 
 export const meta = {
 	requireCredential: true,
diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index a08d4e454..aee1a43f4 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -7,7 +7,7 @@ import Router from "@koa/router";
 import multer from "@koa/multer";
 import bodyParser from "koa-bodyparser";
 import cors from "@koa/cors";
-import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
+import { apiMastodonCompatible } from "./mastodon/ApiMastodonCompatibleService.js";
 import { Instances, AccessTokens, Users } from "@/models/index.js";
 import config from "@/config/index.js";
 import endpoints from "./endpoints.js";
@@ -19,7 +19,7 @@ import signupPending from "./private/signup-pending.js";
 import discord from "./service/discord.js";
 import github from "./service/github.js";
 import twitter from "./service/twitter.js";
-import {koaBody} from "koa-body";
+import { koaBody } from "koa-body";
 
 // Init app
 const app = new Koa();
diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
index 57a86c96d..ddd2a23f0 100644
--- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
+++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts
@@ -1,32 +1,39 @@
 import Router from "@koa/router";
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
-import { apiAuthMastodon } from './endpoints/auth.js';
-import { apiAccountMastodon } from './endpoints/account.js';
-import { apiStatusMastodon } from './endpoints/status.js';
-import { apiFilterMastodon } from './endpoints/filter.js';
-import { apiTimelineMastodon } from './endpoints/timeline.js';
-import { apiNotificationsMastodon } from './endpoints/notifications.js';
-import { apiSearchMastodon } from './endpoints/search.js';
-import { getInstance } from './endpoints/meta.js';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
+import { apiAuthMastodon } from "./endpoints/auth.js";
+import { apiAccountMastodon } from "./endpoints/account.js";
+import { apiStatusMastodon } from "./endpoints/status.js";
+import { apiFilterMastodon } from "./endpoints/filter.js";
+import { apiTimelineMastodon } from "./endpoints/timeline.js";
+import { apiNotificationsMastodon } from "./endpoints/notifications.js";
+import { apiSearchMastodon } from "./endpoints/search.js";
+import { getInstance } from "./endpoints/meta.js";
 
-export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
-	const accessTokenArr = authorization?.split(' ') ?? [null];
+export function getClient(
+	BASE_URL: string,
+	authorization: string | undefined,
+): MegalodonInterface {
+	const accessTokenArr = authorization?.split(" ") ?? [null];
 	const accessToken = accessTokenArr[accessTokenArr.length - 1];
-	const generator = (megalodon as any).default
-	const client = generator('misskey', BASE_URL, accessToken) as MegalodonInterface;
-	return client
+	const generator = (megalodon as any).default;
+	const client = generator(
+		"misskey",
+		BASE_URL,
+		accessToken,
+	) as MegalodonInterface;
+	return client;
 }
 
 export function apiMastodonCompatible(router: Router): void {
-	apiAuthMastodon(router)
-	apiAccountMastodon(router)
-	apiStatusMastodon(router)
-	apiFilterMastodon(router)
-	apiTimelineMastodon(router)
-	apiNotificationsMastodon(router)
-	apiSearchMastodon(router)
+	apiAuthMastodon(router);
+	apiAccountMastodon(router);
+	apiStatusMastodon(router);
+	apiFilterMastodon(router);
+	apiTimelineMastodon(router);
+	apiNotificationsMastodon(router);
+	apiSearchMastodon(router);
 
-	router.get('/v1/custom_emojis', async (ctx) => {
+	router.get("/v1/custom_emojis", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -34,25 +41,24 @@ export function apiMastodonCompatible(router: Router): void {
 			const data = await client.getInstanceCustomEmojis();
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.get('/v1/instance', async (ctx) => {
+	router.get("/v1/instance", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
-		const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt 
-																											// displayed without being logged in
+		const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
+		// displayed without being logged in
 		try {
 			const data = await client.getInstance();
 			ctx.body = getInstance(data.data);
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
-
-} 
+}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts
index 4fda37a48..0162951d6 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/account.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts
@@ -1,323 +1,376 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import { getClient } from '../ApiMastodonCompatibleService.js';
-import { toLimitToInt } from './timeline.js';
+import { koaBody } from "koa-body";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import { toLimitToInt } from "./timeline.js";
 
 export function apiAccountMastodon(router: Router): void {
-
-    router.get('/v1/accounts/verify_credentials', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.verifyAccountCredentials();
-            const acct = data.data;
-            acct.url = `${BASE_URL}/@${acct.url}`
-            acct.note = ''
-            acct.avatar_static = acct.avatar
-            acct.header = acct.header || ''
-            acct.header_static = acct.header || ''
-            acct.source = {
-                note: acct.note,
-                fields: acct.fields,
-                privacy: 'public',
-                sensitive: false,
-                language: ''
-            }
-            ctx.body = acct
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.patch('/v1/accounts/update_credentials', async (ctx) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.updateCredentials((ctx.request as any).body as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/accounts/:id', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getAccount(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/accounts/:id/statuses', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getAccountStatuses(ctx.params.id, toLimitToInt(ctx.query as any));
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/accounts/:id/followers', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getAccountFollowers(ctx.params.id, ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/accounts/:id/following', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getAccountFollowing(ctx.params.id, ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/accounts/:id/lists', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getAccountLists(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/follow', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.followAccount(ctx.params.id);
-            const acct = data.data;
-            acct.following = true;
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/unfollow', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.unfollowAccount(ctx.params.id);
-            const acct = data.data;
-            acct.following = false;
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/block', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.blockAccount(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/unblock', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.unblockAccount(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', async (ctx) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.muteAccount(ctx.params.id, (ctx.request as any).body as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/accounts/:id/unmute', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.unmuteAccount(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/accounts/relationships', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const idsRaw = (ctx.query as any)['id[]']
-            const ids = typeof idsRaw === 'string' ? [idsRaw] : idsRaw
-            const data = await client.getRelationships(ids) as any;
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/bookmarks', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getBookmarks(ctx.query as any) as any;
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/favourites', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getFavourites(ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/mutes', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getMutes(ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/blocks', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getBlocks(ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/follow_ctxs', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getFollowRequests((ctx.query as any || { limit: 20 }).limit);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/follow_ctxs/:id/authorize', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.acceptFollowRequest(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/follow_ctxs/:id/reject', async (ctx, next) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.rejectFollowRequest(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status =(401);
-            ctx.body = e.response.data;
-        }
-    });
-
-} 
+	router.get("/v1/accounts/verify_credentials", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.verifyAccountCredentials();
+			const acct = data.data;
+			acct.url = `${BASE_URL}/@${acct.url}`;
+			acct.note = "";
+			acct.avatar_static = acct.avatar;
+			acct.header = acct.header || "";
+			acct.header_static = acct.header || "";
+			acct.source = {
+				note: acct.note,
+				fields: acct.fields,
+				privacy: "public",
+				sensitive: false,
+				language: "",
+			};
+			ctx.body = acct;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.patch("/v1/accounts/update_credentials", async (ctx) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.updateCredentials(
+				(ctx.request as any).body as any,
+			);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get<{ Params: { id: string } }>(
+		"/v1/accounts/:id",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getAccount(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/accounts/:id/statuses",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getAccountStatuses(
+					ctx.params.id,
+					toLimitToInt(ctx.query as any),
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/accounts/:id/followers",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getAccountFollowers(
+					ctx.params.id,
+					ctx.query as any,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/accounts/:id/following",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getAccountFollowing(
+					ctx.params.id,
+					ctx.query as any,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/accounts/:id/lists",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getAccountLists(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/accounts/:id/follow",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.followAccount(ctx.params.id);
+				const acct = data.data;
+				acct.following = true;
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/accounts/:id/unfollow",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.unfollowAccount(ctx.params.id);
+				const acct = data.data;
+				acct.following = false;
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/accounts/:id/block",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.blockAccount(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/accounts/:id/unblock",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.unblockAccount(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/accounts/:id/mute",
+		async (ctx) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.muteAccount(
+					ctx.params.id,
+					(ctx.request as any).body as any,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/accounts/:id/unmute",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.unmuteAccount(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get("/v1/accounts/relationships", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const idsRaw = (ctx.query as any)["id[]"];
+			const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw;
+			const data = (await client.getRelationships(ids)) as any;
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get("/v1/bookmarks", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = (await client.getBookmarks(ctx.query as any)) as any;
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get("/v1/favourites", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.getFavourites(ctx.query as any);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get("/v1/mutes", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.getMutes(ctx.query as any);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get("/v1/blocks", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.getBlocks(ctx.query as any);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get("/v1/follow_ctxs", async (ctx, next) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.getFollowRequests(
+				((ctx.query as any) || { limit: 20 }).limit,
+			);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.post<{ Params: { id: string } }>(
+		"/v1/follow_ctxs/:id/authorize",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.acceptFollowRequest(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/follow_ctxs/:id/reject",
+		async (ctx, next) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.rejectFollowRequest(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
index 6425aac09..46b013f3f 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts
@@ -1,82 +1,84 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import { getClient } from '../ApiMastodonCompatibleService.js';
+import { koaBody } from "koa-body";
+import { getClient } from "../ApiMastodonCompatibleService.js";
 import bodyParser from "koa-bodyparser";
 
 const readScope = [
-	'read:account',
-	'read:drive',
-	'read:blocks',
-	'read:favorites',
-	'read:following',
-	'read:messaging',
-	'read:mutes',
-	'read:notifications',
-	'read:reactions',
-	'read:pages',
-	'read:page-likes',
-	'read:user-groups',
-	'read:channels',
-	'read:gallery',
-	'read:gallery-likes'
-]
+	"read:account",
+	"read:drive",
+	"read:blocks",
+	"read:favorites",
+	"read:following",
+	"read:messaging",
+	"read:mutes",
+	"read:notifications",
+	"read:reactions",
+	"read:pages",
+	"read:page-likes",
+	"read:user-groups",
+	"read:channels",
+	"read:gallery",
+	"read:gallery-likes",
+];
 const writeScope = [
-	'write:account',
-	'write:drive',
-	'write:blocks',
-	'write:favorites',
-	'write:following',
-	'write:messaging',
-	'write:mutes',
-	'write:notes',
-	'write:notifications',
-	'write:reactions',
-	'write:votes',
-	'write:pages',
-	'write:page-likes',
-	'write:user-groups',
-	'write:channels',
-	'write:gallery',
-	'write:gallery-likes'
-]
+	"write:account",
+	"write:drive",
+	"write:blocks",
+	"write:favorites",
+	"write:following",
+	"write:messaging",
+	"write:mutes",
+	"write:notes",
+	"write:notifications",
+	"write:reactions",
+	"write:votes",
+	"write:pages",
+	"write:page-likes",
+	"write:user-groups",
+	"write:channels",
+	"write:gallery",
+	"write:gallery-likes",
+];
 
 export function apiAuthMastodon(router: Router): void {
-
-	router.post('/v1/apps', async (ctx) => {
+	router.post("/v1/apps", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
 		const body: any = ctx.request.body;
 		try {
-			let scope = body.scopes
-			console.log(body)
-			if (typeof scope === 'string') scope = scope.split(' ')
-			const pushScope = new Set<string>()
+			let scope = body.scopes;
+			console.log(body);
+			if (typeof scope === "string") scope = scope.split(" ");
+			const pushScope = new Set<string>();
 			for (const s of scope) {
-				if (s.match(/^read/)) for (const r of readScope) pushScope.add(r)
-				if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r)
+				if (s.match(/^read/)) for (const r of readScope) pushScope.add(r);
+				if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r);
 			}
-			const scopeArr = Array.from(pushScope)
+			const scopeArr = Array.from(pushScope);
 
-			let red = body.redirect_uris
-			if (red === 'urn:ietf:wg:oauth:2.0:oob') {
-				red = 'https://thedesk.top/hello.html'
+			let red = body.redirect_uris;
+			if (red === "urn:ietf:wg:oauth:2.0:oob") {
+				red = "https://thedesk.top/hello.html";
 			}
-			const appData = await client.registerApp(body.client_name, { scopes: scopeArr, redirect_uris: red, website: body.website });
+			const appData = await client.registerApp(body.client_name, {
+				scopes: scopeArr,
+				redirect_uris: red,
+				website: body.website,
+			});
 			ctx.body = {
 				id: appData.id,
 				name: appData.name,
 				website: appData.website,
 				redirect_uri: red,
-				client_id: Buffer.from(appData.url || '').toString('base64'),
+				client_id: Buffer.from(appData.url || "").toString("base64"),
 				client_secret: appData.clientSecret,
-			}
+			};
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
-
 }
diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
index f665f8d23..6098a9543 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts
@@ -1,10 +1,9 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
 import Router from "@koa/router";
-import { getClient } from '../ApiMastodonCompatibleService.js';
+import { getClient } from "../ApiMastodonCompatibleService.js";
 
 export function apiFilterMastodon(router: Router): void {
-
-	router.get('/v1/filters', async (ctx) => {
+	router.get("/v1/filters", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -13,13 +12,13 @@ export function apiFilterMastodon(router: Router): void {
 			const data = await client.getFilters();
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.get('/v1/filters/:id', async (ctx) => {
+	router.get("/v1/filters/:id", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -28,13 +27,13 @@ export function apiFilterMastodon(router: Router): void {
 			const data = await client.getFilter(ctx.params.id);
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.post('/v1/filters', async (ctx) => {
+	router.post("/v1/filters", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -43,28 +42,32 @@ export function apiFilterMastodon(router: Router): void {
 			const data = await client.createFilter(body.phrase, body.context, body);
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.post('/v1/filters/:id', async (ctx) => {
+	router.post("/v1/filters/:id", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
 		const body: any = ctx.request.body;
 		try {
-			const data = await client.updateFilter(ctx.params.id, body.phrase, body.context);
+			const data = await client.updateFilter(
+				ctx.params.id,
+				body.phrase,
+				body.context,
+			);
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.delete('/v1/filters/:id', async (ctx) => {
+	router.delete("/v1/filters/:id", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -73,10 +76,9 @@ export function apiFilterMastodon(router: Router): void {
 			const data = await client.deleteFilter(ctx.params.id);
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
-
 }
diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
index e65b47f02..9c52414ad 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts
@@ -1,16 +1,15 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
 import Router from "@koa/router";
-import { koaBody } from 'koa-body';
-import { getClient } from '../ApiMastodonCompatibleService.js';
-import { toTextWithReaction } from './timeline.js';
+import { koaBody } from "koa-body";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import { toTextWithReaction } from "./timeline.js";
 function toLimitToInt(q: any) {
-    if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10)
-    return q
+	if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10);
+	return q;
 }
 
 export function apiNotificationsMastodon(router: Router): void {
-
-	router.get('/v1/notifications', async (ctx) => {
+	router.get("/v1/notifications", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -19,23 +18,26 @@ export function apiNotificationsMastodon(router: Router): void {
 			const data = await client.getNotifications(toLimitToInt(ctx.query));
 			const notfs = data.data;
 			const ret = notfs.map((n) => {
-					if(n.type !== 'follow' && n.type !== 'follow_request') {
-							if (n.type === 'reaction') n.type = 'favourite'
-							n.status = toTextWithReaction(n.status ? [n.status] : [], ctx.hostname)[0]
-							return n
-					} else {
-							return n
-					}
-			})
+				if (n.type !== "follow" && n.type !== "follow_request") {
+					if (n.type === "reaction") n.type = "favourite";
+					n.status = toTextWithReaction(
+						n.status ? [n.status] : [],
+						ctx.hostname,
+					)[0];
+					return n;
+				} else {
+					return n;
+				}
+			});
 			ctx.body = ret;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.get('/v1/notification/:id', async (ctx) => {
+	router.get("/v1/notification/:id", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -43,20 +45,20 @@ export function apiNotificationsMastodon(router: Router): void {
 		try {
 			const dataRaw = await client.getNotification(ctx.params.id);
 			const data = dataRaw.data;
-			if(data.type !== 'follow' && data.type !== 'follow_request') {
-					if (data.type === 'reaction') data.type = 'favourite'
-					ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0]
+			if (data.type !== "follow" && data.type !== "follow_request") {
+				if (data.type === "reaction") data.type = "favourite";
+				ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0];
 			} else {
-					ctx.body = data
+				ctx.body = data;
 			}
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.post('/v1/notifications/clear', async (ctx) => {
+	router.post("/v1/notifications/clear", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -65,13 +67,13 @@ export function apiNotificationsMastodon(router: Router): void {
 			const data = await client.dismissNotifications();
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
 
-	router.post('/v1/notification/:id/dismiss', async (ctx) => {
+	router.post("/v1/notification/:id/dismiss", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
@@ -80,10 +82,9 @@ export function apiNotificationsMastodon(router: Router): void {
 			const data = await client.dismissNotification(ctx.params.id);
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
-
 }
diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts
index dcd5be461..48161733e 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/search.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts
@@ -1,24 +1,22 @@
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
 import Router from "@koa/router";
-import { getClient } from '../ApiMastodonCompatibleService.js';
+import { getClient } from "../ApiMastodonCompatibleService.js";
 
 export function apiSearchMastodon(router: Router): void {
-
-	router.get('/v1/search', async (ctx) => {
+	router.get("/v1/search", async (ctx) => {
 		const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 		const accessTokens = ctx.request.headers.authorization;
 		const client = getClient(BASE_URL, accessTokens);
 		const body: any = ctx.request.body;
 		try {
-			const query: any = ctx.query
-			const type = query.type || ''
+			const query: any = ctx.query;
+			const type = query.type || "";
 			const data = await client.search(query.q, type, query);
 			ctx.body = data.data;
 		} catch (e: any) {
-			console.error(e)
+			console.error(e);
 			ctx.status = 401;
 			ctx.body = e.response.data;
 		}
 	});
-
 }
diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts
index f01665537..3afd7e576 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/status.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts
@@ -1,402 +1,483 @@
 import Router from "@koa/router";
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
-import { getClient } from '../ApiMastodonCompatibleService.js';
-import fs from 'fs'
-import { pipeline } from 'node:stream';
-import { promisify } from 'node:util';
-import { createTemp } from '@/misc/create-temp.js';
-import { emojiRegex, emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js';
-import axios from 'axios';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import fs from "fs";
+import { pipeline } from "node:stream";
+import { promisify } from "node:util";
+import { createTemp } from "@/misc/create-temp.js";
+import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
+import axios from "axios";
 const pump = promisify(pipeline);
 
 export function apiStatusMastodon(router: Router): void {
-    router.post('/v1/statuses', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const body: any = ctx.request.body
-            const text = body.status
-            const removed = text.replace(/@\S+/g, '').replaceAll(' ', '')
-            const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed)
-            const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed)
-            if (body.in_reply_to_id && isDefaultEmoji || isCustomEmoji) {
-                const a = await client.createEmojiReaction(body.in_reply_to_id, removed)
-                ctx.body = a.data
-            }
-            if (body.in_reply_to_id && removed === '/unreact') {
-                try {
-                    const id = body.in_reply_to_id
-                    const post = await client.getStatus(id)
-                    const react = post.data.emoji_reactions.filter((e) => e.me)[0].name
-                    const data = await client.deleteEmojiReaction(id, react);
-                    ctx.body = data.data;
-                } catch (e: any) {
-                    console.error(e)
-                    ctx.status = (401);
-                    ctx.body = e.response.data;
-                }
-            }
-            if (!body.media_ids) body.media_ids = undefined
-            if (body.media_ids && !body.media_ids.length) body.media_ids = undefined
-            const data = await client.postStatus(text, body);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.deleteStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    interface IReaction {
-        id: string
-        createdAt: string
-        user: MisskeyEntity.User,
-        type: string
-    }
-    router.get<{ Params: { id: string } }>('/v1/statuses/:id/context', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const id = ctx.params.id
-            const data = await client.getStatusContext(id, ctx.query as any);
-            const status = await client.getStatus(id);
-            const reactionsAxios = await axios.get(`${BASE_URL}/api/notes/reactions?noteId=${id}`)
-            const reactions: IReaction[] = reactionsAxios.data
-            const text = reactions.map((r) => `${r.type.replace('@.', '')} ${r.user.username}`).join('<br />')
-            data.data.descendants.unshift(statusModel(status.data.id, status.data.account.id, status.data.emojis, text))
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/statuses/:id/reblogged_by', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getStatusRebloggedBy(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/statuses/:id/favourited_by', async (ctx, reply) => {
-        ctx.body = []
-    });
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/favourite', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        const react = await getFirstReaction(BASE_URL, accessTokens);
-        try {
-            const a = await client.createEmojiReaction(ctx.params.id, react) as any;
-            //const data = await client.favouriteStatus(ctx.params.id) as any;
-            ctx.body = a.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/unfavourite', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        const react = await getFirstReaction(BASE_URL, accessTokens);
-        try {
-            const data = await client.deleteEmojiReaction(ctx.params.id, react);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.post("/v1/statuses", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const body: any = ctx.request.body;
+			const text = body.status;
+			const removed = text.replace(/@\S+/g, "").replaceAll(" ", "");
+			const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
+			const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
+			if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
+				const a = await client.createEmojiReaction(
+					body.in_reply_to_id,
+					removed,
+				);
+				ctx.body = a.data;
+			}
+			if (body.in_reply_to_id && removed === "/unreact") {
+				try {
+					const id = body.in_reply_to_id;
+					const post = await client.getStatus(id);
+					const react = post.data.emoji_reactions.filter((e) => e.me)[0].name;
+					const data = await client.deleteEmojiReaction(id, react);
+					ctx.body = data.data;
+				} catch (e: any) {
+					console.error(e);
+					ctx.status = 401;
+					ctx.body = e.response.data;
+				}
+			}
+			if (!body.media_ids) body.media_ids = undefined;
+			if (body.media_ids && !body.media_ids.length) body.media_ids = undefined;
+			const data = await client.postStatus(text, body);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get<{ Params: { id: string } }>(
+		"/v1/statuses/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.delete<{ Params: { id: string } }>(
+		"/v1/statuses/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.deleteStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	interface IReaction {
+		id: string;
+		createdAt: string;
+		user: MisskeyEntity.User;
+		type: string;
+	}
+	router.get<{ Params: { id: string } }>(
+		"/v1/statuses/:id/context",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const id = ctx.params.id;
+				const data = await client.getStatusContext(id, ctx.query as any);
+				const status = await client.getStatus(id);
+				const reactionsAxios = await axios.get(
+					`${BASE_URL}/api/notes/reactions?noteId=${id}`,
+				);
+				const reactions: IReaction[] = reactionsAxios.data;
+				const text = reactions
+					.map((r) => `${r.type.replace("@.", "")} ${r.user.username}`)
+					.join("<br />");
+				data.data.descendants.unshift(
+					statusModel(
+						status.data.id,
+						status.data.account.id,
+						status.data.emojis,
+						text,
+					),
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/statuses/:id/reblogged_by",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getStatusRebloggedBy(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/statuses/:id/favourited_by",
+		async (ctx, reply) => {
+			ctx.body = [];
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/favourite",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			const react = await getFirstReaction(BASE_URL, accessTokens);
+			try {
+				const a = (await client.createEmojiReaction(
+					ctx.params.id,
+					react,
+				)) as any;
+				//const data = await client.favouriteStatus(ctx.params.id) as any;
+				ctx.body = a.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/unfavourite",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			const react = await getFirstReaction(BASE_URL, accessTokens);
+			try {
+				const data = await client.deleteEmojiReaction(ctx.params.id, react);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/reblog', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.reblogStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/reblog",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.reblogStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/unreblog', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.unreblogStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/unreblog",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.unreblogStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/bookmark', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.bookmarkStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/bookmark",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.bookmarkStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/unbookmark', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.unbookmarkStatus(ctx.params.id) as any;
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/unbookmark",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = (await client.unbookmarkStatus(ctx.params.id)) as any;
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/pin', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.pinStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-
-    router.post<{ Params: { id: string } }>('/v1/statuses/:id/unpin', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.unpinStatus(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post('/v1/media', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const multipartData = await ctx.file;
-            if (!multipartData) {
-							ctx.body = { error: 'No image' };
-							return;
-						}
-            const [path] = await createTemp();
-            await pump(multipartData.buffer, fs.createWriteStream(path));
-            const image = fs.readFileSync(path);
-            const data = await client.uploadMedia(image);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post('/v2/media', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const multipartData = await ctx.file;
-            if (!multipartData) {
-							ctx.body = { error: 'No image' };
-							return;
-						}
-            const [path] = await createTemp();
-            await pump(multipartData.buffer, fs.createWriteStream(path));
-            const image = fs.readFileSync(path);
-            const data = await client.uploadMedia(image);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getMedia(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.put<{ Params: { id: string } }>('/v1/media/:id',async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.updateMedia(ctx.params.id, ctx.request.body as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/polls/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getPoll(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.votePoll(ctx.params.id, (ctx.request.body as any).choices);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/pin",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.pinStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 
+	router.post<{ Params: { id: string } }>(
+		"/v1/statuses/:id/unpin",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.unpinStatus(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post("/v1/media", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const multipartData = await ctx.file;
+			if (!multipartData) {
+				ctx.body = { error: "No image" };
+				return;
+			}
+			const [path] = await createTemp();
+			await pump(multipartData.buffer, fs.createWriteStream(path));
+			const image = fs.readFileSync(path);
+			const data = await client.uploadMedia(image);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.post("/v2/media", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const multipartData = await ctx.file;
+			if (!multipartData) {
+				ctx.body = { error: "No image" };
+				return;
+			}
+			const [path] = await createTemp();
+			await pump(multipartData.buffer, fs.createWriteStream(path));
+			const image = fs.readFileSync(path);
+			const data = await client.uploadMedia(image);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get<{ Params: { id: string } }>(
+		"/v1/media/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getMedia(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.put<{ Params: { id: string } }>(
+		"/v1/media/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.updateMedia(
+					ctx.params.id,
+					ctx.request.body as any,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/polls/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getPoll(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/polls/:id/votes",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.votePoll(
+					ctx.params.id,
+					(ctx.request.body as any).choices,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 }
 
-async function getFirstReaction(BASE_URL: string, accessTokens: string | undefined) {
-    const accessTokenArr = accessTokens?.split(' ') ?? [null];
-    const accessToken = accessTokenArr[accessTokenArr.length - 1];
-    let react = '👍'
-    try {
-        const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, {
-            scope: ['client', 'base'],
-            key: 'reactions',
-            i: accessToken
-        })
-        const reactRaw = api.data
-        react = Array.isArray(reactRaw) ? api.data[0] : '👍'
-        console.log(api.data)
-        return react
-    } catch (e) {
-        return react
-    }
+async function getFirstReaction(
+	BASE_URL: string,
+	accessTokens: string | undefined,
+) {
+	const accessTokenArr = accessTokens?.split(" ") ?? [null];
+	const accessToken = accessTokenArr[accessTokenArr.length - 1];
+	let react = "👍";
+	try {
+		const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, {
+			scope: ["client", "base"],
+			key: "reactions",
+			i: accessToken,
+		});
+		const reactRaw = api.data;
+		react = Array.isArray(reactRaw) ? api.data[0] : "👍";
+		console.log(api.data);
+		return react;
+	} catch (e) {
+		return react;
+	}
 }
 
-export function statusModel(id: string | null, acctId: string | null, emojis: MastodonEntity.Emoji[], content: string) {
-    const now = "1970-01-02T00:00:00.000Z"
-    return {
-        id: '9atm5frjhb',
-        uri: 'https://http.cat/404', // ""
-        url: 'https://http.cat/404', // "",
-        account: {
-            id: '9arzuvv0sw',
-            username: 'ReactionBot',
-            acct: 'ReactionBot',
-            display_name: 'ReactionOfThisPost',
-            locked: false,
-            created_at: now,
-            followers_count: 0,
-            following_count: 0,
-            statuses_count: 0,
-            note: '',
-            url: 'https://http.cat/404',
-            avatar: 'https://http.cat/404',
-            avatar_static: 'https://http.cat/404',
-            header: 'https://http.cat/404', // ""
-            header_static: 'https://http.cat/404', // ""
-            emojis: [],
-            fields: [],
-            moved: null,
-            bot: false,
-        },
-        in_reply_to_id: id,
-        in_reply_to_account_id: acctId,
-        reblog: null,
-        content: `<p>${content}</p>`,
-        plain_content: null,
-        created_at: now,
-        emojis: emojis,
-        replies_count: 0,
-        reblogs_count: 0,
-        favourites_count: 0,
-        favourited: false,
-        reblogged: false,
-        muted: false,
-        sensitive: false,
-        spoiler_text: '',
-        visibility: 'public' as const,
-        media_attachments: [],
-        mentions: [],
-        tags: [],
-        card: null,
-        poll: null,
-        application: null,
-        language: null,
-        pinned: false,
-        emoji_reactions: [],
-        bookmarked: false,
-        quote: false,
-    }
-} 
+export function statusModel(
+	id: string | null,
+	acctId: string | null,
+	emojis: MastodonEntity.Emoji[],
+	content: string,
+) {
+	const now = "1970-01-02T00:00:00.000Z";
+	return {
+		id: "9atm5frjhb",
+		uri: "https://http.cat/404", // ""
+		url: "https://http.cat/404", // "",
+		account: {
+			id: "9arzuvv0sw",
+			username: "ReactionBot",
+			acct: "ReactionBot",
+			display_name: "ReactionOfThisPost",
+			locked: false,
+			created_at: now,
+			followers_count: 0,
+			following_count: 0,
+			statuses_count: 0,
+			note: "",
+			url: "https://http.cat/404",
+			avatar: "https://http.cat/404",
+			avatar_static: "https://http.cat/404",
+			header: "https://http.cat/404", // ""
+			header_static: "https://http.cat/404", // ""
+			emojis: [],
+			fields: [],
+			moved: null,
+			bot: false,
+		},
+		in_reply_to_id: id,
+		in_reply_to_account_id: acctId,
+		reblog: null,
+		content: `<p>${content}</p>`,
+		plain_content: null,
+		created_at: now,
+		emojis: emojis,
+		replies_count: 0,
+		reblogs_count: 0,
+		favourites_count: 0,
+		favourited: false,
+		reblogged: false,
+		muted: false,
+		sensitive: false,
+		spoiler_text: "",
+		visibility: "public" as const,
+		media_attachments: [],
+		mentions: [],
+		tags: [],
+		card: null,
+		poll: null,
+		application: null,
+		language: null,
+		pinned: false,
+		emoji_reactions: [],
+		bookmarked: false,
+		quote: false,
+	};
+}
diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
index 2cbdf5a0e..9caf43114 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts
@@ -1,245 +1,305 @@
 import Router from "@koa/router";
-import megalodon, { Entity, MegalodonInterface } from '@cutls/megalodon';
-import { getClient } from '../ApiMastodonCompatibleService.js'
-import { statusModel } from './status.js';
-import Autolinker from 'autolinker';
+import megalodon, { Entity, MegalodonInterface } from "@cutls/megalodon";
+import { getClient } from "../ApiMastodonCompatibleService.js";
+import { statusModel } from "./status.js";
+import Autolinker from "autolinker";
 import { ParsedUrlQuery } from "querystring";
 
 export function toLimitToInt(q: ParsedUrlQuery) {
-    if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10).toString()
-    return q
+	if (q.limit)
+		if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10).toString();
+	return q;
 }
 
 export function toTextWithReaction(status: Entity.Status[], host: string) {
 	return status.map((t) => {
-        if (!t) return statusModel(null, null, [], 'no content')
-        if (!t.emoji_reactions) return t
-        if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0]
-        const reactions = t.emoji_reactions.map((r) => `${r.name.replace('@.', '')} (${r.count}${r.me ? "* " : ''})`);
-        //t.emojis = getEmoji(t.content, host)
-        t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(', ')}</p>`
-        return t
-    })
+		if (!t) return statusModel(null, null, [], "no content");
+		if (!t.emoji_reactions) return t;
+		if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
+		const reactions = t.emoji_reactions.map(
+			(r) => `${r.name.replace("@.", "")} (${r.count}${r.me ? "* " : ""})`,
+		);
+		//t.emojis = getEmoji(t.content, host)
+		t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
+			", ",
+		)}</p>`;
+		return t;
+	});
 }
 export function autoLinker(input: string, host: string) {
 	return Autolinker.link(input, {
-        hashtag: 'twitter',
-        mention: 'twitter',
-        email: false,
-        stripPrefix: false,
-        replaceFn : function (match) {
-            switch(match.type) {
-                case 'url':
-									return true
-                case 'mention':
-                    console.log("Mention: ", match.getMention());
-                    console.log("Mention Service Name: ", match.getServiceName());
-                    return `<a href="https://${host}/@${encodeURIComponent(match.getMention())}" target="_blank">@${match.getMention()}</a>`;
-                case 'hashtag':
-                    console.log("Hashtag: ", match.getHashtag());
-                    return `<a href="https://${host}/tags/${encodeURIComponent(match.getHashtag())}" target="_blank">#${match.getHashtag()}</a>`;
-            }
-            return false
-        }
-    } );
+		hashtag: "twitter",
+		mention: "twitter",
+		email: false,
+		stripPrefix: false,
+		replaceFn: function (match) {
+			switch (match.type) {
+				case "url":
+					return true;
+				case "mention":
+					console.log("Mention: ", match.getMention());
+					console.log("Mention Service Name: ", match.getServiceName());
+					return `<a href="https://${host}/@${encodeURIComponent(
+						match.getMention(),
+					)}" target="_blank">@${match.getMention()}</a>`;
+				case "hashtag":
+					console.log("Hashtag: ", match.getHashtag());
+					return `<a href="https://${host}/tags/${encodeURIComponent(
+						match.getHashtag(),
+					)}" target="_blank">#${match.getHashtag()}</a>`;
+			}
+			return false;
+		},
+	});
 }
 
 export function apiTimelineMastodon(router: Router): void {
-    router.get('/v1/timelines/public', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const query: any = ctx.query
-            const data = query.local ? await client.getLocalTimeline(toLimitToInt(query)) : await client.getPublicTimeline(toLimitToInt(query));
-            ctx.body = toTextWithReaction(data.data, ctx.hostname);
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { hashtag: string } }>('/v1/timelines/tag/:hashtag', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getTagTimeline(ctx.params.hashtag, toLimitToInt(ctx.query));
-            ctx.body = toTextWithReaction(data.data, ctx.hostname);
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { hashtag: string } }>('/v1/timelines/home', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getHomeTimeline(toLimitToInt(ctx.query));
-            ctx.body = toTextWithReaction(data.data, ctx.hostname);
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { listId: string } }>('/v1/timelines/list/:listId', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getListTimeline(ctx.params.listId, toLimitToInt(ctx.query));
-            ctx.body = toTextWithReaction(data.data, ctx.hostname);
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/conversations', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getConversationTimeline(toLimitToInt(ctx.query));
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get('/v1/lists', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getLists();
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getList(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post('/v1/lists', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.createList((ctx.query as any).title);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.put<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.updateList(ctx.params.id, ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.delete<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.deleteList(ctx.params.id);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.getAccountsInList(ctx.params.id, ctx.query as any);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.addAccountsToList(ctx.params.id, (ctx.query as any).account_ids);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
-    router.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
-        const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
-        const accessTokens = ctx.headers.authorization;
-        const client = getClient(BASE_URL, accessTokens);
-        try {
-            const data = await client.deleteAccountsFromList(ctx.params.id, (ctx.query as any).account_ids);
-            ctx.body = data.data;
-        } catch (e: any) {
-            console.error(e)
-            console.error(e.response.data)
-            ctx.status = (401);
-            ctx.body = e.response.data;
-        }
-    });
+	router.get("/v1/timelines/public", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const query: any = ctx.query;
+			const data = query.local
+				? await client.getLocalTimeline(toLimitToInt(query))
+				: await client.getPublicTimeline(toLimitToInt(query));
+			ctx.body = toTextWithReaction(data.data, ctx.hostname);
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get<{ Params: { hashtag: string } }>(
+		"/v1/timelines/tag/:hashtag",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getTagTimeline(
+					ctx.params.hashtag,
+					toLimitToInt(ctx.query),
+				);
+				ctx.body = toTextWithReaction(data.data, ctx.hostname);
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { hashtag: string } }>(
+		"/v1/timelines/home",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getHomeTimeline(toLimitToInt(ctx.query));
+				ctx.body = toTextWithReaction(data.data, ctx.hostname);
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { listId: string } }>(
+		"/v1/timelines/list/:listId",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getListTimeline(
+					ctx.params.listId,
+					toLimitToInt(ctx.query),
+				);
+				ctx.body = toTextWithReaction(data.data, ctx.hostname);
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get("/v1/conversations", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.getConversationTimeline(
+				toLimitToInt(ctx.query),
+			);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get("/v1/lists", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.getLists();
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.get<{ Params: { id: string } }>(
+		"/v1/lists/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getList(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post("/v1/lists", async (ctx, reply) => {
+		const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+		const accessTokens = ctx.headers.authorization;
+		const client = getClient(BASE_URL, accessTokens);
+		try {
+			const data = await client.createList((ctx.query as any).title);
+			ctx.body = data.data;
+		} catch (e: any) {
+			console.error(e);
+			console.error(e.response.data);
+			ctx.status = 401;
+			ctx.body = e.response.data;
+		}
+	});
+	router.put<{ Params: { id: string } }>(
+		"/v1/lists/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.updateList(ctx.params.id, ctx.query as any);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.delete<{ Params: { id: string } }>(
+		"/v1/lists/:id",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.deleteList(ctx.params.id);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.get<{ Params: { id: string } }>(
+		"/v1/lists/:id/accounts",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.getAccountsInList(
+					ctx.params.id,
+					ctx.query as any,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.post<{ Params: { id: string } }>(
+		"/v1/lists/:id/accounts",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.addAccountsToList(
+					ctx.params.id,
+					(ctx.query as any).account_ids,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
+	router.delete<{ Params: { id: string } }>(
+		"/v1/lists/:id/accounts",
+		async (ctx, reply) => {
+			const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
+			const accessTokens = ctx.headers.authorization;
+			const client = getClient(BASE_URL, accessTokens);
+			try {
+				const data = await client.deleteAccountsFromList(
+					ctx.params.id,
+					(ctx.query as any).account_ids,
+				);
+				ctx.body = data.data;
+			} catch (e: any) {
+				console.error(e);
+				console.error(e.response.data);
+				ctx.status = 401;
+				ctx.body = e.response.data;
+			}
+		},
+	);
 }
 function escapeHTML(str: string) {
-    if (!str) {
-			return ''
-    }
-    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;')
+	if (!str) {
+		return "";
+	}
+	return str
+		.replace(/&/g, "&amp;")
+		.replace(/</g, "&lt;")
+		.replace(/>/g, "&gt;")
+		.replace(/"/g, "&quot;")
+		.replace(/'/g, "&#039;");
 }
 function nl2br(str: string) {
-    if (!str) {
-			return ''
-    }
-    str = str.replace(/\r\n/g, '<br />')
-    str = str.replace(/(\n|\r)/g, '<br />')
-    return str
+	if (!str) {
+		return "";
+	}
+	str = str.replace(/\r\n/g, "<br />");
+	str = str.replace(/(\n|\r)/g, "<br />");
+	return str;
 }
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 5be8ab109..167aa614c 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -152,7 +152,7 @@ export default class Connection {
 		} catch (e) {
 			return;
 		}
-		
+
 		const simpleObj = objs[0];
 		if (simpleObj.stream) {
 			// is Mastodon Compatible
diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts
index 4ccad96e8..14e07b748 100644
--- a/packages/backend/src/server/api/streaming.ts
+++ b/packages/backend/src/server/api/streaming.ts
@@ -16,7 +16,7 @@ export const initializeStreamingServer = (server: http.Server) => {
 
 	ws.on("request", async (request) => {
 		const q = request.resourceURL.query as ParsedUrlQuery;
-		const headers = request.httpRequest.headers['sec-websocket-protocol'] || '';
+		const headers = request.httpRequest.headers["sec-websocket-protocol"] || "";
 		const cred = q.i || q.access_token || headers;
 		const accessToken = cred.toString();
 
@@ -48,9 +48,17 @@ export const initializeStreamingServer = (server: http.Server) => {
 		redisClient.on("message", onRedisMessage);
 		const host = `https://${request.host}`;
 		const prepareStream = q.stream?.toString();
-		console.log('start', q);
+		console.log("start", q);
 
-		const main = new MainStreamConnection(connection, ev, user, app, host, accessToken, prepareStream);
+		const main = new MainStreamConnection(
+			connection,
+			ev,
+			user,
+			app,
+			host,
+			accessToken,
+			prepareStream,
+		);
 
 		const intervalId = user
 			? setInterval(() => {
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 139e1bd9c..6235bb232 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -20,7 +20,7 @@ import { createTemp } from "@/misc/create-temp.js";
 import { publishMainStream } from "@/services/stream.js";
 import * as Acct from "@/misc/acct.js";
 import { envOption } from "@/env.js";
-import megalodon, { MegalodonInterface } from '@cutls/megalodon';
+import megalodon, { MegalodonInterface } from "@cutls/megalodon";
 import activityPub from "./activitypub.js";
 import nodeinfo from "./nodeinfo.js";
 import wellKnown from "./well-known.js";
@@ -29,7 +29,7 @@ import fileServer from "./file/index.js";
 import proxyServer from "./proxy/index.js";
 import webServer from "./web/index.js";
 import { initializeStreamingServer } from "./api/streaming.js";
-import {koaBody} from "koa-body";
+import { koaBody } from "koa-body";
 
 export const serverLogger = new Logger("server", "gray", false);
 
@@ -141,26 +141,30 @@ router.get("/verify-email/:code", async (ctx) => {
 mastoRouter.get("/oauth/authorize", async (ctx) => {
 	const client_id = ctx.request.query.client_id;
 	console.log(ctx.request.req);
-	ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
+	ctx.redirect(Buffer.from(client_id?.toString() || "", "base64").toString());
 });
 
 mastoRouter.post("/oauth/token", async (ctx) => {
 	const body: any = ctx.request.body;
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;
-	const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
+	const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
 	const m = body.code.match(/^[a-zA-Z0-9-]+/);
 	if (!m.length) {
-		ctx.body = {error: 'Invalid code'}
-		return
+		ctx.body = { error: "Invalid code" };
+		return;
 	}
 	try {
-		const atData = await client.fetchAccessToken(null, body.client_secret, m[0]);
+		const atData = await client.fetchAccessToken(
+			null,
+			body.client_secret,
+			m[0],
+		);
 		ctx.body = {
 			access_token: atData.accessToken,
-			token_type: 'Bearer',
-			scope: 'read write follow',
-			created_at: new Date().getTime() / 1000
+			token_type: "Bearer",
+			scope: "read write follow",
+			created_at: new Date().getTime() / 1000,
 		};
 	} catch (err: any) {
 		console.error(err);
diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts
index cb58efa81..c9f3b6cac 100644
--- a/packages/backend/src/server/web/url-preview.ts
+++ b/packages/backend/src/server/web/url-preview.ts
@@ -44,12 +44,21 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
 
 		logger.succ(`Got preview of ${url}: ${summary.title}`);
 
-		if (summary.url && !(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) {
-			throw new Error('unsupported schema included');
+		if (
+			summary.url &&
+			!(summary.url.startsWith("http://") || summary.url.startsWith("https://"))
+		) {
+			throw new Error("unsupported schema included");
 		}
 
-		if (summary.player?.url && !(summary.player.url.startsWith('http://') || summary.player.url.startsWith('https://'))) {
-			throw new Error('unsupported schema included');
+		if (
+			summary.player?.url &&
+			!(
+				summary.player.url.startsWith("http://") ||
+				summary.player.url.startsWith("https://")
+			)
+		) {
+			throw new Error("unsupported schema included");
 		}
 
 		summary.icon = wrap(summary.icon);
diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index 3f040541f..ddfcc3de1 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -377,12 +377,7 @@ export default defineComponent({
 
 						case "quote": {
 							if (!this.nowrap) {
-								return [
-									h(
-										"blockquote",
-										genEl(token.children),
-									),
-								];
+								return [h("blockquote", genEl(token.children))];
 							} else {
 								return [
 									h(
diff --git a/packages/client/src/navbar.ts b/packages/client/src/navbar.ts
index 5b116cee1..c8c455650 100644
--- a/packages/client/src/navbar.ts
+++ b/packages/client/src/navbar.ts
@@ -5,10 +5,10 @@ import * as os from "@/os";
 import { i18n } from "@/i18n";
 import { ui } from "@/config";
 import { unisonReload } from "@/scripts/unison-reload";
-import { defaultStore } from '@/store';
-import { instance } from '@/instance';
-import { host } from '@/config';
-import XTutorial from '@/components/MkTutorialDialog.vue';
+import { defaultStore } from "@/store";
+import { instance } from "@/instance";
+import { host } from "@/config";
+import XTutorial from "@/components/MkTutorialDialog.vue";
 
 export const navbarItemDef = reactive({
 	notifications: {
@@ -152,54 +152,68 @@ export const navbarItemDef = reactive({
 		title: "help",
 		icon: "ph-question-bold ph-lg",
 		action: (ev) => {
-			os.popupMenu([{
-				text: instance.name ?? host,
-				type: 'label',
-			}, {
-				type: 'link',
-				text: i18n.ts.instanceInfo,
-				icon: 'ph-info-bold ph-lg',
-				to: '/about',
-			}, {
-				type: 'link',
-				text: i18n.ts.aboutMisskey,
-				icon: 'ph-lightbulb-bold ph-lg',
-				to: '/about-calckey',
-			}, {
-				type: 'link',
-				text: i18n.ts._apps.apps,
-				icon: 'ph-device-mobile-bold ph-lg',
-				to: '/apps',
-			}, {
-					type: 'button',
-					action: async () => {
-						defaultStore.set('tutorial', 0);
-						os.popup(XTutorial, {}, {}, 'closed');
+			os.popupMenu(
+				[
+					{
+						text: instance.name ?? host,
+						type: "label",
 					},
-					text: i18n.ts.replayTutorial,
-					icon: 'ph-circle-wavy-question-bold ph-lg',
-				}, null, {
-					type: 'parent',
-					text: i18n.ts.developer,
-					icon: 'ph-code-bold ph-lg',
-					children: [{
-						type: 'link',
-						to: '/api-console',
-						text: 'API Console',
-						icon: 'ph-terminal-window-bold ph-lg',
-					}, {
-						text: i18n.ts.document,
-						icon: 'ph-file-doc-bold ph-lg',
-						action: () => {
-							window.open('/api-doc', '_blank');
+					{
+						type: "link",
+						text: i18n.ts.instanceInfo,
+						icon: "ph-info-bold ph-lg",
+						to: "/about",
+					},
+					{
+						type: "link",
+						text: i18n.ts.aboutMisskey,
+						icon: "ph-lightbulb-bold ph-lg",
+						to: "/about-calckey",
+					},
+					{
+						type: "link",
+						text: i18n.ts._apps.apps,
+						icon: "ph-device-mobile-bold ph-lg",
+						to: "/apps",
+					},
+					{
+						type: "button",
+						action: async () => {
+							defaultStore.set("tutorial", 0);
+							os.popup(XTutorial, {}, {}, "closed");
 						},
-					}, {
-						type: 'link',
-						to: '/scratchpad',
-						text: 'AiScript Scratchpad',
-						icon: 'ph-scribble-loop-bold ph-lg',
-					}]
-				}], ev.currentTarget ?? ev.target,
+						text: i18n.ts.replayTutorial,
+						icon: "ph-circle-wavy-question-bold ph-lg",
+					},
+					null,
+					{
+						type: "parent",
+						text: i18n.ts.developer,
+						icon: "ph-code-bold ph-lg",
+						children: [
+							{
+								type: "link",
+								to: "/api-console",
+								text: "API Console",
+								icon: "ph-terminal-window-bold ph-lg",
+							},
+							{
+								text: i18n.ts.document,
+								icon: "ph-file-doc-bold ph-lg",
+								action: () => {
+									window.open("/api-doc", "_blank");
+								},
+							},
+							{
+								type: "link",
+								to: "/scratchpad",
+								text: "AiScript Scratchpad",
+								icon: "ph-scribble-loop-bold ph-lg",
+							},
+						],
+					},
+				],
+				ev.currentTarget ?? ev.target,
 			);
 		},
 	},
diff --git a/packages/client/src/scripts/aiscript/api.ts b/packages/client/src/scripts/aiscript/api.ts
index 32560b4ab..9ec2db963 100644
--- a/packages/client/src/scripts/aiscript/api.ts
+++ b/packages/client/src/scripts/aiscript/api.ts
@@ -27,7 +27,7 @@ export function createAiScriptEnv(opts) {
 			if (token) {
 				utils.assertString(token);
 				// バグがあればundefinedもあり得るため念のため
-				if (typeof token.value !== 'string') throw new Error('invalid token');
+				if (typeof token.value !== "string") throw new Error("invalid token");
 			}
 			apiRequests++;
 			if (apiRequests > 16) return values.NULL;
diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts
index bca3b198a..3ce1a8173 100644
--- a/packages/client/src/scripts/get-user-menu.ts
+++ b/packages/client/src/scripts/get-user-menu.ts
@@ -125,19 +125,22 @@ export function getUserMenu(user, router: Router = mainRouter) {
 		)
 			return;
 
-		await os.apiWithDialog(user.isBlocking ? "blocking/delete" : "blocking/create", {
-			userId: user.id,
-		})
+		await os.apiWithDialog(
+			user.isBlocking ? "blocking/delete" : "blocking/create",
+			{
+				userId: user.id,
+			},
+		);
 		user.isBlocking = !user.isBlocking;
 		await os.api(user.isBlocking ? "mute/create" : "mute/delete", {
 			userId: user.id,
-		})
+		});
 		user.isMuted = user.isBlocking;
 		if (user.isBlocking) {
-			await os.api('following/delete', {
+			await os.api("following/delete", {
 				userId: user.id,
 			});
-			user.isFollowing = false
+			user.isFollowing = false;
 		}
 	}
 

From bd7910d741c0f8496965b3f8bb4d2b88a8e2c6f8 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:51:45 +0100
Subject: [PATCH 29/35] i need to seperate 404 to own route

---
 packages/backend/src/server/api/index.ts | 11 ++++++++---
 packages/backend/src/server/index.ts     |  7 +++++--
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index aee1a43f4..189705903 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -39,6 +39,7 @@ app.use(async (ctx, next) => {
 // Init router
 const router = new Router();
 const mastoRouter = new Router();
+const errorRouter = new Router();
 
 // Init multer instance
 const upload = multer({
@@ -60,7 +61,10 @@ router.use(
 	}),
 );
 
-mastoRouter.use(koaBody({ multipart: true }));
+mastoRouter.use(koaBody({ 
+	multipart: true,
+	urlencoded: true
+}));
 
 apiMastodonCompatible(mastoRouter);
 
@@ -148,12 +152,13 @@ router.post("/miauth/:session/check", async (ctx) => {
 });
 
 // Return 404 for unknown API
-router.all("(.*)", async (ctx) => {
+errorRouter.all("(.*)", async (ctx) => {
 	ctx.status = 404;
 });
 
 // Register router
-app.use(router.routes());
 app.use(mastoRouter.routes());
+app.use(router.routes());
+app.use(errorRouter.routes());
 
 export default app;
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 6235bb232..9cae761ef 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -72,7 +72,10 @@ app.use(mount("/proxy", proxyServer));
 const router = new Router();
 const mastoRouter = new Router();
 
-mastoRouter.use(koaBody({ multipart: true }));
+mastoRouter.use(koaBody({ 
+	multipart: true,
+	urlencoded: true
+}));
 
 // Routing
 router.use(activityPub.routes());
@@ -174,8 +177,8 @@ mastoRouter.post("/oauth/token", async (ctx) => {
 });
 
 // Register router
-app.use(router.routes());
 app.use(mastoRouter.routes());
+app.use(router.routes());
 
 app.use(mount(webServer));
 

From f4861e9dc56fc0f289e9b248d6e7c439d5e78a25 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 00:57:43 +0100
Subject: [PATCH 30/35] remove multipart here?

---
 packages/backend/src/server/index.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 9cae761ef..fbdc7db44 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -73,7 +73,6 @@ const router = new Router();
 const mastoRouter = new Router();
 
 mastoRouter.use(koaBody({ 
-	multipart: true,
 	urlencoded: true
 }));
 

From ecc0397dd39b8174445a678a090374d9a26b0192 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 01:07:44 +0100
Subject: [PATCH 31/35] make secure check

---
 packages/backend/src/server/index.ts | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index fbdc7db44..ade41d9ed 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -151,11 +151,14 @@ mastoRouter.post("/oauth/token", async (ctx) => {
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;
 	const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
-	const m = body.code.match(/^[a-zA-Z0-9-]+/);
-	if (!m.length) {
-		ctx.body = { error: "Invalid code" };
-		return;
-	}
+	let m = null;
+	if (body.code) {
+		m = body.code.match(/^[a-zA-Z0-9-]+/);
+		if (!m.length) {
+			ctx.body = { error: "Invalid code" };
+			return;
+		}
+	} 
 	try {
 		const atData = await client.fetchAccessToken(
 			null,

From 10f5d49fc6c13a9b47f075c1ed68e32a5766ec43 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 01:08:20 +0100
Subject: [PATCH 32/35] do more change

---
 packages/backend/src/server/index.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index ade41d9ed..957854423 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -163,7 +163,7 @@ mastoRouter.post("/oauth/token", async (ctx) => {
 		const atData = await client.fetchAccessToken(
 			null,
 			body.client_secret,
-			m[0],
+			m ? m[0] : null,
 		);
 		ctx.body = {
 			access_token: atData.accessToken,

From ec23cb65d44e1e39381982c8c077c4c187ed968a Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 01:17:35 +0100
Subject: [PATCH 33/35] parse client id

---
 packages/backend/src/server/index.ts | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 957854423..adadc92c4 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -148,6 +148,7 @@ mastoRouter.get("/oauth/authorize", async (ctx) => {
 
 mastoRouter.post("/oauth/token", async (ctx) => {
 	const body: any = ctx.request.body;
+	let client_id: any = ctx.request.query.client_id;
 	const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
 	const generator = (megalodon as any).default;
 	const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
@@ -159,11 +160,16 @@ mastoRouter.post("/oauth/token", async (ctx) => {
 			return;
 		}
 	} 
+	if (client_id instanceof Array) {
+		client_id = client_id.toString();;
+	} else if (!client_id) {
+		client_id = null;
+	}
 	try {
 		const atData = await client.fetchAccessToken(
-			null,
+			client_id,
 			body.client_secret,
-			m ? m[0] : null,
+			m ? m[0] : '',
 		);
 		ctx.body = {
 			access_token: atData.accessToken,

From 49ad84b7543e4b1640ec269532db71704904f633 Mon Sep 17 00:00:00 2001
From: cutestnekoaqua <waterdev@galaxycrow.de>
Date: Sat, 11 Feb 2023 01:51:14 +0100
Subject: [PATCH 34/35] floor created at

---
 packages/backend/src/server/index.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index adadc92c4..cd495971e 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -175,7 +175,7 @@ mastoRouter.post("/oauth/token", async (ctx) => {
 			access_token: atData.accessToken,
 			token_type: "Bearer",
 			scope: "read write follow",
-			created_at: new Date().getTime() / 1000,
+			created_at: Math.floor(new Date().getTime() / 1000),
 		};
 	} catch (err: any) {
 		console.error(err);

From 572f97771f24be80d3cd69ee5b804a418c63ee39 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <kainoa@t1c.dev>
Date: Sat, 11 Feb 2023 06:11:02 +0000
Subject: [PATCH 35/35] Add source SVG logo files

---
 packages/backend/assets/favicon.svg          | 3 +++
 packages/backend/assets/inverse wordmark.svg | 3 +++
 2 files changed, 6 insertions(+)
 create mode 100644 packages/backend/assets/favicon.svg
 create mode 100644 packages/backend/assets/inverse wordmark.svg

diff --git a/packages/backend/assets/favicon.svg b/packages/backend/assets/favicon.svg
new file mode 100644
index 000000000..7f55f6312
--- /dev/null
+++ b/packages/backend/assets/favicon.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4f17cc091606efe4c5e6fc3dbf04b018bc169705f352d52c43dc771d5a716a1d
+size 4285
diff --git a/packages/backend/assets/inverse wordmark.svg b/packages/backend/assets/inverse wordmark.svg
new file mode 100644
index 000000000..fe9a77be9
--- /dev/null
+++ b/packages/backend/assets/inverse wordmark.svg	
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:be36c9edc904f05d7f4a96f2092154b14cd7696fc2b9a317e77e56d85f1f06a0
+size 4395