From 0756e980d1a41c8abcf2f5d3a228543bc06091f4 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Tue, 7 Feb 2023 22:56:07 +0100 Subject: [PATCH 001/231] wip masto api co-authored-by: cutls --- package.json | 1 + packages/backend/package.json | 1 + packages/backend/src/server/api/endpoints.ts | 12 +- packages/backend/src/server/api/index.ts | 3 + .../mastodon/ApiMastodonCompatibleService.ts | 111 ++++++++++++++++++ packages/backend/src/server/index.ts | 21 ++++ packages/client/src/pages/auth.vue | 2 +- pnpm-lock.yaml | 80 ++++++++++++- 8 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts diff --git a/package.json b/package.json index def7df635..8f6cd272d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "gulp": "gulp build", "watch": "pnpm run dev", "dev": "pnpm node ./scripts/dev.js", + "dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start", "lint": "pnpm -r run lint", "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:run": "cypress run", diff --git a/packages/backend/package.json b/packages/backend/package.json index d4b9c1903..1491d6259 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -81,6 +81,7 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", + "@cutls/megalodon": "^5.1.1", "mfm-js": "0.23.2", "mime-types": "2.1.35", "mocha": "10.2.0", diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index b6d9c3e1f..69298e73f 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -765,17 +765,17 @@ export interface IEndpointMeta { } export interface IEndpoint { - name: string; - exec: any; - meta: IEndpointMeta; - params: Schema; + name: string, + exec: any, // TODO: may be obosolete @ThatOneCalculator + meta: IEndpointMeta, + params: Schema, } -const endpoints: IEndpoint[] = eps.map(([name, ep]) => { +const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => { return { name: name, exec: ep.default, - meta: ep.meta || {}, + meta: ep.meta ?? {}, params: ep.paramDef, }; }); diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index b84bbdbb3..da98a9df1 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -7,6 +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 { Instances, AccessTokens, Users } from "@/models/index.js"; import config from "@/config/index.js"; import endpoints from "./endpoints.js"; @@ -57,6 +58,8 @@ const upload = multer({ // Init router const router = new Router(); +apiMastodonCompatible(router); + /** * Register endpoint handlers */ diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts new file mode 100644 index 000000000..d389592a1 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts @@ -0,0 +1,111 @@ +import Router from "@koa/router"; +import megalodon, { MegalodonInterface } from 'megalodon'; + +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 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' +] +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' +] +export function apiMastodonCompatible(router: Router): void { + + 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.req; + try { + let scope = body.scopes + if (typeof scope === 'string') scope = scope.split(' ') + const pushScope: string[] = [] + for (const s of scope) { + if (s.match(/^read/)) for (const r of readScope) pushScope.push(r) + if (s.match(/^write/)) for (const r of writeScope) pushScope.push(r) + } + 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: pushScope, redirect_uris: red, website: body.website }); + ctx.body = { + id: appData.id, + name: appData.name, + website: appData.website, + redirect_uri: appData.redirectUri, + client_id: Buffer.from(appData.url || '').toString('base64'), + client_secret: appData.clientSecret, + } + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + router.get('/v1/accounts/verify_credentials', async (ctx) => { + const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; + const accessTokens = ctx.request.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + const data = await client.verifyAccountCredentials(); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + reply.code(401); + return e.response.data; + } + }); + + + router.get('/v1/custom_emojis', async (ctx) => { + const BASE_URL = request.protocol + '://' + request.hostname; + const accessTokens = request.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + const data = await client.getInstanceCustomEmojis(); + return data.data; + } catch (e: any) { + console.error(e) + reply.code(401); + return e.response.data; + } + }); + +} diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 4d4b81d7a..6b752676f 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -20,6 +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 'megalodon'; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; import wellKnown from "./well-known.js"; @@ -133,6 +134,26 @@ router.get("/verify-email/:code", async (ctx) => { } }); +router.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.get("/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; + try { + ctx.body = await client.fetchAccessToken(null, body.client_secret, body.code); + } catch (err: any) { + console.error(err); + ctx.status = 401; + ctx.body = err.response.data; + } +}); + // Register router app.use(router.routes()); diff --git a/packages/client/src/pages/auth.vue b/packages/client/src/pages/auth.vue index 22d73a609..81a387422 100644 --- a/packages/client/src/pages/auth.vue +++ b/packages/client/src/pages/auth.vue @@ -79,7 +79,7 @@ export default defineComponent({ accepted() { this.state = 'accepted'; if (this.session.app.callbackUrl) { - location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`; + location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}`; } }, onLogin(res) { login(res.i); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34a4ffc06..f1ce65483 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,6 +163,7 @@ importers: koa-send: 5.0.1 koa-slow: 2.1.0 koa-views: 7.0.2 + megalodon: ^5.1.1 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 + megalodon: 5.1.1 mfm-js: 0.23.2 mime-types: 2.1.35 mocha: 10.2.0 @@ -2348,7 +2350,6 @@ packages: resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==} dependencies: '@types/node': 18.11.18 - dev: true /@types/offscreencanvas/2019.3.0: resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==} @@ -2529,6 +2530,12 @@ packages: '@types/node': 18.11.18 dev: true + /@types/ws/8.5.4: + resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} + dependencies: + '@types/node': 18.11.18 + dev: false + /@types/yauzl/2.10.0: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true @@ -3246,7 +3253,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 @@ -3254,11 +3261,21 @@ 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 + /axios/1.2.2: + resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} + dependencies: + follow-redirects: 1.15.2_debug@4.3.4 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /babel-eslint/10.1.0_eslint@8.31.0: resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==} engines: {node: '>=6'} @@ -4940,7 +4957,6 @@ packages: /dayjs/1.11.7: resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} - dev: true /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -6197,7 +6213,7 @@ packages: readable-stream: 2.3.7 dev: false - /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: @@ -6205,6 +6221,8 @@ packages: peerDependenciesMeta: debug: optional: true + dependencies: + debug: 4.3.4 /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -8654,6 +8672,30 @@ packages: engines: {node: '>= 0.6'} dev: false + /megalodon/5.1.1: + resolution: {integrity: sha512-zsYzzmogmk9lnXzGk3kKv58LUmZVFMebiya/1CZqZYnBVxq18Ep8l1AU41o+INANMqYxG+hAQvJhE+Z5dcUabQ==} + engines: {node: '>=15.0.0'} + dependencies: + '@types/oauth': 0.9.1 + '@types/ws': 8.5.4 + axios: 1.2.2 + dayjs: 1.11.7 + form-data: 4.0.0 + https-proxy-agent: 5.0.1 + oauth: 0.10.0 + object-assign-deep: 0.4.0 + parse-link-header: 2.0.0 + socks-proxy-agent: 7.0.0 + typescript: 4.9.4 + uuid: 9.0.0 + ws: 8.12.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: false + /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -9321,6 +9363,11 @@ packages: resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} dev: false + /object-assign-deep/0.4.0: + resolution: {integrity: sha512-54Uvn3s+4A/cMWx9tlRez1qtc7pN7pbQ+Yi7mjLjcBpWLlP+XbSHiHbQW6CElDiV4OvuzqnMrBdkgxI1mT8V/Q==} + engines: {node: '>=6'} + dev: false + /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -9613,6 +9660,12 @@ packages: error-ex: 1.3.2 dev: false + /parse-link-header/2.0.0: + resolution: {integrity: sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==} + dependencies: + xtend: 4.0.2 + dev: false + /parse-node-version/1.0.1: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} @@ -10254,6 +10307,10 @@ packages: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} dev: true + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /ps-tree/1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -12943,6 +13000,19 @@ packages: optional: true dev: false + /ws/8.12.0: + resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /xev/3.0.2: resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==} dev: false From bb711adff84a8a9843f6eaecbce8a7b97941f025 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Tue, 7 Feb 2023 23:05:26 +0100 Subject: [PATCH 002/231] fix some ctx stuff --- .../api/mastodon/ApiMastodonCompatibleService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts index d389592a1..008fb8943 100644 --- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts +++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts @@ -88,23 +88,23 @@ export function apiMastodonCompatible(router: Router): void { ctx.body = data.data; } catch (e: any) { console.error(e) - reply.code(401); + ctx.status = 401; return e.response.data; } }); router.get('/v1/custom_emojis', async (ctx) => { - const BASE_URL = request.protocol + '://' + request.hostname; - const accessTokens = request.headers.authorization; + const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; + const accessTokens = ctx.request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const data = await client.getInstanceCustomEmojis(); - return data.data; + ctx.body = data.data; } catch (e: any) { console.error(e) - reply.code(401); - return e.response.data; + ctx.status = 401; + ctx.body = e.response.data; } }); From b1e3c2e1c7d495c18779d0e47dfcfa26516580db Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Thu, 9 Feb 2023 23:21:50 +0100 Subject: [PATCH 003/231] =?UTF-8?q?feat:=20=E2=9C=A8=20v1=20Mastodon=20API?= =?UTF-8?q?=20This=20commit=20adds=20(maybe=20unstable)=20support=20for=20?= =?UTF-8?q?Mastodons=20v1=20api=20also=20some=20v2=20endpoints,=20maybe=20?= =?UTF-8?q?I=20miss=20stuff,=20I=20dont=20know.=20We=20will=20need=20to=20?= =?UTF-8?q?test=20this=20but=20it=20should=20be=20kinda=20stable=20and=20w?= =?UTF-8?q?ork=20like=20(old)=20butter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Natty Co-authored-by: cutls --- packages/backend/package.json | 5 +- packages/backend/src/misc/emoji-regex.ts | 1 + .../backend/src/models/repositories/note.ts | 5 +- packages/backend/src/models/schema/note.ts | 22 +- packages/backend/src/server/api/endpoints.ts | 2 + .../server/api/endpoints/i/get-unsecure.ts | 50 +++ .../mastodon/ApiMastodonCompatibleService.ts | 119 ++---- .../server/api/mastodon/endpoints/account.ts | 323 ++++++++++++++ .../src/server/api/mastodon/endpoints/auth.ts | 81 ++++ .../server/api/mastodon/endpoints/filter.ts | 83 ++++ .../src/server/api/mastodon/endpoints/meta.ts | 97 +++++ .../api/mastodon/endpoints/notifications.ts | 89 ++++ .../server/api/mastodon/endpoints/search.ts | 25 ++ .../server/api/mastodon/endpoints/status.ts | 403 ++++++++++++++++++ .../server/api/mastodon/endpoints/timeline.ts | 246 +++++++++++ .../backend/src/server/api/stream/index.ts | 275 +++++++++--- packages/backend/src/server/api/streaming.ts | 10 +- packages/backend/src/server/index.ts | 15 +- packages/backend/src/server/web/index.ts | 4 + packages/client/src/pages/auth.vue | 3 +- pnpm-lock.yaml | 145 +++++-- 21 files changed, 1805 insertions(+), 198 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/i/get-unsecure.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/account.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/auth.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/filter.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/meta.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/notifications.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/search.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/status.ts create mode 100644 packages/backend/src/server/api/mastodon/endpoints/timeline.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index 1491d6259..bdedcc70e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -40,7 +40,10 @@ "@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", "bcryptjs": "2.4.3", @@ -81,7 +84,7 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "@cutls/megalodon": "^5.1.1", + "@cutls/megalodon": "5.1.15", "mfm-js": "0.23.2", "mime-types": "2.1.35", "mocha": "10.2.0", diff --git a/packages/backend/src/misc/emoji-regex.ts b/packages/backend/src/misc/emoji-regex.ts index 573034f6b..08b44788d 100644 --- a/packages/backend/src/misc/emoji-regex.ts +++ b/packages/backend/src/misc/emoji-regex.ts @@ -2,3 +2,4 @@ import twemoji from "twemoji-parser/dist/lib/regex.js"; const twemojiRegex = twemoji.default; export const emojiRegex = new RegExp(`(${twemojiRegex.source})`); +export const emojiRegexAtStartToEnd = new RegExp(`^(${twemojiRegex.source})$`); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 2bc3b90ca..37c5031c0 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -197,6 +197,8 @@ 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 reactionEmoji = await populateEmojis(reactionEmojiNames, host); const packed: Packed<"Note"> = await awaitAll({ id: note.id, createdAt: note.createdAt.toISOString(), @@ -213,8 +215,9 @@ export const NoteRepository = db.getRepository(Note).extend({ renoteCount: note.renoteCount, repliesCount: note.repliesCount, reactions: convertLegacyReactions(note.reactions), + reactionEmojis: reactionEmoji, + emojis: noteEmoji, tags: note.tags.length > 0 ? note.tags : undefined, - emojis: populateEmojis(note.emojis.concat(reactionEmojiNames), host), fileIds: note.fileIds, files: DriveFiles.packMany(note.fileIds), replyId: note.replyId, diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index 4a7bd80fc..6bc8443f0 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -161,26 +161,8 @@ export const packedNoteSchema = { nullable: false, }, emojis: { - type: "array", - optional: false, - nullable: false, - items: { - type: "object", - optional: false, - nullable: false, - properties: { - name: { - type: "string", - optional: false, - nullable: false, - }, - url: { - type: "string", - optional: false, - nullable: true, - }, - }, - }, + type: 'object', + optional: true, nullable: true, }, reactions: { type: "object", diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 69298e73f..353f137a7 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -198,6 +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_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"; @@ -538,6 +539,7 @@ const eps = [ ["i/regenerate-token", ep___i_regenerateToken], ["i/registry/get-all", ep___i_registry_getAll], ["i/registry/get-detail", ep___i_registry_getDetail], + ["i/registry/get-unsecure", ep___i_registry_getUnsecure], ["i/registry/get", ep___i_registry_get], ["i/registry/keys-with-type", ep___i_registry_keysWithType], ["i/registry/keys", ep___i_registry_keys], diff --git a/packages/backend/src/server/api/endpoints/i/get-unsecure.ts b/packages/backend/src/server/api/endpoints/i/get-unsecure.ts new file mode 100644 index 000000000..eef7f5eca --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/get-unsecure.ts @@ -0,0 +1,50 @@ +import { ApiError } from "../../error.js"; +import define from "../../define.js"; +import { Items } from "@/"; + +export const meta = { + requireCredential: true, + + secure: false, + + errors: { + noSuchKey: { + message: "No such key.", + code: "NO_SUCH_KEY", + id: "ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a", + }, + }, +} as const; + +export const paramDef = { + type: "object", + properties: { + key: { type: "string" }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, + }, + required: ["key"], +} as const; + +export default define(meta, paramDef, async (ps, user) => { + if (ps.key !== "reactions") return; + const query = Items.createQueryBuilder("item") + .where("item.domain IS NULL") + .andWhere("item.userId = :userId", { userId: user.id }) + .andWhere("item.key = :key", { key: ps.key }) + .andWhere("item.scope = :scope", { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return item.value; +}); diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts index 008fb8943..57a86c96d 100644 --- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts +++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts @@ -1,98 +1,30 @@ import Router from "@koa/router"; -import megalodon, { MegalodonInterface } from 'megalodon'; +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'; -function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { +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 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' -] -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' -] + export function apiMastodonCompatible(router: Router): void { - - 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.req; - try { - let scope = body.scopes - if (typeof scope === 'string') scope = scope.split(' ') - const pushScope: string[] = [] - for (const s of scope) { - if (s.match(/^read/)) for (const r of readScope) pushScope.push(r) - if (s.match(/^write/)) for (const r of writeScope) pushScope.push(r) - } - 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: pushScope, redirect_uris: red, website: body.website }); - ctx.body = { - id: appData.id, - name: appData.name, - website: appData.website, - redirect_uri: appData.redirectUri, - client_id: Buffer.from(appData.url || '').toString('base64'), - client_secret: appData.clientSecret, - } - } catch (e: any) { - console.error(e) - ctx.status = 401; - ctx.body = e.response.data; - } - }); - - router.get('/v1/accounts/verify_credentials', async (ctx) => { - const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; - const accessTokens = ctx.request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); - try { - const data = await client.verifyAccountCredentials(); - ctx.body = data.data; - } catch (e: any) { - console.error(e) - ctx.status = 401; - return e.response.data; - } - }); - + apiAuthMastodon(router) + apiAccountMastodon(router) + apiStatusMastodon(router) + apiFilterMastodon(router) + apiTimelineMastodon(router) + apiNotificationsMastodon(router) + apiSearchMastodon(router) router.get('/v1/custom_emojis', async (ctx) => { const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; @@ -108,4 +40,19 @@ export function apiMastodonCompatible(router: Router): void { } }); + 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 + try { + const data = await client.getInstance(); + ctx.body = getInstance(data.data); + } catch (e: any) { + 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 new file mode 100644 index 000000000..1b55a5fbd --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -0,0 +1,323 @@ +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'; + +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', koaBody(), 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', koaBody(), 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 new file mode 100644 index 000000000..5f5756077 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts @@ -0,0 +1,81 @@ +import megalodon, { MegalodonInterface } from '@cutls/megalodon'; +import Router from "@koa/router"; +import { koaBody } from 'koa-body'; +import { getClient } from '../ApiMastodonCompatibleService.js'; + +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' +] +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' +] + +export function apiAuthMastodon(router: Router): void { + + 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); + 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() + 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) + } + 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' + } + 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_secret: appData.clientSecret, + } + } catch (e: any) { + 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 new file mode 100644 index 000000000..3c66362dd --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts @@ -0,0 +1,83 @@ +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(), 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.getFilters(); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + try { + const data = await client.getFilter(ctx.params.id); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + try { + const data = await client.createFilter(body.phrase, body.context, body); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + try { + const data = await client.updateFilter(ctx.params.id, body.phrase, body.context); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + try { + const data = await client.deleteFilter(ctx.params.id); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + +} diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts new file mode 100644 index 000000000..3496272b9 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -0,0 +1,97 @@ +import { Entity } from "@cutls/megalodon"; +// TODO: add calckey features +export function getInstance(response: Entity.Instance) { + return { + uri: response.uri, + title: response.title || "", + short_description: response.description || "", + description: response.description || "", + email: response.email || "", + version: "3.0.0 compatible (Calckey)", + urls: response.urls, + stats: response.stats, + thumbnail: response.thumbnail || "", + languages: ["en", "de", "ja"], + registrations: response.registrations, + approval_required: !response.registrations, + invites_enabled: response.registrations, + configuration: { + accounts: { + max_featured_tags: 20, + }, + statuses: { + max_characters: 3000, + max_media_attachments: 4, + characters_reserved_per_url: response.uri.length, + }, + media_attachments: { + supported_mime_types: [ + "image/jpeg", + "image/png", + "image/gif", + "image/heic", + "image/heif", + "image/webp", + "image/avif", + "video/webm", + "video/mp4", + "video/quicktime", + "video/ogg", + "audio/wave", + "audio/wav", + "audio/x-wav", + "audio/x-pn-wave", + "audio/vnd.wave", + "audio/ogg", + "audio/vorbis", + "audio/mpeg", + "audio/mp3", + "audio/webm", + "audio/flac", + "audio/aac", + "audio/m4a", + "audio/x-m4a", + "audio/mp4", + "audio/3gpp", + "video/x-ms-asf", + ], + image_size_limit: 10485760, + image_matrix_limit: 16777216, + video_size_limit: 41943040, + video_frame_rate_limit: 60, + video_matrix_limit: 2304000, + }, + polls: { + max_options: 8, + max_characters_per_option: 50, + min_expiration: 300, + max_expiration: 2629746, + }, + }, + contact_account: { + id: "1", + username: "admin", + acct: "admin", + display_name: "admin", + locked: true, + bot: true, + discoverable: false, + group: false, + created_at: "1971-01-01T00:00:00.000Z", + 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", + followers_count: -1, + following_count: 0, + statuses_count: 0, + last_status_at: "1971-01-01T00:00:00.000Z", + noindex: true, + emojis: [], + fields: [], + }, + rules: [], + }; +} diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts new file mode 100644 index 000000000..638f0d2d4 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -0,0 +1,89 @@ +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'; +function toLimitToInt(q: any) { + if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10) + return q +} + +export function apiNotificationMastodon(router: Router): void { + + 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); + const body: any = ctx.request.body; + try { + 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 + } + }) + ctx.body = ret; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + 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] + } else { + ctx.body = data + } + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + try { + const data = await client.dismissNotifications(); + ctx.body = data.data; + } catch (e: any) { + console.error(e) + ctx.status = 401; + ctx.body = e.response.data; + } + }); + + 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); + const body: any = ctx.request.body; + try { + const data = await client.dismissNotification(ctx.params.id); + ctx.body = data.data; + } catch (e: any) { + 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 new file mode 100644 index 000000000..f87e199f5 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -0,0 +1,25 @@ +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) => { + 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 data = await client.search(query.q, type, query); + ctx.body = data.data; + } catch (e: any) { + 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 new file mode 100644 index 000000000..593be10f9 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -0,0 +1,403 @@ +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' +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', koaBody(), 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('
') + 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/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/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', koaBody(), 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', koaBody(), 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 + } +} + +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: `

