From c68c01a09ebe6ffc656449be4f1de9194e005f09 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 def7df635e..8f6cd272dc 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 d4b9c19033..1491d62590 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 b6d9c3e1fc..69298e73f5 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 b84bbdbb39..da98a9df19 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 0000000000..d389592a1b --- /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 4d4b81d7aa..6b752676f8 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 22d73a609d..81a3874228 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 34a4ffc06f..f1ce65483b 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 9293583bf5a96240d9e26951d885965b61624ee6 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 d389592a1b..008fb89439 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 717aa899b1a640364f7665859a9329f9e52707ff 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 1491d62590..bdedcc70e0 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 573034f6b7..08b44788de 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 2bc3b90ca3..37c5031c0f 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 4a7bd80fcd..6bc8443f0f 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 69298e73f5..353f137a76 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 0000000000..eef7f5eca5 --- /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 008fb89439..57a86c96d2 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 0000000000..1b55a5fbd3 --- /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 0000000000..5f5756077f --- /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 0000000000..3c66362dd5 --- /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 0000000000..3496272b9e --- /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 0000000000..638f0d2d41 --- /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 0000000000..f87e199f5f --- /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 0000000000..593be10f93 --- /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 0000000000..3fdb6ce881 --- /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 9675d184c8..aeeb54606b 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 9e84ec3074..4ccad96e81 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 6b752676f8..4d7259b075 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 4ae8e5bfda..642a17d578 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 81a3874228..bb3c54bd32 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 f1ce65483b..772f4173f2 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 c422fb618bf5c903ab8e96baa0bc88cef3ba11f9 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 aeeb54606b..5be8ab109e 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 3c641b4461f698c1bee3e5e5bef4fa68b754432c 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 7d5858f94a..476fd2fbbb 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 0416c15c0358ff37c91a26d4e88bbd48b8805531 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 4d7259b075..47f7afe42d 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 2c6beb66c3c20bd09d7760914e2d835a16509bed 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 7b9e6a80321ebbf73b05ddebab3eb72982aee1cc 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 eef7f5eca5..40065c83ec 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 42f9d6eb5a7fe005515df03c1332f9cfd3f530ab 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 638f0d2d41..59869da068 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 77a4f74bc8c8e93ce4425e5d8be032f12f38b00d 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 47f7afe42d..ea4740cbad 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 5a3f84a93470b5c6e5f996bc062e1f42efb69729 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 da98a9df19..499ca8767b 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 62f1b799f8599a7ee8a1ad00693ba74edf9a2808 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 499ca8767b..da98a9df19 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 d5eb131f582ad1900392eafbf3e1f6d3e55f1d5f 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 1b55a5fbd3..61d4da8a8a 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 5f5756077f..ff8b8a5188 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 3c66362dd5..810b8be110 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 59869da068..625ff386c1 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 f87e199f5f..dce3ff57c8 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 593be10f93..8dc4ba5f7d 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 ea4740cbad..02a5d8c76b 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 6554b2eb9a866ea6c8c432e4e835795568983f35 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 dfc6a3b667..7644ccaa02 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 da2368bf7af1d4e3a7f2fb4b8703b68a1f8c8ceb 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 61d4da8a8a..65caf71685 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 ff8b8a5188..f396267c5c 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 810b8be110..b4f67cf1e6 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 625ff386c1..b4599de80f 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 dce3ff57c8..dcd5be461a 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 8dc4ba5f7d..cef966e47b 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 02a5d8c76b..6ade50d18d 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 4a1dbfad297079dc94ea94b20d3117eeaafd6480 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 72953c5bfb..0b6dd3eed8 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 f08e5cd250bf8c932fd713777dd0338ad9829952 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 0b6dd3eed8..afb3af6cbc 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 f5cd9449e9eacd24d028f338f1cd27966c036a01 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 65caf71685..61d4da8a8a 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 f396267c5c..ff8b8a5188 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 b4f67cf1e6..810b8be110 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 b4599de80f..625ff386c1 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 dcd5be461a..dce3ff57c8 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 cef966e47b..8dc4ba5f7d 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 6ade50d18d..02a5d8c76b 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 a1cddb95c90ed5d70d6b704966a8c20dce9e2939 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 02a5d8c76b..6ade50d18d 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 96e1a93216eab6e42da37411becc9d997aaddb05 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 ef65cb7960..865d4bcbe6 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 2d328211d5..d22c0b2999 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 6352dc329e..eaf96d60fb 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 f7564d87b0b8ad50d6ba462feeac9e96725c71e1 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 afb3af6cbc..34d8d0ba16 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 0ec671f0a1..88bbca5c46 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 d7da4e72ce..cb58efa812 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 bb3c54bd32..9fc04d4f49 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 eaf96d60fb..292b47338e 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 b37eca8ab5..32560b4ab4 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 f331592d663787430f1dad66202d5271d2ccc67b 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 61d4da8a8a..1b55a5fbd3 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 ff8b8a5188..5f5756077f 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 810b8be110..3c66362dd5 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 625ff386c1..59869da068 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 dce3ff57c8..f87e199f5f 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 8dc4ba5f7d..593be10f93 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 6ade50d18d..ea4740cbad 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 b266b21b91ae62459a22a418b05647fc292c001b 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 40065c83ec..a8169aa956 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 57a86c96d2..cb00f9f38b 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 5f5756077f..63dbcc3649 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 ea4740cbad..14c127e8ba 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 72e5b4f4873d75dde3b05dc57cdf186dbbb1951b 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 292b47338e..a71c7b9a57 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 5bf632d3eb8e8ded77df8020a13bb096624a97bf 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 cb00f9f38b..65f8130a66 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 057af8180d6ffb08107e6980c8496c9ab75d9ad1 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 65f8130a66..57a86c96d2 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 65c73c67decbdf9e5db75ceaaf03d89e19ab730c 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 1b55a5fbd3..65caf71685 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 3c66362dd5..b4f67cf1e6 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 59869da068..b4599de80f 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 f87e199f5f..dcd5be461a 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 593be10f93..cef966e47b 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 f2b014b4a887c425cf305ad193d3ec882ee50cd6 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 14c127e8ba..34ffa3c0e4 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 838ca1841a8233d69ce70ba935c400d2854603eb 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 da98a9df19..40ecd10ecd 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 34ffa3c0e4..14c127e8ba 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 8b0e3161a3b6872bf98f2191866549c4f96244d0 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 65caf71685..4fda37a482 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 b4f67cf1e6..f665f8d237 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 b4599de80f..e65b47f029 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 cef966e47b..f01665537a 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 3fdb6ce881..2cbdf5a0e6 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 14c127e8ba..6609627fea 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 8b08b2e5725a1baa6bc59f03fbcc6f73917c9ce6 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 63dbcc3649..6425aac09a 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 6cfdc31e947b942fa637425cb727ecf5074b2eb0 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 40ecd10ecd..593c7a284c 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 476fd2fbbb..54880d9c92 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 b54b8d4d2ee415914b03329e8d708794cc74080c 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 | Bin 0 -> 6285 bytes .../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, 18 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 0000000000000000000000000000000000000000..39332b0c1beeda1edb90d78d25c16e7372aff030 GIT binary patch literal 6285 zcmdU!x}`g$8M-?Lq&p;>p&N#7(1(TrgrPyYy9A^|N@)-gB!?W3Zn)2T z|A%+2dq14@+xvVud+o0$PFGtEABP$T007{tgOv3F0F-|v3IH4RU(6H90sqA-PmmcD z0Kg^v&!7Nu@+kjZGD1N5S^z*08vqdT0RXsr`IiR(fUf`maA*SnNM->5uRYSNM^pg- zN>X)Y1;c<}$Cj2qcqQCPaaZivyWciPom>G4+gUYapWrCejwBxrg8NP%4Ohx}^n5HI z&%QC7ouY4BpZO1lkGwqA)yJF_hSwb3{Rw}%k#i2|6-W#h&;$&j3*eFV|3?U*;QgO9 z|1U!RTj2lwCjU32l1&yM`aLRT^eY=m_K5fYbu;=RCx8Cu9wYquu0n0)FTsER>Zlyu zEi6`E89?0sdf--;)_Vgo9E1J&nZH+lgOM4)7(qzv^_(L2{ZAgOCw%fnO0GY%FDV4L zHRNyo&uJJlYUFM2y&n4CO(pangtH!<;mE?U8^~@2L+``*AFSaRo%JV_tD`WtXQb8d znA5LFsqKp^+{+&8@YoQ9zufNRw8HNOV-df$s<5KFT3EuJJ>6&vxYr8T+RD3uYF|SF_jr5hYBoHZYQ$ghhHcY0jE5sdgsG}@yM9{J}t$l2{3X{u?}-}v07b|DBVpumf6{SBFKo?OXXW9w~*t9&T*7)9#okdnLcf}=Cg{*W&~hQ4(r>-ii~lu(5Io_P!H@FLF%_n+6q5i3l>%;6pO5fj7x&Xu+ zSs#F^X!q~aXtti<6X&}_eLtA)dH1uGdT6KpIK8m*MPcGu31$4u+kzQ;DR_T(^fuJV ziKrS_oblMSN^P$pEpdIZ0NR_X(-=gNX_?~2sFS{#FT(65y8B#bMsl*31$!nlb-op1 zL?Ovk8o-2@*}6_Zz3rH7eO!p^bF=LA0X8{-(=HQCrfGiW)%h(+^H zt6M2BCE_DcN_k4fR749SnFjxxu&(pp-0Y=aCD913t|O$wTDkP+w(;>HZTMJ4vEuMr zpSX8;Tv|SBzDiPRuASD1wicth*2p(0?wQpL9Qoc@{cb0W7gh@l!OugTQ}h#`F>j3k(PPYf1Ne1<|9+@8r?{%BnM_20wm*LktZ;FZcS&Xdsc4{ zWv7D1iFjv#6){4~YN?mw(Tqm#Rg-6$wO$A8BU=~PHd|e66#YOG&IOSflEiJbFx#aY z>gv!&Y9O;|^5B;f3aU7d4Un6{m8CJ`@VjGldZW5#Q}uA+mz{-Bg-xDTjrWt^=ol(@ zsq{H>B{?oxf@am~ifL2;`n$QNM!0Gt0Og6#O73}cjH*paj`fS^+v&shJ85)|KB$Pn z+N}z~L7vJMYk(mnO0SbfnSe_MVBQ8X(&TYU;_^(qC&_mBZJsg&e_REcF7s_K~)&-RRZR|p-dqB{=yohVi%&VzZct(sdR zRoS(=o@NHGKIz(Fqs_t+B{c_-`{pf`qc`>w%s8P9WCwlXYME(V3#R50Ihrf-!B3Yr zDB3|REeele?t@eW#709qj*-l{^Hr196Mwu@=SWg}x;j5Bev`@lkUm>G@%CO&4LH7W zktTA=d9+O~TtO+7aP`ZvKil>#A+u!mi0dV|!}_HwzwNS&4)UyFO2P@RZb0+cl2YQc zA52&8D>n$pLrX)w+TTXEeB)+I9SN^wiy6z^Ekp0d|CGV;MsiY1;!LEeX`e5(b;Ux6uQ<~F0q)&lEfj3x?T+nSLM0+TN>tm?WF)sl5D`2HN-SUV4dgt7 zY^3~SURzz{9%Vvn+EW?VPg4nR&}Sy&xX_!LAF<`34w62qLj@3jgWWPo*q>abSRZM! z4jh)bt)hEY=Ejg$%MAi~B5J4yBQkJywbLREMS>^d9bqA5hh>eK5$94&3J(4geg?M&Bo1$ zQ+!p$#%~OG?(3+2$qG6KtH%rZjG`ioOCIvT#zb}o7U$-KzV_3Lw@ynol8mj~GLnZ} za9SD%%{tpJkdrx3q(XZJ-^!SYoZfk{1=fv~dT-B1!vJ^xs$nb*ozh&&~blYk{+11={v?&i!9s|MnGIJVcHEVTGMnxpJXz>~SN&O)ye&0P;NMD~%3i^g zYLwr-hlUqKk8oVYmK9wQHt~7btcf%5b=TeAD#$3f+d|Aj$Gks7+0vV-nll_q^qfIg zl|5Y69pBNGtKMm2LW4KC*rRBBRRgRgPyFG|2Px9tH61Mwd~?YrFXL2v9cu^r`q_=T zCx<=4ZiAYhPU6vvKf~ooha1CNb@racuhTf8tD9;Km-~iw*b6wSpha5e@?8Cwsni;C zz$&g5E(V!CFvhE=%h@4toS15%@ir#!xlfuxs0v%NYKN-g)t}sf(@HF%ulrq=27jxVmb4w>I3x?RXu_Hdh6Nww3x@ z&FOpAYmP8htuLLI&h_`ou`7f11sQREhfN{wX`1c)sJz-v$fsMESof`@cNfpJYYq(G zH#w2ZvpAl(Zk#T)2lTtJ1(GlNf6tksX^B4K$lokd&XrKO#acgoqR3cZXrDX2^C}Aa zxjoc#W&>KFg`+tWON)uh1TP~95|1NPyxPQdLX`-%$EZZl>3ud^z^UBwdGxeJp_OZ) z$zU-7#n%U(w^gE;Yi)0WvMl;4X$H6LCXdw=Z8sHj9K4fJ#j9O6uR z4(YNh4KG*=4KvYw@5XfH87`yGC&Fm>+XP|W0TjAzZPdE6;y%1b0hF?1ujc^?DsMrE^wH?;Q6XehT zCc>(MSuhsXUo)(B{fwv?wF3T=w^rudw7zgM>X6w-e~Mp3aWEMN zSC~2cM2uhO+Uoo5tfvtv#cCg_*TkK@w+oyjMc`>8Gssh^-7p7^&}Wp8LRbeK8%eEc ztHumXTEwYWjK90Ni(Hyn7FEP z_+nVpb@pM`&Z<|SEuLtbmIHxof6Cda;wXRo#$b7;cL#1AgY4c<>c_laDda1gjY@dW zz6&e%r%&0_rDE_`mkF~WGYFi)o1{CB(@<Dq;T?wWb9rZ4#%wR<)ZEm)7Vp>bCrG~^&X%Slign$*jnWMG9`u`KdQ zDQxDLb!c?ICRs`8zBUptYBu}(k`!4DH$J(tCyF0NgXZe;FV z;*^W`ERI7oOo&7Dt6>^O?Nt<-JGW`fhMNA3S(ii+JnS0{WP&<@_l7vhum#CycS$E+LZKUyyaC z?(%DVk}V?v>7(LN&}&(V>R-H*!a@RHV3yFJl3xbg6$m(P3=rQCg*SURQ*C_8q=Jin znyF=It+$~ybl4?3#rxq3ce$iQ;ChiZ-Zwd1T3S6A(4{}x5+vF2S^OQ>7amI=QEsAB z^^+o?265&VhV5h()`t-o#DUG@=!0Mo^niZ2QSR?IA&nD@0w(gH_l%lQ>X`YMZ%a%d|qKI2S4XS&D@R+qx6H>I~2pQs{owx-oyQ06P7 z>1w&@5m#@eq{~AsyE+nY&E_=|NS2_^i1|O~WPQ?-?;GsFA;i&Mrb!7*Ph5JZQir8@ zlXe3i0s_vi=1g!MJOX)2yyY08osDKA~+EvQ7rA|C2QU3gOH7|`LqBf_`!f03kT*(nvYV|RNX+FuF+*p}S%HD?Ep{EvG z0&T#^Nw-Ae$SLb|g*-H62N^z1As)t1b|6F;O7DML6}=jB0RYuvT^uhELitBDN|@Bn zJj5DW|3VMn7CO%cp%0i*&9fdzWME1={oHWA(RN@hkX1QZ zcP!v()YN@WGV!Asx{Yvg*BR8(;SO(Ccwq5WWR>;xD?YCmdlpypKVjFZK=lq45uc<_ zU0?;;0+p{xsuJc+Y(&QGc^Iw~bW~-TM>u^sV&+YM)%??Iv|0sS5*_dxI1WxEuf2rp zl64LS`UN?q?c?@bBiDZf1+|vSI16*q1d1*Cixx+V0;{S&IMD1W6Q35X7Im2<6_D;8 zM(sk9B%rNq(dsBbSmapC)6Rj{n#e(brZgv - + {{ 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 b02f62dba472017672efeaf6771ac09dc97bf46a 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 135bf9660a..52139614c2 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 c6568bbff4..f37b2f43f0 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 ace835004341a8c4a2cf89b81f074ed7d2a0c32b 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 18df3e5b23..4a5222f61b 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 653c71dad51cdf858997159973755ea2d66875e4 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 837f42c871..b35c599bde 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 210ea77710..b37b160fbe 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 dd6187d7e4..fab06f1ab6 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 0784b68dea..a0c2635729 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 794b743703..337816e003 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 e8e726ab3b..8ab714af30 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 95fcac3e553928efba4604ffe70989386fce1f5b 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 e8da6c5a73..bc3c0ffd67 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 abff66eada7b2c0a88a9769338d2d08eaba3fc17 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 7ae9e10fba..bb178506b7 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 11ef2273ca..2e035d1695 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 005d0800ac..476a0c29f0 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 fa23360f13d8657a4aef75f758064f780e132c32 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 ddfcc3de17..f2905ec354 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 8ab714af30..eb8006c1c5 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 51e1fc4e63..b39c8b37b8 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 a7045cecfbd09301340c6f9dff83f367d81ef246 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 bc3c0ffd67..da1e886ac1 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 edc563838532596e8db9f052b09d55c2f567b401 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 f2905ec354..6a8f81e409 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 085de45d02d2a734b52156001acbd5ea9a26bc73 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 f1427db7be..5f9dfd7d95 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 4173f5c364..6ee6dc05a5 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 6dabb1db14..479d5dd06b 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 9c5dc7b3f22ad8cc12aaade6212873db0f765ba5 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 e01b0a3325..ec55d15f70 100644 --- a/packages/client/src/components/MkDialog.vue +++ b/packages/client/src/components/MkDialog.vue @@ -1,51 +1,51 @@ - From 0f9daa4ac4e7d4bfd878273940e89f9eb310bc93 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 4048177a63..840a1aee25 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 3e87ca0e94ee09c7eb6db85f9ede566eedbafca6 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 479d5dd06b..147ba9542b 100644 --- a/packages/client/src/components/MkPostFormDialog.vue +++ b/packages/client/src/components/MkPostFormDialog.vue @@ -2,45 +2,45 @@ - + - +function onModalClosed() { + emit('closed'); +} + From 7132ad2285beaa08bc3adde85439e246c21e59b1 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 840a1aee25..b88dee380e 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 6663824666..2765a99270 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -1,19 +1,19 @@ From 302e452572128deefc6bc07d1f80836f1b6a7319 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 ec55d15f70..1bcfa81a5f 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 362484f5fc..dc61e577b1 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 d3516bf4ca..0e6bdf3182 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 cc7be70bdf1a522ce99a2c34b5a2025f2d8c7b59 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 dfc5115cad..00d34e9495 100644 --- a/packages/client/src/components/MkWaitingDialog.vue +++ b/packages/client/src/components/MkWaitingDialog.vue @@ -1,18 +1,18 @@ - From b8499df5eb10f0f4c26ee94dc7d5039abc904349 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 6f5ca6618b..d1df6af4c1 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 1d2e638571b769737c0f15a79337ef716b8a2796 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 b88dee380e..80cfaf8da4 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 961837b226..f47632d40f 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 0868cba3d1..91ab737830 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 19c88fc68bca7ce5afa20185d8b316bdec02a2af 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 d1df6af4c1..a3fc37751a 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 0a3e39b6ec048dc6cb448a60dd86ea0d4cdccc68 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 a3fc37751a..640b722d22 100644 --- a/packages/client/src/components/MkUpdated.vue +++ b/packages/client/src/components/MkUpdated.vue @@ -15,6 +15,7 @@ - From 684f2e883a6dfea32cddec239a577a033e9b94bf 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 5fba6f8a63..a19acbaa14 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 f5537af8a12369b859daa0366735371addbfe31f 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 3ea3ff67e7..0f58d3acfc 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 a72ac2c7e0..0ba1e42297 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 d0a3c2c2b7af5539ad26a5b09f6e393d03b8b3d0 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 0ba1e42297..2ab834b64d 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 42a0c1b4a184ac610bf99732883987c76fd38294 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 2ab834b64d..89c733bd17 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 bb27ce997ea0526bf51b635b6b5808d655d28198 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 89c733bd17..7da2a2d7b0 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 726e469e23439639126755aab19f9445c5f62416 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 a19acbaa14..7ca6625f43 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 3281b194e41c7d8e8d4737cc2a5b4d9c5ad2b184 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 7ca6625f43..e5726fbaee 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 37d6039c1e53b2d32db9809a31606074704c5ff8 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 e5726fbaee..809a9aa117 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 8dd610a6a1581842d1568b341c3152eefe8d2312 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 809a9aa117..0a4ead16f3 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 2c6368afc426a4be32151d5d10ef5eec064cfd8a 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 0a4ead16f3..67f3901e4e 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 2dbbd1d39dc72b1e5a58cd9bbc0372d1599b3b01 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 7da2a2d7b0..b2c6f97815 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 cc44cc9da767a2d9fb3537079e3570bf0370be4f 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 b2c6f97815..c861323647 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 1728419130cb73549db8ec710bfe516d0ac0f520 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 0f58d3acfc..9b6c629245 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 c861323647..3981a17811 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 91df15209a68edfa21e8f387b4a32086824236c2 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 9b6c629245..dcf79d16d0 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 41d696c6ca78317ee2f5210bd9ce53202bd556bd 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 dcf79d16d0..a496641363 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 0be450d95f45d4caad739752f98f05c50c98c9b3 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 a496641363..fbbd16ba08 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 19d84a73ac1d5a706c8e54a01b6688ce6b0b66f0 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 fbbd16ba08..752303e95f 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 eca6a784a1c77ba9055092b092e3dd6889f14813 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 3646e8d86d..9357050c7e 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 ea5725b986a6922c0a1c21994231d65109af0756 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 11a539409e..cc44c64682 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 0000000000..8bf134a105 --- /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 55a64191a3..f3aaafc9a2 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 fce27471ab66572a5ce84e0505f5f710043f1192 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 0316a6bdf7..7fa0152663 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 09b7bd5425..d2e57586e0 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 04ca34b556..3ed838d9fe 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 155faba81b..a768be59ce 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 83eb127301..1a4589ab31 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 dc8079c0eb..4a26ed07c0 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 94fc5ba267..452ab8014e 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 7624d6104d..f883645e39 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 e43f4c76df..0707f52f9f 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 0bb4aa904e..24d704571d 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 96c7314cae..7949bff03b 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 a5fbc774ee..86632234fb 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 c2297e212c..e7f2c8391b 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 52ad2abec0..dd12576ac0 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 d3b94690d0..03dcd24c10 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 02eea112bf..b0742b279e 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 4cb43be540..15c65bd105 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 d99de887da..7c4cc3460d 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 c0ebd993d5..ab16ccecd4 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 e0ad346dd2..ae42f3e303 100644 --- a/packages/client/src/pages/settings/webhook.vue +++ b/packages/client/src/pages/settings/webhook.vue @@ -11,10 +11,10 @@