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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''') + if (!str) { + return ""; + } + return str + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); } 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