${content}

`, + 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 new file mode 100644 index 000000000..3fdb6ce88 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -0,0 +1,246 @@ +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'; +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 +} + +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 = `

${autoLinker(t.content, host)}

${reactions.join(', ')}

` + 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 `@${match.getMention()}`; + case 'hashtag': + console.log("Hashtag: ", match.getHashtag()); + return `#${match.getHashtag()}`; + } + 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; + } + }); +} +function escapeHTML(str: string) { + if (!str) { + return '' + } + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''') +} +function nl2br(str: string) { + if (!str) { + return '' + } + str = str.replace(/\r\n/g, '
') + str = str.replace(/(\n|\r)/g, '
') + return str +} diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 9675d184c..aeeb54606 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -24,6 +24,9 @@ import { readNotification } from "../common/read-notification.js"; import channels from "./channels/index.js"; import type Channel from "./channel.js"; import type { StreamEventEmitter, StreamMessages } from "./types.js"; +import { Converter } from "@cutls/megalodon"; +import { getClient } from "../mastodon/ApiMastodonCompatibleService.js"; +import { toTextWithReaction } from "../mastodon/endpoints/timeline.js"; /** * Main stream connection @@ -41,17 +44,27 @@ export default class Connection { private channels: Channel[] = []; private subscribingNotes: any = {}; private cachedNotes: Packed<"Note">[] = []; + private isMastodonCompatible: boolean = false; + private host: string; + private accessToken: string; + private currentSubscribe: string[][] = []; constructor( wsConnection: websocket.connection, subscriber: EventEmitter, user: User | null | undefined, token: AccessToken | null | undefined, + host: string, + accessToken: string, + prepareStream: string | undefined, ) { + console.log("constructor", prepareStream); this.wsConnection = wsConnection; this.subscriber = subscriber; if (user) this.user = user; if (token) this.token = token; + if (host) this.host = host; + if (accessToken) this.accessToken = accessToken; this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this); this.onUserEvent = this.onUserEvent.bind(this); @@ -73,6 +86,13 @@ export default class Connection { this.subscriber.on(`user:${this.user.id}`, this.onUserEvent); } + console.log("prepare", prepareStream); + if (prepareStream) { + this.onWsConnectionMessage({ + type: "utf8", + utf8Data: JSON.stringify({ stream: prepareStream, type: "subscribe" }), + }); + } } private onUserEvent(data: StreamMessages["user"]["payload"]) { @@ -125,58 +145,150 @@ export default class Connection { if (data.type !== "utf8") return; if (data.utf8Data == null) return; - let obj: Record; + let objs: Record[]; try { - obj = JSON.parse(data.utf8Data); + objs = [JSON.parse(data.utf8Data)]; } catch (e) { return; } + const simpleObj = objs[0]; - const { type, body } = obj; + const simpleObj = objs[0]; + if (simpleObj.stream) { + // is Mastodon Compatible + this.isMastodonCompatible = true; + if (simpleObj.type === "subscribe") { + let forSubscribe = []; + if (simpleObj.stream === "user") { + this.currentSubscribe.push(["user"]); + objs = [ + { + type: "connect", + body: { + channel: "main", + id: simpleObj.stream, + }, + }, + { + type: "connect", + body: { + channel: "homeTimeline", + id: simpleObj.stream, + }, + }, + ]; + const client = getClient(this.host, this.accessToken); + try { + const tl = await client.getHomeTimeline(); + for (const t of tl.data) forSubscribe.push(t.id); + } catch (e: any) { + console.log(e); + console.error(e.response.data); + } + } else if (simpleObj.stream === "public:local") { + this.currentSubscribe.push(["public:local"]); + objs = [ + { + type: "connect", + body: { + channel: "localTimeline", + id: simpleObj.stream, + }, + }, + ]; + const client = getClient(this.host, this.accessToken); + const tl = await client.getLocalTimeline(); + for (const t of tl.data) forSubscribe.push(t.id); + } else if (simpleObj.stream === "public") { + this.currentSubscribe.push(["public"]); + objs = [ + { + type: "connect", + body: { + channel: "globalTimeline", + id: simpleObj.stream, + }, + }, + ]; + const client = getClient(this.host, this.accessToken); + const tl = await client.getPublicTimeline(); + for (const t of tl.data) forSubscribe.push(t.id); + } else if (simpleObj.stream === "list") { + this.currentSubscribe.push(["list", simpleObj.list]); + objs = [ + { + type: "connect", + body: { + channel: "list", + id: simpleObj.stream, + params: { + listId: simpleObj.list, + }, + }, + }, + ]; + const client = getClient(this.host, this.accessToken); + const tl = await client.getListTimeline(simpleObj.list); + for (const t of tl.data) forSubscribe.push(t.id); + } + for (const s of forSubscribe) { + objs.push({ + type: "s", + body: { + id: s, + }, + }); + } + } + } - switch (type) { - case "readNotification": - this.onReadNotification(body); - break; - case "subNote": - this.onSubscribeNote(body); - break; - case "s": - this.onSubscribeNote(body); - break; // alias - case "sr": - this.onSubscribeNote(body); - this.readNote(body); - break; - case "unsubNote": - this.onUnsubscribeNote(body); - break; - case "un": - this.onUnsubscribeNote(body); - break; // alias - case "connect": - this.onChannelConnectRequested(body); - break; - case "disconnect": - this.onChannelDisconnectRequested(body); - break; - case "channel": - this.onChannelMessageRequested(body); - break; - case "ch": - this.onChannelMessageRequested(body); - break; // alias + for (const obj of objs) { + const { type, body } = obj; + console.log(type, body); + switch (type) { + case "readNotification": + this.onReadNotification(body); + break; + case "subNote": + this.onSubscribeNote(body); + break; + case "s": + this.onSubscribeNote(body); + break; // alias + case "sr": + this.onSubscribeNote(body); + this.readNote(body); + break; + case "unsubNote": + this.onUnsubscribeNote(body); + break; + case "un": + this.onUnsubscribeNote(body); + break; // alias + case "connect": + this.onChannelConnectRequested(body); + break; + case "disconnect": + this.onChannelDisconnectRequested(body); + break; + case "channel": + this.onChannelMessageRequested(body); + break; + case "ch": + this.onChannelMessageRequested(body); + break; // alias - // 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、 - // クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別 - // なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。 - case "typingOnChannel": - this.typingOnChannel(body.channel); - break; - case "typingOnMessaging": - this.typingOnMessaging(body); - break; + // 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、 + // クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別 + // なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。 + case "typingOnChannel": + this.typingOnChannel(body.channel); + break; + case "typingOnMessaging": + this.typingOnMessaging(body); + break; + } } } @@ -280,12 +392,75 @@ export default class Connection { * クライアントにメッセージ送信 */ public sendMessageToWs(type: string, payload: any) { - this.wsConnection.send( - JSON.stringify({ - type: type, - body: payload, - }), - ); + console.log(payload, this.isMastodonCompatible); + if (this.isMastodonCompatible) { + if (payload.type === "note") { + this.wsConnection.send( + JSON.stringify({ + stream: [payload.id], + event: "update", + payload: JSON.stringify( + toTextWithReaction( + [Converter.note(payload.body, this.host)], + this.host, + )[0], + ), + }), + ); + this.onSubscribeNote({ + id: payload.body.id, + }); + } else if (payload.type === "reacted" || payload.type === "unreacted") { + // reaction + const client = getClient(this.host, this.accessToken); + client.getStatus(payload.id).then((data) => { + const newPost = toTextWithReaction([data.data], this.host); + for (const stream of this.currentSubscribe) { + this.wsConnection.send( + JSON.stringify({ + stream, + event: "status.update", + payload: JSON.stringify(newPost[0]), + }), + ); + } + }); + } else if (payload.type === "deleted") { + // delete + for (const stream of this.currentSubscribe) { + this.wsConnection.send( + JSON.stringify({ + stream, + event: "delete", + payload: payload.id, + }), + ); + } + } else if (payload.type === "unreadNotification") { + if (payload.id === "user") { + const body = Converter.notification(payload.body, this.host); + if (body.type === "reaction") body.type = "favourite"; + body.status = toTextWithReaction( + body.status ? [body.status] : [], + "", + )[0]; + this.wsConnection.send( + JSON.stringify({ + stream: ["user"], + event: "notification", + payload: JSON.stringify(body), + }), + ); + } + } + } else { + this.wsConnection.send( + JSON.stringify({ + type: type, + body: payload, + }), + ); + } } /** diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index 9e84ec307..4ccad96e8 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -16,10 +16,13 @@ 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 cred = q.i || q.access_token || headers; + const accessToken = cred.toString(); const [user, app] = await authenticate( request.httpRequest.headers.authorization, - q.i, + accessToken, ).catch((err) => { request.reject(403, err.message); return []; @@ -43,8 +46,11 @@ export const initializeStreamingServer = (server: http.Server) => { } redisClient.on("message", onRedisMessage); + const host = `https://${request.host}`; + const prepareStream = q.stream?.toString(); + console.log('start', q); - const main = new MainStreamConnection(connection, ev, user, app); + 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 6b752676f..4d7259b07 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -20,6 +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"; +const { koaBody } = require('koa-body'); import megalodon, { MegalodonInterface } from 'megalodon'; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; @@ -140,13 +141,21 @@ router.get("/oauth/authorize", async (ctx) => { ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString()); }); -router.get("/oauth/token", async (ctx) => { - const body: any = ctx.request.body +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; 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' } try { - ctx.body = await client.fetchAccessToken(null, body.client_secret, body.code); + 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 + }; } catch (err: any) { console.error(err); ctx.status = 401; diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 4ae8e5bfd..642a17d57 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -634,6 +634,10 @@ router.get("/streaming", async (ctx) => { ctx.status = 503; ctx.set("Cache-Control", "private, max-age=0"); }); +router.get("/api/v1/streaming", async (ctx) => { + ctx.status = 503; + ctx.set("Cache-Control", "private, max-age=0"); +}); // Render base html for all requests router.get("(.*)", async (ctx) => { diff --git a/packages/client/src/pages/auth.vue b/packages/client/src/pages/auth.vue index 81a387422..bb3c54bd3 100644 --- a/packages/client/src/pages/auth.vue +++ b/packages/client/src/pages/auth.vue @@ -78,8 +78,9 @@ export default defineComponent({ methods: { accepted() { 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) { - location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}`; + location.href = `${this.session.app.callbackUrl}?token=${this.session.token}&code=${this.session.token}&state=${getUrlParams().state || ''}`; } }, onLogin(res) { login(res.i); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1ce65483..772f4173f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,7 @@ importers: '@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 @@ -120,8 +121,10 @@ importers: ajv: 8.11.2 archiver: 5.3.1 autobind-decorator: 2.4.0 + autolinker: 4.0.0 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 @@ -155,6 +158,7 @@ importers: 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 @@ -163,7 +167,6 @@ importers: koa-send: 5.0.1 koa-slow: 2.1.0 koa-views: 7.0.2 - megalodon: ^5.1.1 mfm-js: 0.23.2 mime-types: 2.1.35 mocha: 10.2.0 @@ -224,6 +227,7 @@ importers: '@bull-board/api': 4.10.2 '@bull-board/koa': 4.10.2_6tybghmia4wsnt33xeid7y4rby '@bull-board/ui': 4.10.2 + '@cutls/megalodon': 5.1.15 '@discordapp/twemoji': 14.0.2 '@elastic/elasticsearch': 7.17.0 '@koa/cors': 3.4.3 @@ -239,8 +243,10 @@ importers: ajv: 8.11.2 archiver: 5.3.1 autobind-decorator: 2.4.0 + autolinker: 4.0.0 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 @@ -271,6 +277,7 @@ importers: 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 @@ -279,7 +286,6 @@ importers: koa-send: 5.0.1 koa-slow: 2.1.0 koa-views: 7.0.2_6tybghmia4wsnt33xeid7y4rby - megalodon: 5.1.1 mfm-js: 0.23.2 mime-types: 2.1.35 mocha: 10.2.0 @@ -847,6 +853,30 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: false + /@cutls/megalodon/5.1.15: + resolution: {integrity: sha512-4+mIKUYYr2CLY3idSxXk56WSTG9ww3opeenmsPRxftTwcjQTYxGntNkWmJWEbzeJ4rPslnvpwD7cFR62bPf41g==} + engines: {node: '>=15.0.0'} + dependencies: + '@types/oauth': 0.9.1 + '@types/ws': 8.5.4 + axios: 1.2.2 + dayjs: 1.11.7 + form-data: 4.0.0 + https-proxy-agent: 5.0.1 + oauth: 0.10.0 + object-assign-deep: 0.4.0 + parse-link-header: 2.0.0 + socks-proxy-agent: 7.0.0 + typescript: 4.9.4 + uuid: 9.0.0 + ws: 8.12.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: false + /@cypress/request/2.88.11: resolution: {integrity: sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==} engines: {node: '>= 6'} @@ -2016,6 +2046,13 @@ packages: cbor: 8.1.0 dev: true + /@types/co-body/6.1.0: + resolution: {integrity: sha512-3e0q2jyDAnx/DSZi0z2H0yoZ2wt5yRDZ+P7ymcMObvq0ufWRT4tsajyO+Q1VwVWiv9PRR4W3YEjEzBjeZlhF+w==} + dependencies: + '@types/node': 18.11.18 + '@types/qs': 6.9.7 + dev: false + /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: @@ -2083,6 +2120,12 @@ packages: '@types/node': 18.11.18 dev: true + /@types/formidable/2.0.5: + resolution: {integrity: sha512-uvMcdn/KK3maPOaVUAc3HEYbCEhjaGFwww4EsX6IJfWIJ1tzHtDHczuImH3GKdusPnAAmzB07St90uabZeCKPA==} + dependencies: + '@types/node': 18.11.18 + dev: false + /@types/glob-stream/6.1.1: resolution: {integrity: sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==} dependencies: @@ -3202,6 +3245,12 @@ packages: engines: {node: '>=8.10', npm: '>=6.4.1'} dev: false + /autolinker/4.0.0: + resolution: {integrity: sha512-fl5Kh6BmEEZx+IWBfEirnRUU5+cOiV0OK7PEt0RBKvJMJ8GaRseIOeDU3FKf4j3CE5HVefcjHmhYPOcaVt0bZw==} + dependencies: + tslib: 2.4.1 + dev: false + /autoprefixer/6.7.7: resolution: {integrity: sha512-WKExI/eSGgGAkWAO+wMVdFObZV7hQen54UpD1kCCTN3tvlL3W1jL4+lPP/M7MwoP7Q4RHzKtO3JQ4HxYEcd+xQ==} dependencies: @@ -3253,7 +3302,7 @@ packages: /axios/0.24.0: resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} dependencies: - follow-redirects: 1.15.2_debug@4.3.4 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: false @@ -3261,7 +3310,7 @@ packages: /axios/0.25.0_debug@4.3.4: resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} dependencies: - follow-redirects: 1.15.2_debug@4.3.4 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: true @@ -3269,7 +3318,17 @@ packages: /axios/1.2.2: resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} dependencies: - follow-redirects: 1.15.2_debug@4.3.4 + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /axios/1.3.2: + resolution: {integrity: sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==} + dependencies: + follow-redirects: 1.15.2 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -4102,7 +4161,7 @@ packages: resolution: {integrity: sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==} dependencies: inflation: 2.0.0 - qs: 6.10.4 + qs: 6.11.0 raw-body: 2.5.1 type-is: 1.6.18 dev: false @@ -4111,7 +4170,7 @@ packages: resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} dependencies: inflation: 2.0.0 - qs: 6.10.4 + qs: 6.11.0 raw-body: 2.5.1 type-is: 1.6.18 dev: false @@ -5232,6 +5291,13 @@ packages: engines: {node: '>=8'} dev: false + /dezalgo/1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + dev: false + /diff/4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -6213,7 +6279,7 @@ packages: readable-stream: 2.3.7 dev: false - /follow-redirects/1.15.2_debug@4.3.4: + /follow-redirects/1.15.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} peerDependencies: @@ -6221,8 +6287,6 @@ packages: peerDependenciesMeta: debug: optional: true - dependencies: - debug: 4.3.4 /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -6282,6 +6346,15 @@ packages: dependencies: fetch-blob: 3.2.0 + /formidable/2.1.1: + resolution: {integrity: sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==} + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.11.0 + dev: false + /fragment-cache/0.2.1: resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} engines: {node: '>=0.10.0'} @@ -6932,6 +7005,11 @@ packages: hasBin: true dev: false + /hexoid/1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + dev: false + /highlight.js/10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: false @@ -7999,6 +8077,17 @@ packages: engines: {node: '>=0.10.0'} dev: false + /koa-body/6.0.1: + resolution: {integrity: sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==} + dependencies: + '@types/co-body': 6.1.0 + '@types/formidable': 2.0.5 + '@types/koa': 2.13.5 + co-body: 6.1.0 + formidable: 2.1.1 + zod: 3.20.3 + dev: false + /koa-bodyparser/4.3.0: resolution: {integrity: sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==} engines: {node: '>=8.0.0'} @@ -8672,30 +8761,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /megalodon/5.1.1: - resolution: {integrity: sha512-zsYzzmogmk9lnXzGk3kKv58LUmZVFMebiya/1CZqZYnBVxq18Ep8l1AU41o+INANMqYxG+hAQvJhE+Z5dcUabQ==} - engines: {node: '>=15.0.0'} - dependencies: - '@types/oauth': 0.9.1 - '@types/ws': 8.5.4 - axios: 1.2.2 - dayjs: 1.11.7 - form-data: 4.0.0 - https-proxy-agent: 5.0.1 - oauth: 0.10.0 - object-assign-deep: 0.4.0 - parse-link-header: 2.0.0 - socks-proxy-agent: 7.0.0 - typescript: 4.9.4 - uuid: 9.0.0 - ws: 8.12.0 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - dev: false - /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -10473,6 +10538,14 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 + dev: true + + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false /qs/6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} @@ -13230,6 +13303,10 @@ packages: resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} dev: false + /zod/3.20.3: + resolution: {integrity: sha512-+MLeeUcLTlnzVo5xDn9+LVN9oX4esvgZ7qfZczBN+YVUvZBafIrPPVyG2WdjMWU2Qkb2ZAh2M8lpqf1wIoGqJQ==} + dev: false + github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c: resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/0380d12c8e736788ea7f4e6e985175521ea7b23c} name: browser-image-resizer From 6bbc66938284dd0e1220272a4b10e39f1d895703 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Thu, 9 Feb 2023 23:32:40 +0100 Subject: [PATCH 004/231] making build work --- packages/backend/src/server/api/stream/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index aeeb54606..5be8ab109 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -152,8 +152,7 @@ export default class Connection { } catch (e) { return; } - const simpleObj = objs[0]; - + const simpleObj = objs[0]; if (simpleObj.stream) { // is Mastodon Compatible From d4c486e590787ade703725e1d06fe362527f5b1a Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:15:26 +0100 Subject: [PATCH 005/231] update pnpm locks --- pnpm-lock.yaml | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d5858f94..476fd2fbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -236,6 +236,7 @@ importers: '@tensorflow/tfjs': 4.2.0_seedrandom@3.0.5 ajv: 8.11.2 archiver: 5.3.1 + autobind-decorator: 2.4.0 autolinker: 4.0.0 autwh: 0.1.0 aws-sdk: 2.1277.0 @@ -371,7 +372,6 @@ importers: '@types/web-push': 3.3.2 '@types/websocket': 1.0.5 '@types/ws': 8.5.3 - autobind-decorator: 2.4.0 cross-env: 7.0.3 eslint: 8.31.0 execa: 6.1.0 @@ -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_debug@4.3.4 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: true @@ -6309,19 +6309,6 @@ packages: peerDependenciesMeta: debug: optional: true - dev: false - - /follow-redirects/1.15.2_debug@4.3.4: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dependencies: - debug: 4.3.4 - dev: true /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} From bc75b2664e582157d1c39c1f36d12f94dd4a9ca6 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:23:44 +0100 Subject: [PATCH 006/231] fix import --- 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 4d7259b07..47f7afe42 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -21,7 +21,7 @@ import { publishMainStream } from "@/services/stream.js"; import * as Acct from "@/misc/acct.js"; import { envOption } from "@/env.js"; const { koaBody } = require('koa-body'); -import megalodon, { MegalodonInterface } from 'megalodon'; +import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; import wellKnown from "./well-known.js"; From cf13cf44e226f4755bfb9c60b8805fa89373f459 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:27:57 +0100 Subject: [PATCH 007/231] move get-unsecure --- .../src/server/api/endpoints/i/{ => registry}/get-unsecure.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/backend/src/server/api/endpoints/i/{ => registry}/get-unsecure.ts (100%) diff --git a/packages/backend/src/server/api/endpoints/i/get-unsecure.ts b/packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts similarity index 100% rename from packages/backend/src/server/api/endpoints/i/get-unsecure.ts rename to packages/backend/src/server/api/endpoints/i/registry/get-unsecure.ts From fc8d328018d179ced62d0d8a1d214bc548682ee7 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:32:30 +0100 Subject: [PATCH 008/231] change imports --- .../src/server/api/endpoints/i/registry/get-unsecure.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 eef7f5eca..40065c83e 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 { Items } from "@/"; +import { ApiError } from "../../../error.js"; +import define from "../../../define.js"; +import { RegistryItems } from "../../../../../models/index.js"; export const meta = { requireCredential: true, @@ -34,7 +34,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { if (ps.key !== "reactions") return; - const query = Items.createQueryBuilder("item") + const query = RegistryItems.createQueryBuilder("item") .where("item.domain IS NULL") .andWhere("item.userId = :userId", { userId: user.id }) .andWhere("item.key = :key", { key: ps.key }) From 77e836797bac5c8fd04c620e42c9a001821147b1 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:34:24 +0100 Subject: [PATCH 009/231] change method name of masto api --- .../backend/src/server/api/mastodon/endpoints/notifications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index 638f0d2d4..59869da06 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -8,7 +8,7 @@ function toLimitToInt(q: any) { return q } -export function apiNotificationMastodon(router: Router): void { +export function apiNotificationsMastodon(router: Router): void { router.get('/v1/notifications', koaBody(), async (ctx) => { const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; From 96933b696a84b0bc5b7ff6c1aead13efe60f0d6a Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:38:39 +0100 Subject: [PATCH 010/231] use ES import in index --- 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 47f7afe42..ea4740cba 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"; -const { koaBody } = require('koa-body'); +import { koaBody } from 'koa-body'; import megalodon, { MegalodonInterface } from '@cutls/megalodon'; import activityPub from "./activitypub.js"; import nodeinfo from "./nodeinfo.js"; From 09e18eb312942e3e5c6139f8067ce135d6b5e0fc Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:54:10 +0100 Subject: [PATCH 011/231] shrugs --- packages/backend/src/server/api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index da98a9df1..499ca8767 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -32,7 +32,7 @@ app.use( // No caching app.use(async (ctx, next) => { ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); - await next(); + //await next(); }); app.use( From 0cf351b3da396fe53600127aed36746773aada2f Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 00:59:16 +0100 Subject: [PATCH 012/231] Revert "shrugs" This reverts commit 5a3f84a93470b5c6e5f996bc062e1f42efb69729. --- packages/backend/src/server/api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 499ca8767..da98a9df1 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -32,7 +32,7 @@ app.use( // No caching app.use(async (ctx, next) => { ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); - //await next(); + await next(); }); app.use( From db14e343d58e8cbb55c9a138b7009915ea2e686a Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 01:05:49 +0100 Subject: [PATCH 013/231] yeet koabody --- .../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 1b55a5fbd..61d4da8a8 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', 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', 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 5f5756077..ff8b8a518 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', koaBody(), 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); diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index 3c66362dd..810b8be11 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', 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', 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', 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', 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', 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..625ff386c 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', 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..dce3ff57c 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', 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..8dc4ba5f7 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', 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', 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', 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 ea4740cba..02a5d8c76 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.get("/oauth/token", koaBody(), async (ctx) => { +router.get("/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; From d72da65c2ceb460dad6e57b9885d43f09071dc08 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 01:11:11 +0100 Subject: [PATCH 014/231] lower build targets to support mobile ui stuff, eh --- packages/client/vite.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts index dfc6a3b66..7644ccaa0 100644 --- a/packages/client/vite.config.ts +++ b/packages/client/vite.config.ts @@ -46,10 +46,10 @@ export default defineConfig(({ command, mode }) => { build: { target: [ - 'chrome108', - 'firefox109', - 'safari16', - 'es2022', + 'chrome87', + 'firefox78', + 'safari14', + 'es2017', ], manifest: 'manifest.json', rollupOptions: { From 3cfb6675e7179797332b0a50a366dc4f42228f19 Mon Sep 17 00:00:00 2001 From: GeopJr Date: Fri, 10 Feb 2023 02:58:52 +0000 Subject: [PATCH 015/231] fix: some Masotdon API compat issues (#9592) Co-authored-by: GeopJr Co-committed-by: GeopJr --- .../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 | 3 +-- .../src/server/api/mastodon/endpoints/status.ts | 6 +++--- packages/backend/src/server/index.ts | 2 +- 7 files changed, 17 insertions(+), 18 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..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', 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', 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/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts index ff8b8a518..f396267c5 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({ 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/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index 810b8be11..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', 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', 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', 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', 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', 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 625ff386c..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', 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', 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', 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', 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 dce3ff57c..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', 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 8dc4ba5f7..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', 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', 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', 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); diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 02a5d8c76..6ade50d18 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.get("/oauth/token", 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; From cdc210f0f0f7511e3ac8d78de09cc6638dbeb234 Mon Sep 17 00:00:00 2001 From: Kio-td Date: Fri, 10 Feb 2023 00:19:47 -0500 Subject: [PATCH 016/231] Close #9473 --- packages/backend/src/remote/activitypub/models/note.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 72953c5bf..0b6dd3eed 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -123,7 +123,8 @@ export async function createNote( // Skip if author is suspended. if (actor.isSuspended) { - throw new Error("actor has been suspended"); + logger.debug(`User ${`${actor.usernameLower}@${actor.host}`} suspended; discarding.`) + return null; } const noteAudience = await parseAudience(actor, note.to, note.cc); From 79a3aadc60846b0d4a7797b741410c4ea1a79989 Mon Sep 17 00:00:00 2001 From: Kio-td Date: Fri, 10 Feb 2023 00:38:23 -0500 Subject: [PATCH 017/231] Clean up bad coding practices --- packages/backend/src/remote/activitypub/models/note.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 0b6dd3eed..afb3af6cb 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -123,7 +123,7 @@ 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; } From a0e4927f4c515e7f79ea105663b31a9fc4d0e189 Mon Sep 17 00:00:00 2001 From: Cleo Date: Fri, 10 Feb 2023 07:56:46 +0000 Subject: [PATCH 018/231] revert da2368bf7af1d4e3a7f2fb4b8703b68a1f8c8ceb revert fix: some Masotdon API compat issues (#9592) Co-authored-by: GeopJr Co-committed-by: GeopJr --- .../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 | 3 ++- .../src/server/api/mastodon/endpoints/status.ts | 6 +++--- packages/backend/src/server/index.ts | 2 +- 7 files changed, 18 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 65caf7168..61d4da8a8 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/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts index f396267c5..ff8b8a518 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', koaBody({ 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); diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index b4f67cf1e..810b8be11 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({ 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 +20,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 +35,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 +50,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 +65,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..625ff386c 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', 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', 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', 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({ 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/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index dcd5be461..dce3ff57c 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,10 +1,11 @@ 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', 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 cef966e47..8dc4ba5f7 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({ 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 +284,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 +310,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/index.ts b/packages/backend/src/server/index.ts index 6ade50d18..02a5d8c76 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", async (ctx) => { const body: any = ctx.request.body; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const generator = (megalodon as any).default; From b2ebf7c7fac46a6c696a7a4c170e9e354bb008e4 Mon Sep 17 00:00:00 2001 From: Cleo Date: Fri, 10 Feb 2023 08:01:38 +0000 Subject: [PATCH 019/231] =?UTF-8?q?=E2=80=9Epackages/backend/src/server/in?= =?UTF-8?q?dex.ts=E2=80=9C=20=C3=A4ndern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 02a5d8c76..6ade50d18 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.get("/oauth/token", 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; From e917dc6be368078055960e29750a97263ff0c39d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 4 Feb 2023 09:10:01 +0900 Subject: [PATCH 020/231] 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 { 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 Date: Fri, 10 Feb 2023 11:14:33 -0800 Subject: [PATCH 021/231] 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 { 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 Date: Fri, 10 Feb 2023 20:40:54 +0100 Subject: [PATCH 022/231] 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 Date: Fri, 10 Feb 2023 20:45:29 +0100 Subject: [PATCH 023/231] Merge Masto Api changes Co-authored-by Natty --- .../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 Date: Fri, 10 Feb 2023 20:50:42 +0100 Subject: [PATCH 024/231] 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 { } } +function deny(): void { state = 'denied'; } From ebd53e1f90613efad3d4d46ade806eda6c51bb69 Mon Sep 17 00:00:00 2001 From: cutestnekoaqua Date: Fri, 10 Feb 2023 20:52:58 +0100 Subject: [PATCH 025/231] 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 Date: Fri, 10 Feb 2023 20:53:27 +0100 Subject: [PATCH 026/231] 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 Date: Fri, 10 Feb 2023 21:16:55 +0100 Subject: [PATCH 027/231] 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 Date: Fri, 10 Feb 2023 22:30:19 +0100 Subject: [PATCH 028/231] 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 Date: Fri, 10 Feb 2023 23:00:15 +0100 Subject: [PATCH 029/231] 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 Date: Fri, 10 Feb 2023 23:15:34 +0100 Subject: [PATCH 030/231] migrate middleware usage Co-authored-by Natty --- .../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 Date: Fri, 10 Feb 2023 23:17:29 +0100 Subject: [PATCH 031/231] 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 Date: Fri, 10 Feb 2023 23:29:29 +0100 Subject: [PATCH 032/231] 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 Date: Fri, 10 Feb 2023 22:46:08 +0000 Subject: [PATCH 033/231] 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 @@ - + {{ instance.host }} @@ -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'; +} From 9f3c9f886896a193880873f297dbd6f05d112a02 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 11 Feb 2023 17:14:50 -0800 Subject: [PATCH 077/231] Formatting --- .gitignore | 2 + .../src/components/global/MkPageHeader.vue | 659 +++++++++--------- 2 files changed, 331 insertions(+), 330 deletions(-) diff --git a/.gitignore b/.gitignore index 135bf9660..52139614c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,8 @@ api-docs.json files ormconfig.json packages/backend/assets/instance.css +packages/backend/assets/sounds/None.mp3 + # blender backups *.blend1 diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue index c6568bbff..f37b2f43f 100644 --- a/packages/client/src/components/global/MkPageHeader.vue +++ b/packages/client/src/components/global/MkPageHeader.vue @@ -7,7 +7,7 @@
- +
{{ metadata.title }}
@@ -31,284 +31,226 @@
- + - + From 8c45c5fc5b933962ebf9b420c42a68656c241d6d Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 11 Feb 2023 17:16:55 -0800 Subject: [PATCH 078/231] chore: tag dev1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18df3e5b2..4a5222f61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "13.2.0-dev", + "version": "13.2.0-dev1", "codename": "aqua", "repository": { "type": "git", From 7091f889ee27c44f2680fc4518917656729591af Mon Sep 17 00:00:00 2001 From: Kaity A Date: Sun, 12 Feb 2023 01:19:43 +0000 Subject: [PATCH 079/231] Enable reply update/display in detailed view. (#9606) This PR establishes a new replied note stream update for subscribed notes, which gets fired off whenever a note receives a reply and the user is subscribed to the note for updates. It specifically does not provide note details as part of the update, just the note id of the reply, so that they must go and retrieve the note and be subject to the proper permission and visibility checks. The detailed note component has then been updated to watch for the replied notification so it can add new replies to the thread as they are created. This allows both seeing new replies while on the page, and also to see your own replies appear after you post them without having to reload the page. This PR relies on https://codeberg.org/calckey/calckey.js/pulls/2 to add the replied type to the calkey.js module. Co-authored-by: Kaity A Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9606 Co-authored-by: Kaity A Co-committed-by: Kaity A --- .../backend/src/server/api/stream/types.ts | 3 +++ packages/backend/src/services/note/create.ts | 8 +++++- .../client/src/components/MkNoteDetailed.vue | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 837f42c87..b35c599bd 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -135,6 +135,9 @@ export interface NoteStreamTypes { reaction: string; userId: User["id"]; }; + replied: { + id: Note["id"]; + }; } type NoteStreamEventTypes = { [key in keyof NoteStreamTypes]: { diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 210ea7771..b37b160fb 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -1,6 +1,6 @@ import * as mfm from "mfm-js"; import es from "../../db/elasticsearch.js"; -import { publishMainStream, publishNotesStream } from "@/services/stream.js"; +import { publishMainStream, publishNotesStream, publishNoteStream } from "@/services/stream.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js"; import renderNote from "@/remote/activitypub/renderer/note.js"; import renderCreate from "@/remote/activitypub/renderer/create.js"; @@ -430,6 +430,12 @@ export default async ( } publishNotesStream(note); + if (note.replyId != null) { + // Only provide the reply note id here as the recipient may not be authorized to see the note. + publishNoteStream(note.replyId, "replied", { + id: note.id, + }); + } const webhooks = await getActiveWebhooks().then((webhooks) => webhooks.filter((x) => x.userId === user.id && x.on.includes("note")), diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index dd6187d7e..fab06f1ab 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -142,6 +142,8 @@ import { i18n } from '@/i18n'; import { getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; import { deepClone } from '@/scripts/clone'; +import { stream } from '@/stream'; +import { NoteUpdatedEvent } from 'calckey-js/built/streaming.types'; const router = useRouter(); @@ -302,6 +304,31 @@ if (appearNote.replyId) { conversation.value = res.reverse(); }); } + +function onNoteReplied(noteData: NoteUpdatedEvent): void { + const { type, id, body } = noteData; + if (type === 'replied' && id === appearNote.id) { + const { id: createdId } = body; + + os.api('notes/show', { + noteId: createdId, + }).then(note => { + if (note.replyId === appearNote.id) { + replies.value.unshift(note); + directReplies.value.unshift(note); + } + }); + } + +} + +onMounted(() => { + stream.on("noteUpdated", onNoteReplied); +}); + +onUnmounted(() => { + stream.off("noteUpdated", onNoteReplied); +}); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 0784b68de..a0c263572 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -545,6 +545,21 @@ export async function selectUser() { }); } +export async function selectInstance(): Promise { + return new Promise((resolve, reject) => { + popup( + defineAsyncComponent(() => import("@/components/MkInstanceSelectDialog.vue")), + {}, + { + ok: (instance) => { + resolve(instance); + }, + }, + "closed", + ); + }); +} + export async function selectDriveFile(multiple: boolean) { return new Promise((resolve, reject) => { popup( diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue index 794b74370..337816e00 100644 --- a/packages/client/src/pages/my-antennas/create.vue +++ b/packages/client/src/pages/my-antennas/create.vue @@ -5,7 +5,6 @@ diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index e8e726ab3..8ab714af3 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -223,6 +223,12 @@ export function getNoteMenu(props: { }); } + function showReactions(): void { + os.popup(defineAsyncComponent(() => import('@/components/MkReactedUsersDialog.vue')), { + noteId: appearNote.id, + }, {}, 'closed'); + } + async function translate(): Promise { if (props.translation.value != null) return; props.translating.value = true; @@ -252,6 +258,11 @@ export function getNoteMenu(props: { null, ] : []), + { + icon: "ph-smiley-bold ph-lg", + text: i18n.ts.reaction, + action: showReactions, + }, { icon: "ph-clipboard-text-bold ph-lg", text: i18n.ts.copyContent, From 72319e8f1fcf44ba15bccce4b977ee95641422ec Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sun, 12 Feb 2023 20:15:33 -0800 Subject: [PATCH 097/231] dev6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8da6c5a7..bc3c0ffd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "13.2.0-dev4", + "version": "13.2.0-dev6", "codename": "aqua", "repository": { "type": "git", From 428dac79c8e144f1869bdfe1b514b4fd231d53f7 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sun, 12 Feb 2023 20:18:45 -0800 Subject: [PATCH 098/231] fix: :bug: first user gets admin Closes #9620 Co-authored-by: @Johann150 --- packages/backend/src/server/api/common/signup.ts | 1 + .../backend/src/server/api/endpoints/admin/accounts/create.ts | 1 + packages/backend/src/server/api/endpoints/meta.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts index 7ae9e10fb..bb178506b 100644 --- a/packages/backend/src/server/api/common/signup.ts +++ b/packages/backend/src/server/api/common/signup.ts @@ -107,6 +107,7 @@ export async function signup(opts: { isAdmin: (await Users.countBy({ host: IsNull(), + isAdmin: true, })) === 0, }), ); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 11ef2273c..2e035d169 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -35,6 +35,7 @@ export default define(meta, paramDef, async (ps, _me) => { const noUsers = (await Users.countBy({ host: IsNull(), + isAdmin: true, })) === 0; if (!(noUsers || me?.isAdmin)) throw new Error("access denied"); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 005d0800a..476a0c29f 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -489,6 +489,7 @@ export default define(meta, paramDef, async (ps, me) => { requireSetup: (await Users.countBy({ host: IsNull(), + isAdmin: true, })) === 0, } : {}), From 2bd38a64f0f93127a49e1381efd021b4322c4a84 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sun, 12 Feb 2023 20:25:23 -0800 Subject: [PATCH 099/231] feat: :sparkles: add position, scale , fg, and bg MFM from v13 --- packages/client/src/components/mfm.ts | 29 ++++++++++++++++++++ packages/client/src/scripts/get-note-menu.ts | 13 +++++++-- packages/client/src/scripts/mfm-tags.ts | 4 +++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index ddfcc3de1..f2905ec35 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -254,6 +254,35 @@ export default defineComponent({ style = `transform: ${rotate}(${degrees}deg); transform-origin: center center;`; break; } + case "position": { + if (!defaultStore.state.advancedMfm) break; + const x = parseFloat(token.props.args.x ?? "0"); + const y = parseFloat(token.props.args.y ?? "0"); + style = `transform: translateX(${x}em) translateY(${y}em);`; + break; + } + case "scale": { + if (!defaultStore.state.advancedMfm) { + style = ""; + break; + } + const x = Math.min(parseFloat(token.props.args.x ?? "1"), 5); + const y = Math.min(parseFloat(token.props.args.y ?? "1"), 5); + style = `transform: scale(${x}, ${y});`; + break; + } + case "fg": { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00"; + style = `color: #${color};`; + break; + } + case "bg": { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00"; + style = `background-color: #${color};`; + break; + } } if (style == null) { return h("span", {}, [ diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index 8ab714af3..eb8006c1c 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -224,9 +224,16 @@ export function getNoteMenu(props: { } function showReactions(): void { - os.popup(defineAsyncComponent(() => import('@/components/MkReactedUsersDialog.vue')), { - noteId: appearNote.id, - }, {}, 'closed'); + os.popup( + defineAsyncComponent( + () => import("@/components/MkReactedUsersDialog.vue"), + ), + { + noteId: appearNote.id, + }, + {}, + "closed", + ); } async function translate(): Promise { diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 51e1fc4e6..b39c8b37b 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -10,6 +10,10 @@ export const MFM_TAGS = [ "x2", "x3", "x4", + "scale", + "position", + "fg", + "bg", "font", "blur", "rainbow", From c32128c2da9590dafb8122ddb6bf085b676f7664 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sun, 12 Feb 2023 20:25:45 -0800 Subject: [PATCH 100/231] dev7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc3c0ffd6..da1e886ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "13.2.0-dev6", + "version": "13.2.0-dev7", "codename": "aqua", "repository": { "type": "git", From 8d052f8e0ef71aa5f36b8d7334f2a2e00b908ae1 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sun, 12 Feb 2023 20:29:34 -0800 Subject: [PATCH 101/231] remove defaultStore check in MFM --- packages/client/src/components/mfm.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index f2905ec35..6a8f81e40 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -255,17 +255,12 @@ export default defineComponent({ break; } case "position": { - if (!defaultStore.state.advancedMfm) break; const x = parseFloat(token.props.args.x ?? "0"); const y = parseFloat(token.props.args.y ?? "0"); style = `transform: translateX(${x}em) translateY(${y}em);`; break; } case "scale": { - if (!defaultStore.state.advancedMfm) { - style = ""; - break; - } const x = Math.min(parseFloat(token.props.args.x ?? "1"), 5); const y = Math.min(parseFloat(token.props.args.y ?? "1"), 5); style = `transform: scale(${x}, ${y});`; From 51ba8304821700dfc5b497160c0224c5ab1c9ffe Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sun, 12 Feb 2023 20:46:56 -0800 Subject: [PATCH 102/231] fix --- packages/client/src/components/MkReactedUsersDialog.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/MkReactedUsersDialog.vue b/packages/client/src/components/MkReactedUsersDialog.vue index f1427db7b..5f9dfd7d9 100644 --- a/packages/client/src/components/MkReactedUsersDialog.vue +++ b/packages/client/src/components/MkReactedUsersDialog.vue @@ -17,7 +17,11 @@ - + - + diff --git a/packages/client/src/widgets/user-list.vue b/packages/client/src/widgets/user-list.vue index 4173f5c36..6ee6dc05a 100644 --- a/packages/client/src/widgets/user-list.vue +++ b/packages/client/src/widgets/user-list.vue @@ -2,7 +2,7 @@ - +
{{ i18n.ts._widgets._userList.chooseList }} @@ -14,7 +14,7 @@
- + - + diff --git a/packages/client/src/components/MkPostFormDialog.vue b/packages/client/src/components/MkPostFormDialog.vue index 6dabb1db1..479d5dd06 100644 --- a/packages/client/src/components/MkPostFormDialog.vue +++ b/packages/client/src/components/MkPostFormDialog.vue @@ -1,19 +1,46 @@ + + + + - + const props = defineProps<{ + reply?: misskey.entities.Note; + renote?: misskey.entities.Note; + channel?: any; // TODO + mention?: misskey.entities.User; + specified?: misskey.entities.User; + initialText?: string; + initialVisibility?: typeof misskey.noteVisibilities; + initialFiles?: misskey.entities.DriveFile[]; + initialLocalOnly?: boolean; + initialVisibleUsers?: misskey.entities.User[]; + initialNote?: misskey.entities.Note; + instant?: boolean; + fixed?: boolean; + autofocus?: boolean; + }>(); + + const emit = defineEmits<{ + (ev: 'closed'): void; + }>(); + + let modal = $shallowRef>(); + let form = $shallowRef>(); + + function onPosted() { + modal.close({ + useSendAnimation: true, + }); + } + + function onModalClosed() { + emit('closed'); + } + From 378be06d96f29638af1ec978e1df57a80b595656 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 15:34:41 -0800 Subject: [PATCH 158/231] fix: dialog --- packages/client/src/components/MkDialog.vue | 189 ++++++++++---------- 1 file changed, 97 insertions(+), 92 deletions(-) diff --git a/packages/client/src/components/MkDialog.vue b/packages/client/src/components/MkDialog.vue index e01b0a332..ec55d15f7 100644 --- a/packages/client/src/components/MkDialog.vue +++ b/packages/client/src/components/MkDialog.vue @@ -1,51 +1,51 @@ - From 4e5e970036d7197b3bf43b4d7986381b641c4d94 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 15:48:46 -0800 Subject: [PATCH 159/231] fix? --- packages/client/src/components/MkModal.vue | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue index 4048177a6..840a1aee2 100644 --- a/packages/client/src/components/MkModal.vue +++ b/packages/client/src/components/MkModal.vue @@ -414,6 +414,17 @@ defineExpose({ -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%); mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%); } + + > ::v-deep(*) { + margin: auto; + } + + &.top { + > ::v-deep(*) { + margin-top: 0; + } + } + } } @@ -441,6 +452,10 @@ defineExpose({ left: 0; right: 0; margin: auto; + + > ::v-deep(*) { + margin: auto; + } } } } From 6ca9860a72a0e453bf4f9f7f0b1438baf4023ed8 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 15:53:19 -0800 Subject: [PATCH 160/231] formatting --- .../src/components/MkPostFormDialog.vue | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/client/src/components/MkPostFormDialog.vue b/packages/client/src/components/MkPostFormDialog.vue index 479d5dd06..147ba9542 100644 --- a/packages/client/src/components/MkPostFormDialog.vue +++ b/packages/client/src/components/MkPostFormDialog.vue @@ -2,45 +2,45 @@ - + - +function onModalClosed() { + emit('closed'); +} + From 3e78ff2439a2ed5ec939cb0ff9b647c974d1eab5 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 16:16:24 -0800 Subject: [PATCH 161/231] fix?? --- packages/client/src/components/MkModal.vue | 14 +++---- .../client/src/components/MkModalWindow.vue | 38 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue index 840a1aee2..b88dee380 100644 --- a/packages/client/src/components/MkModal.vue +++ b/packages/client/src/components/MkModal.vue @@ -7,9 +7,9 @@ :leave-to-class="$style['transition_' + transitionName + '_leaveTo']" :duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened" > -
+
-
+
@@ -33,7 +33,7 @@ function getFixedContainer(el: Element | null): Element | null { } } -type ModalTypes = 'popup' | 'dialog' | 'drawer'; +type ModalTypes = 'popup' | 'dialog' | 'dialog:top' | 'drawer'; const props = withDefaults(defineProps<{ manualShowing?: boolean | null; @@ -194,21 +194,21 @@ const align = () => { } } else { // 画面から横にはみ出る場合 - if (left + width - window.pageXOffset > window.innerWidth) { - left = window.innerWidth - width + window.pageXOffset - 1; + if (left + width - window.scrollX > window.innerWidth) { + left = window.innerWidth - width + window.scrollX - 1; } const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset); const upperSpace = (srcRect.top - MARGIN); // 画面から縦にはみ出る場合 - if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) { + if (top + height - window.scrollY > (window.innerHeight - MARGIN)) { if (props.noOverlap && props.anchor.x === 'center') { if (underSpace >= (upperSpace / 3)) { maxHeight = underSpace; } else { maxHeight = upperSpace; - top = window.pageYOffset + ((upperSpace + MARGIN) - height); + top = window.scrollY + ((upperSpace + MARGIN) - height); } } else { top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1; diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue index 666382466..2765a9927 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -1,19 +1,19 @@ From d5e28cb129def31bca0b75980b38ee60b775ed2e Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 19:48:25 -0800 Subject: [PATCH 165/231] fix --- packages/client/src/components/MkDialog.vue | 2 +- .../src/components/global/MkLoading.vue | 8 +++ packages/client/src/os.ts | 51 ++++++++++++------- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/packages/client/src/components/MkDialog.vue b/packages/client/src/components/MkDialog.vue index ec55d15f7..1bcfa81a5 100644 --- a/packages/client/src/components/MkDialog.vue +++ b/packages/client/src/components/MkDialog.vue @@ -9,7 +9,7 @@ - +
diff --git a/packages/client/src/components/global/MkLoading.vue b/packages/client/src/components/global/MkLoading.vue index 362484f5f..dc61e577b 100644 --- a/packages/client/src/components/global/MkLoading.vue +++ b/packages/client/src/components/global/MkLoading.vue @@ -15,10 +15,12 @@ const props = withDefaults(defineProps<{ inline?: boolean; colored?: boolean; mini?: boolean; + em?: boolean; }>(), { inline: false, colored: true, mini: false, + em: false, }); @@ -70,6 +72,12 @@ const props = withDefaults(defineProps<{ padding: 16px; --size: 32px; } + &.em { + display: inline-block; + vertical-align: middle; + padding: 0; + --size: 1em; + } } .container { diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index d3516bf4c..0e6bdf318 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -7,8 +7,8 @@ import * as Misskey from "calckey-js"; import { apiUrl, url } from "@/config"; import MkPostFormDialog from "@/components/MkPostFormDialog.vue"; import MkWaitingDialog from "@/components/MkWaitingDialog.vue"; -import MkToast from '@/components/MkToast.vue'; -import MkDialog from '@/components/MkDialog.vue'; +import MkToast from "@/components/MkToast.vue"; +import MkDialog from "@/components/MkDialog.vue"; import { MenuItem } from "@/types/menu"; import { $i } from "@/account"; @@ -248,41 +248,56 @@ export function modalPageWindow(path: string) { } export function toast(message: string) { - popup(MkToast, { - message, - }, {}, 'closed'); + popup( + MkToast, + { + message, + }, + {}, + "closed", + ); } export function alert(props: { - type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'; + type?: "error" | "info" | "success" | "warning" | "waiting" | "question"; title?: string | null; text?: string | null; }): Promise { return new Promise((resolve, reject) => { - popup(MkDialog, props, { - done: result => { - resolve(); + popup( + MkDialog, + props, + { + done: (result) => { + resolve(); + }, }, - }, 'closed'); + "closed", + ); }); } export function confirm(props: { - type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'; + type: "error" | "info" | "success" | "warning" | "waiting" | "question"; title?: string | null; text?: string | null; okText?: string; cancelText?: string; }): Promise<{ canceled: boolean }> { return new Promise((resolve, reject) => { - popup(MkDialog, { - ...props, - showCancelButton: true, - }, { - done: result => { - resolve(result ? result : { canceled: true }); + popup( + MkDialog, + { + ...props, + showCancelButton: true, }, - }, 'closed'); + { + done: (result) => { + resolve(result ? result : { canceled: true }); + }, + }, + "closed", + ); }); } From 20cc13434cffd831dd9aded85cfc05e6981dbd42 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 19:55:18 -0800 Subject: [PATCH 166/231] fix --- .../client/src/components/MkWaitingDialog.vue | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/client/src/components/MkWaitingDialog.vue b/packages/client/src/components/MkWaitingDialog.vue index dfc5115ca..00d34e949 100644 --- a/packages/client/src/components/MkWaitingDialog.vue +++ b/packages/client/src/components/MkWaitingDialog.vue @@ -1,18 +1,18 @@ - From 8008721c3ef8fe433cbd775ad5a5562e862609cd Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 19:58:42 -0800 Subject: [PATCH 167/231] fix MkUpdated --- packages/client/src/components/MkUpdated.vue | 42 ++++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/client/src/components/MkUpdated.vue b/packages/client/src/components/MkUpdated.vue index 6f5ca6618..d1df6af4c 100644 --- a/packages/client/src/components/MkUpdated.vue +++ b/packages/client/src/components/MkUpdated.vue @@ -1,15 +1,15 @@ @@ -39,7 +39,8 @@ console.log(data); From b43ee5481896a0da7cf25fb4720bf26abfe56600 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 20:01:00 -0800 Subject: [PATCH 168/231] style --- packages/client/src/components/MkModal.vue | 4 ++-- .../client/src/pages/admin/overview.federation.vue | 4 ++-- packages/client/src/pages/admin/overview.stats.vue | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue index b88dee380..80cfaf8da 100644 --- a/packages/client/src/components/MkModal.vue +++ b/packages/client/src/components/MkModal.vue @@ -341,12 +341,12 @@ defineExpose({ .transition_modal-popup_enterActive, .transition_modal-popup_leaveActive { > .bg { - transition: opacity 0.1s !important; + transition: opacity 0.2s !important; } > .content { transform-origin: var(--transformOrigin); - transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important; + transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important; } } .transition_modal-popup_enterFrom, diff --git a/packages/client/src/pages/admin/overview.federation.vue b/packages/client/src/pages/admin/overview.federation.vue index 961837b22..f47632d40 100644 --- a/packages/client/src/pages/admin/overview.federation.vue +++ b/packages/client/src/pages/admin/overview.federation.vue @@ -147,14 +147,14 @@ onMounted(async () => { &.sub { > .icon { - background: #907aa955; + background: #907aa922; color: #c4a7e7; } } &.pub { > .icon { - background: #56949f55; + background: #56949f22; color: #9ccfd8; } } diff --git a/packages/client/src/pages/admin/overview.stats.vue b/packages/client/src/pages/admin/overview.stats.vue index 0868cba3d..91ab73783 100644 --- a/packages/client/src/pages/admin/overview.stats.vue +++ b/packages/client/src/pages/admin/overview.stats.vue @@ -106,35 +106,35 @@ onMounted(async () => { &.users { > .icon { - background: #56949f55; + background: #56949f22; color: #9ccfd8; } } &.notes { > .icon { - background: #28698355; + background: #28698322; color: #31748f; } } &.instances { > .icon { - background: #d7827e55; + background: #d7827e22; color: #ebbcba; } } &.emojis { > .icon { - background: #ea9d3455; + background: #ea9d3422; color: #f6c177; } } &.online { > .icon { - background: #907aa955; + background: #907aa922; color: #c4a7e7; } } From d33570ad8f5ae2e659b7de5883316ea1cbf5bcf6 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 20:02:58 -0800 Subject: [PATCH 169/231] fix broken style --- packages/client/src/components/MkUpdated.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/MkUpdated.vue b/packages/client/src/components/MkUpdated.vue index d1df6af4c..a3fc37751 100644 --- a/packages/client/src/components/MkUpdated.vue +++ b/packages/client/src/components/MkUpdated.vue @@ -63,7 +63,7 @@ console.log(data); margin: 8px 0 0 0; } -> .releaseNotes { +.releaseNotes { > img { border-radius: 10px; } From 5c7a9f66ebaaca00f4814809713d913324d427b5 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Sat, 18 Feb 2023 20:09:14 -0800 Subject: [PATCH 170/231] testing --- packages/client/src/components/MkUpdated.vue | 4 +++- packages/client/src/init.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/MkUpdated.vue b/packages/client/src/components/MkUpdated.vue index a3fc37751..640b722d2 100644 --- a/packages/client/src/components/MkUpdated.vue +++ b/packages/client/src/components/MkUpdated.vue @@ -15,6 +15,7 @@ - From e2ad9a94ec7079e3f0c17502cd89b1bbfe9715b2 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Wed, 22 Feb 2023 09:44:47 +0100 Subject: [PATCH 177/231] fix mastodon api stats --- .../backend/src/server/api/mastodon/endpoints/meta.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 5fba6f8a6..a19acbaa1 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -1,9 +1,13 @@ import { Entity } from "@calckey/megalodon"; import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Users, Notes } from "@/models/index.js"; +import { IsNull, MoreThan } from "typeorm"; // TODO: add calckey features export async function getInstance(response: Entity.Instance) { const meta = await fetchMeta(true); + const totalUsers = Users.count({ where: { host: IsNull() } }); + const totalStatuses = Notes.count({ where: { userHost: IsNull() } }); return { uri: response.uri, title: response.title || "", @@ -12,7 +16,11 @@ export async function getInstance(response: Entity.Instance) { email: response.email || "", version: "3.0.0 compatible (Calckey)", urls: response.urls, - stats: response.stats, + stats: { + user_count: totalUsers, + status_count: totalStatuses, + domain_count: response.stats.domain_count + }, thumbnail: response.thumbnail || "", languages: meta.langs, registrations: !meta.disableRegistration || response.registrations, From e4a276a9892937b8c8d97ff74f4023a0c4aa3aaa Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 00:31:14 +0100 Subject: [PATCH 178/231] aaa --- packages/backend/src/server/api/index.ts | 1 + .../src/server/api/mastodon/endpoints/status.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 3ea3ff67e..0f58d3acf 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -171,6 +171,7 @@ errorRouter.all("(.*)", async (ctx) => { // Register router app.use(mastoRouter.routes()); +app.use(mastoRouter.allowedMethods()); app.use(router.routes()); app.use(errorRouter.routes()); diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index a72ac2c7e..0ba1e4229 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -289,14 +289,16 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const multipartData = await ctx.file; + let multipartData = await ctx.request.files; if (!multipartData) { ctx.body = { error: "No image" }; + ctx.status = 401; return; } - const [path] = await createTemp(); - await pump(multipartData.buffer, fs.createWriteStream(path)); - const image = fs.readFileSync(path); + if ((multipartData as any).file) { + multipartData = (multipartData as any).file; + } + const image = fs.readFileSync((multipartData as any).path); const data = await client.uploadMedia(image); ctx.body = data.data; } catch (e: any) { @@ -313,6 +315,7 @@ export function apiStatusMastodon(router: Router): void { const multipartData = await ctx.file; if (!multipartData) { ctx.body = { error: "No image" }; + ctx.status = 401; return; } const [path] = await createTemp(); From 75bd0baf99193fc61a51952d38c2d8a97bee47aa Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 15:46:12 +0100 Subject: [PATCH 179/231] remove not needed middleware handlers --- .../server/api/mastodon/endpoints/status.ts | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 0ba1e4229..2ab834b64 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -9,8 +9,17 @@ import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import axios from "axios"; const pump = promisify(pipeline); +// Init multer instance +const upload = multer({ + storage: multer.diskStorage({}), + limits: { + fileSize: config.maxFileSize || 262144000, + files: 1, + }, +}); + export function apiStatusMastodon(router: Router): void { - router.post("/v1/statuses", async (ctx, reply) => { + router.post("/v1/statuses", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -52,7 +61,7 @@ export function apiStatusMastodon(router: Router): void { }); router.get<{ Params: { id: string } }>( "/v1/statuses/:id", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -68,7 +77,7 @@ export function apiStatusMastodon(router: Router): void { ); router.delete<{ Params: { id: string } }>( "/v1/statuses/:id", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -90,7 +99,7 @@ export function apiStatusMastodon(router: Router): void { } router.get<{ Params: { id: string } }>( "/v1/statuses/:id/context", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -123,7 +132,7 @@ export function apiStatusMastodon(router: Router): void { ); router.get<{ Params: { id: string } }>( "/v1/statuses/:id/reblogged_by", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -139,13 +148,13 @@ export function apiStatusMastodon(router: Router): void { ); router.get<{ Params: { id: string } }>( "/v1/statuses/:id/favourited_by", - async (ctx, reply) => { + async (ctx) => { ctx.body = []; }, ); router.post<{ Params: { id: string } }>( "/v1/statuses/:id/favourite", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -167,7 +176,7 @@ export function apiStatusMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/statuses/:id/unfavourite", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -185,7 +194,7 @@ export function apiStatusMastodon(router: Router): void { router.post<{ Params: { id: string } }>( "/v1/statuses/:id/reblog", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -202,7 +211,7 @@ export function apiStatusMastodon(router: Router): void { router.post<{ Params: { id: string } }>( "/v1/statuses/:id/unreblog", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -219,7 +228,7 @@ export function apiStatusMastodon(router: Router): void { router.post<{ Params: { id: string } }>( "/v1/statuses/:id/bookmark", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -236,7 +245,7 @@ export function apiStatusMastodon(router: Router): void { router.post<{ Params: { id: string } }>( "/v1/statuses/:id/unbookmark", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -253,7 +262,7 @@ export function apiStatusMastodon(router: Router): void { router.post<{ Params: { id: string } }>( "/v1/statuses/:id/pin", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -270,7 +279,7 @@ export function apiStatusMastodon(router: Router): void { router.post<{ Params: { id: string } }>( "/v1/statuses/:id/unpin", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -284,7 +293,7 @@ export function apiStatusMastodon(router: Router): void { } }, ); - router.post("/v1/media", async (ctx, reply) => { + router.post("/v1/media", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -307,7 +316,7 @@ export function apiStatusMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.post("/v2/media", async (ctx, reply) => { + router.post("/v2/media", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -331,7 +340,7 @@ export function apiStatusMastodon(router: Router): void { }); router.get<{ Params: { id: string } }>( "/v1/media/:id", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -347,7 +356,7 @@ export function apiStatusMastodon(router: Router): void { ); router.put<{ Params: { id: string } }>( "/v1/media/:id", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -366,7 +375,7 @@ export function apiStatusMastodon(router: Router): void { ); router.get<{ Params: { id: string } }>( "/v1/polls/:id", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -382,7 +391,7 @@ export function apiStatusMastodon(router: Router): void { ); router.post<{ Params: { id: string } }>( "/v1/polls/:id/votes", - async (ctx, reply) => { + async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); From 0ca238d6806278e26183ac46598c1036b9dd2471 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 15:55:28 +0100 Subject: [PATCH 180/231] import multer --- packages/backend/src/server/api/mastodon/endpoints/status.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 2ab834b64..89c733bd1 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -5,6 +5,7 @@ import fs from "fs"; import { pipeline } from "node:stream"; import { promisify } from "node:util"; import { createTemp } from "@/misc/create-temp.js"; +import multer from "@koa/multer"; import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import axios from "axios"; const pump = promisify(pipeline); From 83cc0c9828306813ae51a961e7a91fc690b03c86 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 15:58:54 +0100 Subject: [PATCH 181/231] import config --- packages/backend/src/server/api/mastodon/endpoints/status.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 89c733bd1..7da2a2d7b 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -5,6 +5,7 @@ import fs from "fs"; import { pipeline } from "node:stream"; import { promisify } from "node:util"; import { createTemp } from "@/misc/create-temp.js"; +import config from "@/config/index.js"; import multer from "@koa/multer"; import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import axios from "axios"; From b1d785b486be75a6a98aa2f97ea9d3b5ef400ab3 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:12:53 +0100 Subject: [PATCH 182/231] await the instance meta --- packages/backend/src/server/api/mastodon/endpoints/meta.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index a19acbaa1..7ca6625f4 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -17,8 +17,8 @@ export async function getInstance(response: Entity.Instance) { version: "3.0.0 compatible (Calckey)", urls: response.urls, stats: { - user_count: totalUsers, - status_count: totalStatuses, + user_count: (await totalUsers), + status_count: (await totalStatuses), domain_count: response.stats.domain_count }, thumbnail: response.thumbnail || "", From 273b86b9f4126620a50242da9297b84bcf9a6d6b Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:26:33 +0100 Subject: [PATCH 183/231] ?? --- packages/backend/src/server/api/mastodon/endpoints/meta.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 7ca6625f4..e5726fbae 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -89,6 +89,7 @@ export async function getInstance(response: Entity.Instance) { discoverable: false, group: false, created_at: Math.floor(new Date().getTime() / 1000), + createdAt: Math.floor(new Date().getTime() / 1000), note: "Please refer to the original instance for the actual admin contact.", url: "/", avatar: "/static-assets/badges/info.png", From 506709d2eb0b5e70dfddde5f3e8687fb3b57aa13 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:32:21 +0100 Subject: [PATCH 184/231] fixed what ever calc did here, masto app didnt --- packages/backend/src/server/api/mastodon/endpoints/meta.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index e5726fbae..809a9aa11 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -88,8 +88,8 @@ export async function getInstance(response: Entity.Instance) { bot: true, discoverable: false, group: false, - created_at: Math.floor(new Date().getTime() / 1000), - createdAt: Math.floor(new Date().getTime() / 1000), + created_at: new Date().toISOString(), + createdAt: new Date().toISOString(), note: "Please refer to the original instance for the actual admin contact.", url: "/", avatar: "/static-assets/badges/info.png", @@ -99,7 +99,7 @@ export async function getInstance(response: Entity.Instance) { followers_count: -1, following_count: 0, statuses_count: 0, - last_status_at: Math.floor(new Date().getTime() / 1000), + last_status_at: new Date().toISOString(), noindex: true, emojis: [], fields: [], From 678c3c24838d4ba7debac0b05b7b2e2e14bebbaa Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:34:16 +0100 Subject: [PATCH 185/231] lol --- packages/backend/src/server/api/mastodon/endpoints/meta.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 809a9aa11..0a4ead16f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -89,7 +89,6 @@ export async function getInstance(response: Entity.Instance) { discoverable: false, group: false, created_at: new Date().toISOString(), - createdAt: new Date().toISOString(), note: "Please refer to the original instance for the actual admin contact.", url: "/", avatar: "/static-assets/badges/info.png", From 6b97e42a7fe5f5175aa1f5a045e8e5ab487bb736 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:42:57 +0100 Subject: [PATCH 186/231] update thingy? --- .../backend/src/server/api/mastodon/endpoints/meta.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 0a4ead16f..67f3901e4 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -89,10 +89,10 @@ export async function getInstance(response: Entity.Instance) { discoverable: false, group: false, created_at: new Date().toISOString(), - note: "Please refer to the original instance for the actual admin contact.", - url: "/", - avatar: "/static-assets/badges/info.png", - avatar_static: "/static-assets/badges/info.png", + note: "

Please refer to the original instance for the actual admin contact.

", + url: `${response.uri}/`, + avatar: `${response.uri}/static-assets/badges/info.png`, + avatar_static: `${response.uri}/static-assets/badges/info.png`, header: "https://http.cat/404", header_static: "https://http.cat/404", followers_count: -1, From b712b67df3698451735c47892adf97b6e1dc7bd7 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:55:38 +0100 Subject: [PATCH 187/231] use multer upload instead --- .../server/api/mastodon/endpoints/status.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 7da2a2d7b..b2c6f9781 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -295,21 +295,18 @@ export function apiStatusMastodon(router: Router): void { } }, ); - router.post("/v1/media", async (ctx) => { + router.post("/v1/media", upload.single("file"), async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - let multipartData = await ctx.request.files; + let multipartData = await ctx.request.file; if (!multipartData) { ctx.body = { error: "No image" }; ctx.status = 401; return; } - if ((multipartData as any).file) { - multipartData = (multipartData as any).file; - } - const image = fs.readFileSync((multipartData as any).path); + const image = fs.readFileSync((multipartData).path); const data = await client.uploadMedia(image); ctx.body = data.data; } catch (e: any) { @@ -318,20 +315,18 @@ export function apiStatusMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.post("/v2/media", async (ctx) => { + router.post("/v2/media", upload.single("file"), async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const multipartData = await ctx.file; + let multipartData = await ctx.request.file; if (!multipartData) { ctx.body = { error: "No image" }; ctx.status = 401; return; } - const [path] = await createTemp(); - await pump(multipartData.buffer, fs.createWriteStream(path)); - const image = fs.readFileSync(path); + const image = fs.readFileSync((multipartData).path); const data = await client.uploadMedia(image); ctx.body = data.data; } catch (e: any) { @@ -340,6 +335,7 @@ export function apiStatusMastodon(router: Router): void { ctx.body = e.response.data; } }); + }); router.get<{ Params: { id: string } }>( "/v1/media/:id", async (ctx) => { From 76a4228de3674d4f97bf73b22eebe852da02c893 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 16:57:21 +0100 Subject: [PATCH 188/231] oh ich hab verkackt --- packages/backend/src/server/api/mastodon/endpoints/status.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index b2c6f9781..c86132364 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -335,7 +335,6 @@ export function apiStatusMastodon(router: Router): void { ctx.body = e.response.data; } }); - }); router.get<{ Params: { id: string } }>( "/v1/media/:id", async (ctx) => { From 50687ef209035589ef6d6b3051dac7a98013f2fe Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 17:02:02 +0100 Subject: [PATCH 189/231] move file upload to new router --- packages/backend/src/server/api/index.ts | 46 ++++++++++++++- .../server/api/mastodon/endpoints/status.ts | 59 +------------------ 2 files changed, 46 insertions(+), 59 deletions(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 0f58d3acf..9b6c62924 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, getClient } from "./mastodon/ApiMastodonCompatibleService.js"; import { Instances, AccessTokens, Users } from "@/models/index.js"; import config from "@/config/index.js"; import endpoints from "./endpoints.js"; @@ -39,6 +39,7 @@ app.use(async (ctx, next) => { // Init router const router = new Router(); const mastoRouter = new Router(); +const mastoFileRouter = new Router(); const errorRouter = new Router(); // Init multer instance @@ -68,6 +69,48 @@ mastoRouter.use( }), ); + +mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => { + const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; + const accessTokens = ctx.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + let multipartData = await ctx.request.file; + if (!multipartData) { + ctx.body = { error: "No image" }; + ctx.status = 401; + return; + } + const image = fs.readFileSync((multipartData).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; + } +}); +mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => { + const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; + const accessTokens = ctx.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + let multipartData = await ctx.request.file; + if (!multipartData) { + ctx.body = { error: "No image" }; + ctx.status = 401; + return; + } + const image = fs.readFileSync((multipartData).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; + } +}); + mastoRouter.use(async (ctx, next) => { if (ctx.request.query) { if (!ctx.request.body || Object.keys(ctx.request.body).length === 0) { @@ -170,6 +213,7 @@ errorRouter.all("(.*)", async (ctx) => { }); // Register router +app.use(mastoFileRouter.routes()); app.use(mastoRouter.routes()); app.use(mastoRouter.allowedMethods()); app.use(router.routes()); diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index c86132364..3981a1781 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -1,24 +1,7 @@ import Router from "@koa/router"; -import megalodon, { MegalodonInterface } from "@calckey/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 config from "@/config/index.js"; -import multer from "@koa/multer"; -import { emojiRegex, emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; +import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import axios from "axios"; -const pump = promisify(pipeline); - -// Init multer instance -const upload = multer({ - storage: multer.diskStorage({}), - limits: { - fileSize: config.maxFileSize || 262144000, - files: 1, - }, -}); export function apiStatusMastodon(router: Router): void { router.post("/v1/statuses", async (ctx) => { @@ -295,46 +278,6 @@ export function apiStatusMastodon(router: Router): void { } }, ); - router.post("/v1/media", upload.single("file"), async (ctx) => { - const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; - const accessTokens = ctx.headers.authorization; - const client = getClient(BASE_URL, accessTokens); - try { - let multipartData = await ctx.request.file; - if (!multipartData) { - ctx.body = { error: "No image" }; - ctx.status = 401; - return; - } - const image = fs.readFileSync((multipartData).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", upload.single("file"), async (ctx) => { - const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; - const accessTokens = ctx.headers.authorization; - const client = getClient(BASE_URL, accessTokens); - try { - let multipartData = await ctx.request.file; - if (!multipartData) { - ctx.body = { error: "No image" }; - ctx.status = 401; - return; - } - const image = fs.readFileSync((multipartData).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) => { From bab5577ee580b8611c4a4302fbb320293e597ebb Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 17:07:49 +0100 Subject: [PATCH 190/231] this is the sign I need a break --- packages/backend/src/server/api/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 9b6c62924..dcf79d16d 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -10,6 +10,7 @@ import cors from "@koa/cors"; import { apiMastodonCompatible, getClient } from "./mastodon/ApiMastodonCompatibleService.js"; import { Instances, AccessTokens, Users } from "@/models/index.js"; import config from "@/config/index.js"; +import fs from "fs"; import endpoints from "./endpoints.js"; import compatibility from "./compatibility.js"; import handler from "./api-handler.js"; From ac6cc1ea45c4cffb66bf4936dbc69c633be7e12f Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 17:13:35 +0100 Subject: [PATCH 191/231] maybe buffer? --- packages/backend/src/server/api/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index dcf79d16d..a49664136 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -82,8 +82,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => { ctx.status = 401; return; } - const image = fs.readFileSync((multipartData).path); - const data = await client.uploadMedia(image); + const data = await client.uploadMedia(multipartData.buffer); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -102,8 +101,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => { ctx.status = 401; return; } - const image = fs.readFileSync((multipartData).path); - const data = await client.uploadMedia(image); + const data = await client.uploadMedia(multipartData.buffer); ctx.body = data.data; } catch (e: any) { console.error(e); From 066b4fa78d60d806e77367a80f59b3acba91cf44 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 17:20:34 +0100 Subject: [PATCH 192/231] meow? --- packages/backend/src/server/api/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index a49664136..fbbd16ba0 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -82,7 +82,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => { ctx.status = 401; return; } - const data = await client.uploadMedia(multipartData.buffer); + const data = await client.uploadMedia(multipartData); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -101,7 +101,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => { ctx.status = 401; return; } - const data = await client.uploadMedia(multipartData.buffer); + const data = await client.uploadMedia(multipartData); ctx.body = data.data; } catch (e: any) { console.error(e); From f013669e7b1a5fab3274989919a69a204b5f3f62 Mon Sep 17 00:00:00 2001 From: CutestNekoAqua Date: Thu, 23 Feb 2023 17:38:18 +0100 Subject: [PATCH 193/231] aa --- packages/backend/src/server/api/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index fbbd16ba0..752303e95 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -76,13 +76,13 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - let multipartData = await ctx.request.file; + let multipartData = await ctx.file; if (!multipartData) { ctx.body = { error: "No image" }; ctx.status = 401; return; } - const data = await client.uploadMedia(multipartData); + const data = await client.uploadMedia(multipartData.buffer); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -95,13 +95,13 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - let multipartData = await ctx.request.file; + let multipartData = await ctx.file; if (!multipartData) { ctx.body = { error: "No image" }; ctx.status = 401; return; } - const data = await client.uploadMedia(multipartData); + const data = await client.uploadMedia(multipartData.buffer); ctx.body = data.data; } catch (e: any) { console.error(e); From 4c8c2364b2844c478931e5dd7021e82029daf64d Mon Sep 17 00:00:00 2001 From: Freeplay Date: Thu, 23 Feb 2023 20:50:58 -0500 Subject: [PATCH 194/231] fix not being able to click around there are new posts button --- packages/client/src/pages/timeline.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 3646e8d86..9357050c7 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -315,12 +315,14 @@ onMounted(() => { top: calc(var(--stickyTop, 0px) + 16px); z-index: 1000; width: 100%; + pointer-events: none; > button { display: block; margin: var(--margin) auto 0 auto; padding: 8px 16px; border-radius: 32px; + pointer-events: all; } } From 00e42db68103a022f50cc42b4b1859c1f7251254 Mon Sep 17 00:00:00 2001 From: Masaya Suzuki <15100604+massongit@users.noreply.github.com> Date: Fri, 24 Feb 2023 14:09:17 +0900 Subject: [PATCH 195/231] Check redis connection --------- Co-authored-by: tamaina --- package.json | 2 +- packages/backend/check_connect.js | 10 ++++++++++ packages/backend/package.json | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 packages/backend/check_connect.js diff --git a/package.json b/package.json index 11a539409..cc44c6468 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp", "build": "pnpm -r run build && pnpm run gulp", - "start": "pnpm --filter backend run start", + "start": "pnpm check:connect && pnpm --filter backend run start", "start:test": "pnpm --filter backend run start:test", "init": "pnpm run migrate", "migrate": "pnpm --filter backend run migrate", diff --git a/packages/backend/check_connect.js b/packages/backend/check_connect.js new file mode 100644 index 000000000..8bf134a10 --- /dev/null +++ b/packages/backend/check_connect.js @@ -0,0 +1,10 @@ +import {loadConfig} from './built/config.js'; +import {createRedisConnection} from "./built/redis.js"; + +const config = loadConfig(); +const redis = createRedisConnection(config); + +redis.on('connect', () => redis.disconnect()); +redis.on('error', (e) => { + throw e; +}); diff --git a/packages/backend/package.json b/packages/backend/package.json index 55a64191a..f3aaafc9a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,6 +8,7 @@ "start:test": "NODE_ENV=test pnpm node ./built/index.js", "migrate": "typeorm migration:run -d ormconfig.js", "revertmigration": "typeorm migration:revert -d ormconfig.js", + "check:connect": "node ./check_connect.js", "build": "pnpm swc src -d built -D", "watch": "pnpm swc src -d built -D -w", "lint": "pnpm rome check \"src/**/*.ts\"", From d2f12681950c6a0fc31bf411088faac1ba61f0d4 Mon Sep 17 00:00:00 2001 From: yawhn Date: Fri, 24 Feb 2023 13:58:45 +0200 Subject: [PATCH 196/231] fix: multiple Ads' bugs. feat: Ads widget https://codeberg.org/calckey/calckey/issues/9138 https://codeberg.org/calckey/calckey/issues/9080 --- .../client/src/components/global/MkAd.vue | 15 ++++++----- .../client/src/pages/admin/promotions.vue | 26 +++++++++---------- packages/client/src/ui/classic.widgets.vue | 13 +++++----- .../client/src/ui/deck/widgets-column.vue | 16 ++++++------ packages/client/src/ui/universal.widgets.vue | 12 ++++----- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/client/src/components/global/MkAd.vue b/packages/client/src/components/global/MkAd.vue index 0316a6bdf..7fa015266 100644 --- a/packages/client/src/components/global/MkAd.vue +++ b/packages/client/src/components/global/MkAd.vue @@ -56,10 +56,13 @@ const choseAd = (): Ad | null => { } const lowPriorityAds = ads.filter(ad => ad.ratio === 0); + const widgetAds = ads.filter(ad => ad.place === 'widget'); ads = ads.filter(ad => ad.ratio !== 0); - - if (ads.length === 0) { - if (lowPriorityAds.length !== 0) { + + if (widgetAds.length !== 0) { + return widgetAds; + } else if (ads.length === 0) { + if (lowPriorityAds.length !== 0) { return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)]; } else { return null; @@ -132,7 +135,7 @@ function reduceFrequency(): void { } } - &.square { + &.widget { > a , > a > img { max-width: min(300px, 100%); @@ -140,7 +143,7 @@ function reduceFrequency(): void { } } - &.horizontal { + &.inline { padding: 8px; > a , @@ -150,7 +153,7 @@ function reduceFrequency(): void { } } - &.horizontal-big { + &.inline-big { padding: 8px; > a , diff --git a/packages/client/src/pages/admin/promotions.vue b/packages/client/src/pages/admin/promotions.vue index 09b7bd542..d2e57586e 100644 --- a/packages/client/src/pages/admin/promotions.vue +++ b/packages/client/src/pages/admin/promotions.vue @@ -13,20 +13,12 @@ - - - + + + - - + @@ -56,23 +48,29 @@ import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { formatDateTimeString } from '@/scripts/format-time-string'; let ads: any[] = $ref([]); os.api('admin/ad/list').then(adsResponse => { ads = adsResponse; + // The date format should be changed to yyyy-MM-dd in order to be properly displayed + for (let i in ads) { + ads[i].expiresAt = ads[i].expiresAt.substr(0, 10); + } }); function add() { + const tomorrow = formatDateTimeString(new Date(new Date().setDate(new Date().getDate() + 1)), 'yyyy-MM-dd'); ads.unshift({ id: null, memo: '', - place: 'square', + place: 'widget', priority: 'middle', ratio: 1, url: '', imageUrl: null, - expiresAt: null, + expiresAt: tomorrow, }); } diff --git a/packages/client/src/ui/classic.widgets.vue b/packages/client/src/ui/classic.widgets.vue index 04ca34b55..3ed838d9f 100644 --- a/packages/client/src/ui/classic.widgets.vue +++ b/packages/client/src/ui/classic.widgets.vue @@ -1,11 +1,10 @@ diff --git a/packages/client/src/pages/about-calckey.vue b/packages/client/src/pages/about-calckey.vue index 155faba81..a768be59c 100644 --- a/packages/client/src/pages/about-calckey.vue +++ b/packages/client/src/pages/about-calckey.vue @@ -20,17 +20,17 @@ @@ -209,6 +209,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.security, - icon: 'ph-lock-bold ph-lg', + icon: 'ph-lock ph-bold ph-lg', }); diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index 83eb12730..1a4589ab3 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -14,7 +14,7 @@ - + @@ -24,7 +24,7 @@ - + @@ -80,27 +80,27 @@ - + - + - + - + - + @@ -163,12 +163,12 @@ @@ -178,7 +178,7 @@ - + @@ -316,7 +316,7 @@ function save() { const headerActions = $computed(() => [{ asFullButton: true, - icon: 'ph-check-bold ph-lg', + icon: 'ph-check ph-bold ph-lg', text: i18n.ts.save, handler: save, }]); @@ -325,6 +325,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.general, - icon: 'ph-gear-six-bold ph-lg', + icon: 'ph-gear-six ph-bold ph-lg', }); diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index dc8079c0e..4a26ed07c 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -114,17 +114,17 @@ function show(user) { } const headerActions = $computed(() => [{ - icon: 'ph-magnifying-glass-bold ph-lg', + icon: 'ph-magnifying-glass ph-bold ph-lg', text: i18n.ts.search, handler: searchUser, }, { asFullButton: true, - icon: 'ph-plus-bold ph-lg', + icon: 'ph-plus ph-bold ph-lg', text: i18n.ts.addUser, handler: addUser, }, { asFullButton: true, - icon: 'ph-magnifying-glass-bold ph-lg', + icon: 'ph-magnifying-glass ph-bold ph-lg', text: i18n.ts.lookup, handler: lookupUser, }]); @@ -133,7 +133,7 @@ const headerTabs = $computed(() => []); definePageMetadata(computed(() => ({ title: i18n.ts.users, - icon: 'ph-users-bold ph-lg', + icon: 'ph-users ph-bold ph-lg', }))); diff --git a/packages/client/src/pages/announcements.vue b/packages/client/src/pages/announcements.vue index 94fc5ba26..452ab8014 100644 --- a/packages/client/src/pages/announcements.vue +++ b/packages/client/src/pages/announcements.vue @@ -10,7 +10,7 @@
@@ -46,7 +46,7 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.announcements, - icon: 'ph-megaphone-simple-bold ph-lg', + icon: 'ph-megaphone-simple ph-bold ph-lg', }); diff --git a/packages/client/src/pages/antenna-timeline.vue b/packages/client/src/pages/antenna-timeline.vue index 7624d6104..f883645e3 100644 --- a/packages/client/src/pages/antenna-timeline.vue +++ b/packages/client/src/pages/antenna-timeline.vue @@ -88,15 +88,15 @@ watch(() => props.antennaId, async () => { }, { immediate: true }); const headerActions = $computed(() => antenna ? [{ - icon: 'ph-calendar-blank-bold ph-lg', + icon: 'ph-calendar-blank ph-bold ph-lg', text: i18n.ts.jumpToSpecifiedDate, handler: timetravel, }, { - icon: 'ph-gear-six-bold ph-lg', + icon: 'ph-gear-six ph-bold ph-lg', text: i18n.ts.settings, handler: settings, }, { - icon: 'ph-check-bold ph-lg', + icon: 'ph-check ph-bold ph-lg', text: i18n.ts.markAllAsRead, handler: markRead, }] : []); @@ -105,7 +105,7 @@ const headerTabs = $computed(() => []); definePageMetadata(computed(() => antenna ? { title: antenna.name, - icon: 'ph-flying-saucer-bold ph-lg', + icon: 'ph-flying-saucer ph-bold ph-lg', } : null)); diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue index e43f4c76d..0707f52f9 100644 --- a/packages/client/src/pages/api-console.vue +++ b/packages/client/src/pages/api-console.vue @@ -15,7 +15,7 @@ - +
@@ -84,6 +84,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: 'API console', - icon: 'ph-terminal-window-bold ph-lg', + icon: 'ph-terminal-window ph-bold ph-lg', }); diff --git a/packages/client/src/pages/apps.vue b/packages/client/src/pages/apps.vue index 0bb4aa904..24d704571 100644 --- a/packages/client/src/pages/apps.vue +++ b/packages/client/src/pages/apps.vue @@ -13,9 +13,9 @@ @@ -119,6 +119,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts._plugin.install, - icon: 'ph-download-simple-bold ph-lg', + icon: 'ph-download-simple ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index 96c7314ca..7949bff03 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -1,6 +1,6 @@
@@ -46,8 +46,8 @@
- {{ i18n.ts.preview }} - {{ i18n.ts.default }} + {{ i18n.ts.preview }} + {{ i18n.ts.default }}
@@ -127,9 +127,9 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.reaction, - icon: 'ph-smiley-bold ph-lg', + icon: 'ph-smiley ph-bold ph-lg', action: { - icon: 'ph-eye-bold ph-lg', + icon: 'ph-eye ph-bold ph-lg', handler: preview, }, }); diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index a5fbc774e..86632234f 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -17,8 +17,8 @@
- - + + {{ item.ip }}
@@ -30,7 +30,7 @@ - {{ i18n.ts.regenerateLoginToken }} + {{ i18n.ts.regenerateLoginToken }} @@ -103,7 +103,7 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.security, - icon: 'ph-lock-bold ph-lg', + icon: 'ph-lock ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index c2297e212..e7f2c8391 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -9,11 +9,11 @@ {{ i18n.t('_sfx.' + type) }} - + - {{ i18n.ts.default }} + {{ i18n.ts.default }}
@@ -38,7 +38,7 @@ const masterVolume = computed({ }, }); -const volumeIcon = computed(() => masterVolume.value === 0 ? 'ph-speaker-none-bold ph-lg' : 'ph-speaker-high-bold ph-lg'); +const volumeIcon = computed(() => masterVolume.value === 0 ? 'ph-speaker-none-bold ph-lg' : 'ph-speaker-high ph-bold ph-lg'); const sounds = ref({ note: ColdDeviceStorage.get('sound_note'), @@ -106,6 +106,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.sounds, - icon: 'ph-speaker-high-bold ph-lg', + icon: 'ph-speaker-high ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/statusbar.vue b/packages/client/src/pages/settings/statusbar.vue index 52ad2abec..dd12576ac 100644 --- a/packages/client/src/pages/settings/statusbar.vue +++ b/packages/client/src/pages/settings/statusbar.vue @@ -48,7 +48,7 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.statusbar, - icon: 'ph-list-bullets-bold ph-lg', + icon: 'ph-list-bullets ph-bold ph-lg', bg: 'var(--bg)', }); diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index d3b94690d..03dcd24c1 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -5,8 +5,8 @@
- {{ i18n.ts.preview }} - {{ i18n.ts.install }} + {{ i18n.ts.preview }} + {{ i18n.ts.install }}
@@ -75,6 +75,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts._theme.install, - icon: 'ph-download-simple-bold ph-lg', + icon: 'ph-download-simple ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index 02eea112b..b0742b279 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -20,7 +20,7 @@ - {{ i18n.ts.uninstall }} + {{ i18n.ts.uninstall }} @@ -73,6 +73,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts._theme.manage, - icon: 'ph-folder-notch-open-bold ph-lg', + icon: 'ph-folder-notch-open ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index 4cb43be54..15c65bd10 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -29,7 +29,7 @@
- + @@ -40,7 +40,7 @@ - + @@ -53,10 +53,10 @@
- {{ i18n.ts._theme.manage }} - {{ i18n.ts._theme.explore }} - {{ i18n.ts._theme.install }} - {{ i18n.ts._theme.make }} + {{ i18n.ts._theme.manage }} + {{ i18n.ts._theme.explore }} + {{ i18n.ts._theme.install }} + {{ i18n.ts._theme.make }}
@@ -160,7 +160,7 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: i18n.ts.theme, - icon: 'ph-palette-bold ph-lg', + icon: 'ph-palette ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue index d99de887d..7c4cc3460 100644 --- a/packages/client/src/pages/settings/webhook.edit.vue +++ b/packages/client/src/pages/settings/webhook.edit.vue @@ -9,7 +9,7 @@ - + @@ -28,7 +28,7 @@ Active
- {{ i18n.ts.save }} + {{ i18n.ts.save }}
@@ -89,6 +89,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: 'Edit webhook', - icon: 'ph-lightning-bold ph-lg', + icon: 'ph-lightning ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/webhook.new.vue b/packages/client/src/pages/settings/webhook.new.vue index c0ebd993d..ab16ccecd 100644 --- a/packages/client/src/pages/settings/webhook.new.vue +++ b/packages/client/src/pages/settings/webhook.new.vue @@ -9,7 +9,7 @@ - + @@ -26,7 +26,7 @@
- {{ i18n.ts.create }} + {{ i18n.ts.create }}
@@ -77,6 +77,6 @@ const headerTabs = $computed(() => []); definePageMetadata({ title: 'Create new webhook', - icon: 'ph-lightning-bold ph-lg', + icon: 'ph-lightning ph-bold ph-lg', }); diff --git a/packages/client/src/pages/settings/webhook.vue b/packages/client/src/pages/settings/webhook.vue index e0ad346dd..ae42f3e30 100644 --- a/packages/client/src/pages/settings/webhook.vue +++ b/packages/client/src/pages/settings/webhook.vue @@ -11,10 +11,10 @@