From 680d1f1459ee8d5babd17089a5e8b85202068863 Mon Sep 17 00:00:00 2001 From: Jeder Date: Sun, 4 Dec 2022 20:56:46 +0100 Subject: [PATCH 01/30] docker: only publish port on localhost Changelog: Changed --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 68051c494..3f1264e4f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - redis # - es ports: - - "3000:3000" + - "127.0.0.1:3000:3000" networks: - internal_network - external_network From 38786b6999ece7c447eb2b7408575bf0e045a0f1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 1 Jun 2023 23:21:03 +0200 Subject: [PATCH 02/30] transform tests from ts to js This allows to get rid of the special loader for ts files. There is no need for the test files to be written in TypeScript, plain JavaScript should be fine for this purpose. --- packages/backend/.mocharc.json | 4 +- packages/backend/package.json | 5 +- .../test/{activitypub.ts => activitypub.mjs} | 18 ++-- .../test/{ap-request.ts => ap-request.mjs} | 6 +- .../{api-visibility.ts => api-visibility.mjs} | 58 ++++++------- packages/backend/test/{api.ts => api.mjs} | 8 +- packages/backend/test/{block.ts => block.mjs} | 14 ++-- packages/backend/test/{chart.ts => chart.mjs} | 16 ++-- packages/backend/test/docker-compose.yml | 15 ---- .../test/{endpoints.ts => endpoints.mjs} | 9 +- ...tract-mentions.ts => extract-mentions.mjs} | 6 +- .../{fetch-resource.ts => fetch-resource.mjs} | 7 +- .../{ff-visibility.ts => ff-visibility.mjs} | 8 +- .../{get-file-info.ts => get-file-info.mjs} | 22 ++--- packages/backend/test/loader.js | 34 -------- packages/backend/test/{mfm.ts => mfm.mjs} | 4 +- .../{mock-resolver.ts => mock-resolver.mjs} | 15 ++-- packages/backend/test/{mute.ts => mute.mjs} | 28 +++---- packages/backend/test/{note.ts => note.mjs} | 11 ++- .../backend/test/prelude/{url.ts => url.mjs} | 2 +- packages/backend/test/reaction-lib.ts | 83 ------------------- .../services/{blocking.ts => blocking.mjs} | 20 ++--- .../test/{streaming.ts => streaming.mjs} | 28 +++---- .../test/{thread-mute.ts => thread-mute.mjs} | 18 ++-- packages/backend/test/tsconfig.json | 41 --------- .../test/{user-notes.ts => user-notes.mjs} | 19 ++--- packages/backend/test/{utils.ts => utils.mjs} | 58 ++++++------- 27 files changed, 175 insertions(+), 382 deletions(-) rename packages/backend/test/{activitypub.ts => activitypub.mjs} (80%) rename packages/backend/test/{ap-request.ts => ap-request.mjs} (86%) rename packages/backend/test/{api-visibility.ts => api-visibility.mjs} (94%) rename packages/backend/test/{api.ts => api.mjs} (94%) rename packages/backend/test/{block.ts => block.mjs} (86%) rename packages/backend/test/{chart.ts => chart.mjs} (95%) delete mode 100644 packages/backend/test/docker-compose.yml rename packages/backend/test/{endpoints.ts => endpoints.mjs} (99%) rename packages/backend/test/{extract-mentions.ts => extract-mentions.mjs} (81%) rename packages/backend/test/{fetch-resource.ts => fetch-resource.mjs} (98%) rename packages/backend/test/{ff-visibility.ts => ff-visibility.mjs} (96%) rename packages/backend/test/{get-file-info.ts => get-file-info.mjs} (87%) delete mode 100644 packages/backend/test/loader.js rename packages/backend/test/{mfm.ts => mfm.mjs} (96%) rename packages/backend/test/misc/{mock-resolver.ts => mock-resolver.mjs} (50%) rename packages/backend/test/{mute.ts => mute.mjs} (78%) rename packages/backend/test/{note.ts => note.mjs} (98%) rename packages/backend/test/prelude/{url.ts => url.mjs} (82%) delete mode 100644 packages/backend/test/reaction-lib.ts rename packages/backend/test/services/{blocking.ts => blocking.mjs} (67%) rename packages/backend/test/{streaming.ts => streaming.mjs} (96%) rename packages/backend/test/{thread-mute.ts => thread-mute.mjs} (84%) delete mode 100644 packages/backend/test/tsconfig.json rename packages/backend/test/{user-notes.ts => user-notes.mjs} (73%) rename packages/backend/test/{utils.ts => utils.mjs} (74%) diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json index f836f9e90..cfc511621 100644 --- a/packages/backend/.mocharc.json +++ b/packages/backend/.mocharc.json @@ -1,8 +1,6 @@ { - "extension": ["ts","js","cjs","mjs"], "node-option": [ - "experimental-specifier-resolution=node", - "loader=./test/loader.js" + "experimental-specifier-resolution=node" ], "slow": 1000, "timeout": 30000, diff --git a/packages/backend/package.json b/packages/backend/package.json index 097f6ee73..4dcc72456 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,10 +8,10 @@ "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "watch": "node watch.mjs", "lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts", - "mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "mocha": "NODE_ENV=test mocha", "migrate": "npx typeorm migration:run -d ormconfig.js", "start": "node --experimental-json-modules ./built/index.js", - "start:test": "cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js", + "start:test": "NODE_ENV=test node --experimental-json-modules ./built/index.js", "test": "npm run mocha" }, "dependencies": { @@ -164,7 +164,6 @@ "@types/ws": "8.5.3", "@typescript-eslint/eslint-plugin": "^5.46.1", "@typescript-eslint/parser": "^5.46.1", - "cross-env": "7.0.3", "eslint": "^8.29.0", "eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules", "eslint-plugin-import": "^2.26.0", diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.mjs similarity index 80% rename from packages/backend/test/activitypub.ts rename to packages/backend/test/activitypub.mjs index 6ce9c7c8e..844b2dbb1 100644 --- a/packages/backend/test/activitypub.ts +++ b/packages/backend/test/activitypub.mjs @@ -1,11 +1,11 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { initDb } from '../src/db/postgre.js'; -import { initTestDb } from './utils.js'; +import { initDb } from '../built/db/postgre.js'; +import { initTestDb } from './utils.mjs'; -function rndstr(length): string { +function rndstr(length) { const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const chars_len = 62; @@ -52,8 +52,8 @@ describe('ActivityPub', () => { }; it('Minimum Actor', async () => { - const { MockResolver } = await import('./misc/mock-resolver.js'); - const { createPerson } = await import('../src/remote/activitypub/models/person.js'); + const { MockResolver } = await import('./misc/mock-resolver.mjs'); + const { createPerson } = await import('../built/remote/activitypub/models/person.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); @@ -66,8 +66,8 @@ describe('ActivityPub', () => { }); it('Minimum Note', async () => { - const { MockResolver } = await import('./misc/mock-resolver.js'); - const { createNote } = await import('../src/remote/activitypub/models/note.js'); + const { MockResolver } = await import('./misc/mock-resolver.mjs'); + const { createNote } = await import('../built/remote/activitypub/models/note.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); @@ -99,8 +99,8 @@ describe('ActivityPub', () => { }; it('Actor', async () => { - const { MockResolver } = await import('./misc/mock-resolver.js'); - const { createPerson } = await import('../src/remote/activitypub/models/person.js'); + const { MockResolver } = await import('./misc/mock-resolver.mjs'); + const { createPerson } = await import('../built/remote/activitypub/models/person.js'); const resolver = new MockResolver(); resolver._register(actor.id, actor); diff --git a/packages/backend/test/ap-request.ts b/packages/backend/test/ap-request.mjs similarity index 86% rename from packages/backend/test/ap-request.ts rename to packages/backend/test/ap-request.mjs index 744b2f2c9..8b5fecfa9 100644 --- a/packages/backend/test/ap-request.ts +++ b/packages/backend/test/ap-request.mjs @@ -1,9 +1,9 @@ import * as assert from 'assert'; import httpSignature from '@peertube/http-signature'; -import { genRsaKeyPair } from '../src/misc/gen-key-pair.js'; -import { createSignedPost, createSignedGet } from '../src/remote/activitypub/ap-request.js'; +import { genRsaKeyPair } from '../built/misc/gen-key-pair.js'; +import { createSignedPost, createSignedGet } from '../built/remote/activitypub/ap-request.js'; -export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => { +export const buildParsedSignature = (signingString, signature, algorithm) => { return { scheme: 'Signature', params: { diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.mjs similarity index 94% rename from packages/backend/test/api-visibility.ts rename to packages/backend/test/api-visibility.mjs index 18da1d0c9..941661297 100644 --- a/packages/backend/test/api-visibility.ts +++ b/packages/backend/test/api-visibility.mjs @@ -2,12 +2,12 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, startServer, shutdownServer } from './utils.js'; +import { async, signup, request, post, startServer, shutdownServer } from './utils.mjs'; describe('API visibility', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; before(async () => { p = await startServer(); @@ -20,48 +20,48 @@ describe('API visibility', function() { describe('Note visibility', async () => { //#region vars /** protagonist */ - let alice: any; + let alice; /** follower */ - let follower: any; + let follower; /** non-follower */ - let other: any; + let other; /** non-follower who has been replied to or mentioned */ - let target: any; + let target; /** actor for which a specified visibility was set */ - let target2: any; + let target2; /** public-post */ - let pub: any; + let pub; /** home-post */ - let home: any; + let home; /** followers-post */ - let fol: any; + let fol; /** specified-post */ - let spe: any; + let spe; /** public-reply to target's post */ - let pubR: any; + let pubR; /** home-reply to target's post */ - let homeR: any; + let homeR; /** followers-reply to target's post */ - let folR: any; + let folR; /** specified-reply to target's post */ - let speR: any; + let speR; /** public-mention to target */ - let pubM: any; + let pubM; /** home-mention to target */ - let homeM: any; + let homeM; /** followers-mention to target */ - let folM: any; + let folM; /** specified-mention to target */ - let speM: any; + let speM; /** reply target post */ - let tgt: any; + let tgt; //#endregion - const show = async (noteId: any, by: any) => { + const show = async (noteId, by) => { return await request('/notes/show', { noteId, }, by); @@ -412,21 +412,21 @@ describe('API visibility', function() { it('[TL] public post on author home TL', async(async () => { const res = await request('/notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == pub.id); + const notes = res.body.filter((n) => n.id == pub.id); assert.strictEqual(notes[0].text, 'x'); })); it('[TL] public post absent from non-follower home TL', async(async () => { const res = await request('/notes/timeline', { limit: 100 }, other); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == pub.id); + const notes = res.body.filter((n) => n.id == pub.id); assert.strictEqual(notes.length, 0); })); it('[TL] followers post on follower home TL', async(async () => { const res = await request('/notes/timeline', { limit: 100 }, follower); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == fol.id); + const notes = res.body.filter((n) => n.id == fol.id); assert.strictEqual(notes[0].text, 'x'); })); //#endregion @@ -435,21 +435,21 @@ describe('API visibility', function() { it('[TL] followers reply on follower reply TL', async(async () => { const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == folR.id); + const notes = res.body.filter((n) => n.id == folR.id); assert.strictEqual(notes[0].text, 'x'); })); it('[TL] followers reply absent from not replied to non-follower reply TL', async(async () => { const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == folR.id); + const notes = res.body.filter((n) => n.id == folR.id); assert.strictEqual(notes.length, 0); })); it('[TL] followers reply on replied to actor reply TL', async(async () => { const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == folR.id); + const notes = res.body.filter((n) => n.id == folR.id); assert.strictEqual(notes[0].text, 'x'); })); //#endregion @@ -458,14 +458,14 @@ describe('API visibility', function() { it('[TL] followers reply on replied to non-follower mention TL', async(async () => { const res = await request('/notes/mentions', { limit: 100 }, target); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == folR.id); + const notes = res.body.filter((n) => n.id == folR.id); assert.strictEqual(notes[0].text, 'x'); })); it('[TL] followers mention on mentioned non-follower mention TL', async(async () => { const res = await request('/notes/mentions', { limit: 100 }, target); assert.strictEqual(res.status, 200); - const notes = res.body.filter((n: any) => n.id == folM.id); + const notes = res.body.filter((n) => n.id == folM.id); assert.strictEqual(notes[0].text, '@target x'); })); //#endregion diff --git a/packages/backend/test/api.ts b/packages/backend/test/api.mjs similarity index 94% rename from packages/backend/test/api.ts rename to packages/backend/test/api.mjs index 8bcef2ea2..7c5b7e0b2 100644 --- a/packages/backend/test/api.ts +++ b/packages/backend/test/api.mjs @@ -2,15 +2,13 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.js'; +import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.mjs'; describe('API', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; - let alice: any; - let bob: any; - let carol: any; + let p; + let alice, bob, carol; before(async () => { p = await startServer(); diff --git a/packages/backend/test/block.ts b/packages/backend/test/block.mjs similarity index 86% rename from packages/backend/test/block.ts rename to packages/backend/test/block.mjs index b01096e80..ed5cf2170 100644 --- a/packages/backend/test/block.ts +++ b/packages/backend/test/block.mjs @@ -2,17 +2,15 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, startServer, shutdownServer } from './utils.js'; +import { async, signup, request, post, startServer, shutdownServer } from './utils.mjs'; describe('Block', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; // alice blocks bob - let alice: any; - let bob: any; - let carol: any; + let alice, bob, carol; before(async () => { p = await startServer(); @@ -80,8 +78,8 @@ describe('Block', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), false); + assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === carolNote.id), true); })); }); diff --git a/packages/backend/test/chart.ts b/packages/backend/test/chart.mjs similarity index 95% rename from packages/backend/test/chart.ts rename to packages/backend/test/chart.mjs index ac0844679..70874df16 100644 --- a/packages/backend/test/chart.ts +++ b/packages/backend/test/chart.mjs @@ -2,18 +2,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as lolex from '@sinonjs/fake-timers'; -import TestChart from '../src/services/chart/charts/test.js'; -import TestGroupedChart from '../src/services/chart/charts/test-grouped.js'; -import TestUniqueChart from '../src/services/chart/charts/test-unique.js'; -import TestIntersectionChart from '../src/services/chart/charts/test-intersection.js'; -import { initDb } from '../src/db/postgre.js'; +import TestChart from '../built/services/chart/charts/test.js'; +import TestGroupedChart from '../built/services/chart/charts/test-grouped.js'; +import TestUniqueChart from '../built/services/chart/charts/test-unique.js'; +import TestIntersectionChart from '../built/services/chart/charts/test-intersection.js'; +import { initDb } from '../built/db/postgre.js'; describe('Chart', () => { - let testChart: TestChart; - let testGroupedChart: TestGroupedChart; - let testUniqueChart: TestUniqueChart; - let testIntersectionChart: TestIntersectionChart; - let clock: lolex.InstalledClock; + let testChart, testGroupedChart, testUniqueChart, testIntersectionChart, clock; beforeEach(async () => { await initDb(true); diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml deleted file mode 100644 index 5f95bec4c..000000000 --- a/packages/backend/test/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: "3" - -services: - redistest: - image: redis:6 - ports: - - "127.0.0.1:56312:6379" - - dbtest: - image: postgres:13 - ports: - - "127.0.0.1:54312:5432" - environment: - POSTGRES_DB: "test-misskey" - POSTGRES_HOST_AUTH_METHOD: trust diff --git a/packages/backend/test/endpoints.ts b/packages/backend/test/endpoints.mjs similarity index 99% rename from packages/backend/test/endpoints.ts rename to packages/backend/test/endpoints.mjs index 2aedc25f2..40bb91ed7 100644 --- a/packages/backend/test/endpoints.ts +++ b/packages/backend/test/endpoints.mjs @@ -3,13 +3,12 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.js'; +import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.mjs'; describe('API: Endpoints', () => { - let p: childProcess.ChildProcess; - let alice: any; - let bob: any; - let carol: any; + let p; + + let alice, bob, carol; before(async () => { p = await startServer(); diff --git a/packages/backend/test/extract-mentions.ts b/packages/backend/test/extract-mentions.mjs similarity index 81% rename from packages/backend/test/extract-mentions.ts rename to packages/backend/test/extract-mentions.mjs index 85afb098d..2512ac868 100644 --- a/packages/backend/test/extract-mentions.ts +++ b/packages/backend/test/extract-mentions.mjs @@ -1,11 +1,11 @@ import * as assert from 'assert'; import { parse } from 'mfm-js'; -import { extractMentions } from '../src/misc/extract-mentions.js'; +import { extractMentions } from '../built/misc/extract-mentions.js'; describe('Extract mentions', () => { it('simple', () => { - const ast = parse('@foo @bar @baz')!; + const ast = parse('@foo @bar @baz'); const mentions = extractMentions(ast); assert.deepStrictEqual(mentions, [{ username: 'foo', @@ -23,7 +23,7 @@ describe('Extract mentions', () => { }); it('nested', () => { - const ast = parse('@foo **@bar** @baz')!; + const ast = parse('@foo **@bar** @baz'); const mentions = extractMentions(ast); assert.deepStrictEqual(mentions, [{ username: 'foo', diff --git a/packages/backend/test/fetch-resource.ts b/packages/backend/test/fetch-resource.mjs similarity index 98% rename from packages/backend/test/fetch-resource.ts rename to packages/backend/test/fetch-resource.mjs index ba1bf54a9..ad135bcec 100644 --- a/packages/backend/test/fetch-resource.ts +++ b/packages/backend/test/fetch-resource.mjs @@ -3,7 +3,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; import * as openapi from '@redocly/openapi-core'; -import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js'; +import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.mjs'; // Request Accept const ONLY_AP = 'application/activity+json'; @@ -19,10 +19,9 @@ const HTML = 'text/html; charset=utf-8'; describe('Fetch resource', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; - let alice: any; - let alicesPost: any; + let alice, alicesPost; before(async () => { p = await startServer(); diff --git a/packages/backend/test/ff-visibility.ts b/packages/backend/test/ff-visibility.mjs similarity index 96% rename from packages/backend/test/ff-visibility.ts rename to packages/backend/test/ff-visibility.mjs index d71464a39..96126b072 100644 --- a/packages/backend/test/ff-visibility.ts +++ b/packages/backend/test/ff-visibility.mjs @@ -2,16 +2,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, react, connectStream, startServer, shutdownServer, simpleGet } from './utils.js'; +import { async, signup, request, post, react, connectStream, startServer, shutdownServer, simpleGet } from './utils.mjs'; describe('FF visibility', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; - let alice: any; - let bob: any; - let follower: any; + let alice, bob, follower; before(async () => { p = await startServer(); diff --git a/packages/backend/test/get-file-info.ts b/packages/backend/test/get-file-info.mjs similarity index 87% rename from packages/backend/test/get-file-info.ts rename to packages/backend/test/get-file-info.mjs index 7ce98db50..8aa6dd6f0 100644 --- a/packages/backend/test/get-file-info.ts +++ b/packages/backend/test/get-file-info.mjs @@ -1,8 +1,8 @@ import * as assert from 'assert'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; -import { getFileInfo } from '../src/misc/get-file-info.js'; -import { async } from './utils.js'; +import { getFileInfo } from '../built/misc/get-file-info.js'; +import { async } from './utils.mjs'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -10,7 +10,7 @@ const _dirname = dirname(_filename); describe('Get file info', () => { it('Empty file', async (async () => { const path = `${_dirname}/resources/emptyfile`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -28,7 +28,7 @@ describe('Get file info', () => { it('Generic JPEG', async (async () => { const path = `${_dirname}/resources/Lenna.jpg`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -46,7 +46,7 @@ describe('Get file info', () => { it('Generic APNG', async (async () => { const path = `${_dirname}/resources/anime.png`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -64,7 +64,7 @@ describe('Get file info', () => { it('Generic AGIF', async (async () => { const path = `${_dirname}/resources/anime.gif`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -82,7 +82,7 @@ describe('Get file info', () => { it('PNG with alpha', async (async () => { const path = `${_dirname}/resources/with-alpha.png`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -100,7 +100,7 @@ describe('Get file info', () => { it('Generic SVG', async (async () => { const path = `${_dirname}/resources/image.svg`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -119,7 +119,7 @@ describe('Get file info', () => { it('SVG with XML definition', async (async () => { // https://github.com/misskey-dev/misskey/issues/4413 const path = `${_dirname}/resources/with-xml-def.svg`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -137,7 +137,7 @@ describe('Get file info', () => { it('Dimension limit', async (async () => { const path = `${_dirname}/resources/25000x25000.png`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { @@ -155,7 +155,7 @@ describe('Get file info', () => { it('Rotate JPEG', async (async () => { const path = `${_dirname}/resources/rotate.jpg`; - const info = await getFileInfo(path) as any; + const info = await getFileInfo(path); delete info.warnings; delete info.blurhash; assert.deepStrictEqual(info, { diff --git a/packages/backend/test/loader.js b/packages/backend/test/loader.js deleted file mode 100644 index 6b21587e3..000000000 --- a/packages/backend/test/loader.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * ts-node/esmローダーに投げる前にpath mappingを解決する - * 参考 - * - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115 - * - https://nodejs.org/api/esm.html#loaders - * ※ https://github.com/TypeStrong/ts-node/pull/1585 が取り込まれたらこのカスタムローダーは必要なくなる - */ - -import { resolve as resolveTs, load } from 'ts-node/esm'; -import { loadConfig, createMatchPath } from 'tsconfig-paths'; -import { pathToFileURL } from 'url'; - -const tsconfig = loadConfig(); -const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths); - -export function resolve(specifier, ctx, defaultResolve) { - let resolvedSpecifier; - if (specifier.endsWith('.js')) { - // maybe transpiled - const specifierWithoutExtension = specifier.substring(0, specifier.length - '.js'.length); - const matchedSpecifier = matchPath(specifierWithoutExtension); - if (matchedSpecifier) { - resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href; - } - } else { - const matchedSpecifier = matchPath(specifier); - if (matchedSpecifier) { - resolvedSpecifier = pathToFileURL(matchedSpecifier).href; - } - } - return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve); -} - -export { load }; diff --git a/packages/backend/test/mfm.ts b/packages/backend/test/mfm.mjs similarity index 96% rename from packages/backend/test/mfm.ts rename to packages/backend/test/mfm.mjs index 5b9e47414..8ccf30911 100644 --- a/packages/backend/test/mfm.ts +++ b/packages/backend/test/mfm.mjs @@ -1,7 +1,7 @@ import * as assert from 'assert'; -import { toHtml } from '../src/mfm/to-html.js'; -import { fromHtml } from '../src/mfm/from-html.js'; +import { toHtml } from '../built/mfm/to-html.js'; +import { fromHtml } from '../built/mfm/from-html.js'; describe('toHtml', () => { it('br', async () => { diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.mjs similarity index 50% rename from packages/backend/test/misc/mock-resolver.ts rename to packages/backend/test/misc/mock-resolver.mjs index 75b80e98c..d74bc36a7 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.mjs @@ -1,21 +1,16 @@ -import { Resolver } from '../../src/remote/activitypub/resolver.js'; -import { IObject } from '../../src/remote/activitypub/type.js'; - -type MockResponse = { - type: string; - content: string; -}; +import { Resolver } from '../../built/remote/activitypub/resolver.js'; export class MockResolver extends Resolver { - private _rs = new Map(); - public async _register(uri: string, content: string | Record, type = 'application/activity+json') { + _rs = new Map(); + + async _register(uri, content, type = 'application/activity+json') { this._rs.set(uri, { type, content: typeof content === 'string' ? content : JSON.stringify(content), }); } - public async resolve(value: string | IObject): Promise { + async resolve(value) { if (typeof value !== 'string') return value; const r = this._rs.get(value); diff --git a/packages/backend/test/mute.ts b/packages/backend/test/mute.mjs similarity index 78% rename from packages/backend/test/mute.ts rename to packages/backend/test/mute.mjs index 178018eea..539f7dc3a 100644 --- a/packages/backend/test/mute.ts +++ b/packages/backend/test/mute.mjs @@ -2,17 +2,15 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, react, startServer, shutdownServer, waitFire } from './utils.js'; +import { async, signup, request, post, react, startServer, shutdownServer, waitFire } from './utils.mjs'; describe('Mute', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; // alice mutes carol - let alice: any; - let bob: any; - let carol: any; + let alice, bob, carol; before(async () => { p = await startServer(); @@ -41,8 +39,8 @@ describe('Mute', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); + assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false); })); it('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async(async () => { @@ -86,9 +84,9 @@ describe('Mute', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); + assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false); })); it('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async(async () => { @@ -102,9 +100,9 @@ describe('Mute', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); - assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); + assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === bobNote.id), false); + assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false); })); }); @@ -118,8 +116,8 @@ describe('Mute', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); - assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + assert.strictEqual(res.body.some((notification) => notification.userId === bob.id), true); + assert.strictEqual(res.body.some((notification) => notification.userId === carol.id), false); })); }); }); diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.mjs similarity index 98% rename from packages/backend/test/note.ts rename to packages/backend/test/note.mjs index 0f10f5ff9..f9a66c465 100644 --- a/packages/backend/test/note.ts +++ b/packages/backend/test/note.mjs @@ -2,17 +2,16 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { Note } from '../src/models/entities/note.js'; -import { async, signup, request, post, uploadUrl, startServer, shutdownServer, initTestDb, api } from './utils.js'; +import { Note } from '../built/models/entities/note.js'; +import { async, signup, request, post, uploadUrl, startServer, shutdownServer, initTestDb, api } from './utils.mjs'; describe('Note', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; - let Notes: any; + let p; + let Notes; - let alice: any; - let bob: any; + let alice, bob; before(async () => { p = await startServer(); diff --git a/packages/backend/test/prelude/url.ts b/packages/backend/test/prelude/url.mjs similarity index 82% rename from packages/backend/test/prelude/url.ts rename to packages/backend/test/prelude/url.mjs index df102c8df..66879ce30 100644 --- a/packages/backend/test/prelude/url.ts +++ b/packages/backend/test/prelude/url.mjs @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import { query } from '../../src/prelude/url.js'; +import { query } from '../../built/prelude/url.js'; describe('url', () => { it('query', () => { diff --git a/packages/backend/test/reaction-lib.ts b/packages/backend/test/reaction-lib.ts deleted file mode 100644 index 7c61dc76c..000000000 --- a/packages/backend/test/reaction-lib.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* -import * as assert from 'assert'; - -import { toDbReaction } from '../src/misc/reaction-lib.js'; - -describe('toDbReaction', async () => { - it('既存の文字列リアクションはそのまま', async () => { - assert.strictEqual(await toDbReaction('like'), 'like'); - }); - - it('Unicodeプリンは寿司化不能とするため文字列化しない', async () => { - assert.strictEqual(await toDbReaction('🍮'), '🍮'); - }); - - it('プリン以外の既存のリアクションは文字列化する like', async () => { - assert.strictEqual(await toDbReaction('👍'), 'like'); - }); - - it('プリン以外の既存のリアクションは文字列化する love', async () => { - assert.strictEqual(await toDbReaction('❤️'), 'love'); - }); - - it('プリン以外の既存のリアクションは文字列化する love 異体字セレクタなし', async () => { - assert.strictEqual(await toDbReaction('❤'), 'love'); - }); - - it('プリン以外の既存のリアクションは文字列化する laugh', async () => { - assert.strictEqual(await toDbReaction('😆'), 'laugh'); - }); - - it('プリン以外の既存のリアクションは文字列化する hmm', async () => { - assert.strictEqual(await toDbReaction('🤔'), 'hmm'); - }); - - it('プリン以外の既存のリアクションは文字列化する surprise', async () => { - assert.strictEqual(await toDbReaction('😮'), 'surprise'); - }); - - it('プリン以外の既存のリアクションは文字列化する congrats', async () => { - assert.strictEqual(await toDbReaction('🎉'), 'congrats'); - }); - - it('プリン以外の既存のリアクションは文字列化する angry', async () => { - assert.strictEqual(await toDbReaction('💢'), 'angry'); - }); - - it('プリン以外の既存のリアクションは文字列化する confused', async () => { - assert.strictEqual(await toDbReaction('😥'), 'confused'); - }); - - it('プリン以外の既存のリアクションは文字列化する rip', async () => { - assert.strictEqual(await toDbReaction('😇'), 'rip'); - }); - - it('それ以外はUnicodeのまま', async () => { - assert.strictEqual(await toDbReaction('🍅'), '🍅'); - }); - - it('異体字セレクタ除去', async () => { - assert.strictEqual(await toDbReaction('㊗️'), '㊗'); - }); - - it('異体字セレクタ除去 必要なし', async () => { - assert.strictEqual(await toDbReaction('㊗'), '㊗'); - }); - - it('fallback - undefined', async () => { - assert.strictEqual(await toDbReaction(undefined), 'like'); - }); - - it('fallback - null', async () => { - assert.strictEqual(await toDbReaction(null), 'like'); - }); - - it('fallback - empty', async () => { - assert.strictEqual(await toDbReaction(''), 'like'); - }); - - it('fallback - unknown', async () => { - assert.strictEqual(await toDbReaction('unknown'), 'like'); - }); -}); -*/ diff --git a/packages/backend/test/services/blocking.ts b/packages/backend/test/services/blocking.mjs similarity index 67% rename from packages/backend/test/services/blocking.ts rename to packages/backend/test/services/blocking.mjs index 122e8126e..f0e39772b 100644 --- a/packages/backend/test/services/blocking.ts +++ b/packages/backend/test/services/blocking.mjs @@ -3,17 +3,15 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; import * as sinon from 'sinon'; -import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.js'; +import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.mjs'; describe('Creating a block activity', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; // alice blocks bob - let alice: any; - let bob: any; - let carol: any; + let alice, bob, carol; before(async () => { await initTestDb(); @@ -34,10 +32,10 @@ describe('Creating a block activity', function() { }); it('Should federate blocks normally', async(async () => { - const createBlock = (await import('../../src/services/blocking/create')).default; - const deleteBlock = (await import('../../src/services/blocking/delete')).default; + const createBlock = (await import('../../built/services/blocking/create')).default; + const deleteBlock = (await import('../../built/services/blocking/delete')).default; - const queues = await import('../../src/queue/index'); + const queues = await import('../../built/queue/index'); const spy = sinon.spy(queues, 'deliver'); await createBlock(alice, bob); assert(spy.calledOnce); @@ -46,12 +44,12 @@ describe('Creating a block activity', function() { })); it('Should not federate blocks if federateBlocks is false', async () => { - const createBlock = (await import('../../src/services/blocking/create')).default; - const deleteBlock = (await import('../../src/services/blocking/delete')).default; + const createBlock = (await import('../../built/services/blocking/create')).default; + const deleteBlock = (await import('../../built/services/blocking/delete')).default; alice.federateBlocks = true; - const queues = await import('../../src/queue/index'); + const queues = await import('../../built/queue/index'); const spy = sinon.spy(queues, 'deliver'); await createBlock(alice, carol); await deleteBlock(alice, carol); diff --git a/packages/backend/test/streaming.ts b/packages/backend/test/streaming.mjs similarity index 96% rename from packages/backend/test/streaming.ts rename to packages/backend/test/streaming.mjs index ad326703d..830dd91d7 100644 --- a/packages/backend/test/streaming.ts +++ b/packages/backend/test/streaming.mjs @@ -2,14 +2,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { Following } from '../src/models/entities/following.js'; -import { connectStream, signup, api, post, startServer, shutdownServer, initTestDb, waitFire } from './utils.js'; +import { Following } from '../built/models/entities/following.js'; +import { connectStream, signup, api, post, startServer, shutdownServer, initTestDb, waitFire } from './utils.mjs'; describe('Streaming', () => { - let p: childProcess.ChildProcess; - let Followings: any; + let p; + let Followings; - const follow = async (follower: any, followee: any) => { + const follow = async (follower, followee) => { await Followings.save({ id: 'a', createdAt: new Date(), @@ -28,16 +28,12 @@ describe('Streaming', () => { this.timeout(20*60*1000); // Local users - let ayano: any; - let kyoko: any; - let chitose: any; + let ayano, kyoko, chitose; // Remote users - let akari: any; - let chinatsu: any; + let akari, chinatsu; - let kyokoNote: any; - let list: any; + let kyokoNote, list; before(async () => { p = await startServer(); @@ -388,7 +384,7 @@ describe('Streaming', () => { }); describe('Hashtag Timeline', () => { - it('指定したハッシュタグの投稿が流れる', () => new Promise(async done => { + it('指定したハッシュタグの投稿が流れる', () => new Promise(async done => { const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => { if (type == 'note') { assert.deepStrictEqual(body.text, '#foo'); @@ -406,7 +402,7 @@ describe('Streaming', () => { }); })); - it('指定したハッシュタグの投稿が流れる (AND)', () => new Promise(async done => { + it('指定したハッシュタグの投稿が流れる (AND)', () => new Promise(async done => { let fooCount = 0; let barCount = 0; let fooBarCount = 0; @@ -444,7 +440,7 @@ describe('Streaming', () => { }, 3000); })); - it('指定したハッシュタグの投稿が流れる (OR)', () => new Promise(async done => { + it('指定したハッシュタグの投稿が流れる (OR)', () => new Promise(async done => { let fooCount = 0; let barCount = 0; let fooBarCount = 0; @@ -490,7 +486,7 @@ describe('Streaming', () => { }, 3000); })); - it('指定したハッシュタグの投稿が流れる (AND + OR)', () => new Promise(async done => { + it('指定したハッシュタグの投稿が流れる (AND + OR)', () => new Promise(async done => { let fooCount = 0; let barCount = 0; let fooBarCount = 0; diff --git a/packages/backend/test/thread-mute.ts b/packages/backend/test/thread-mute.mjs similarity index 84% rename from packages/backend/test/thread-mute.ts rename to packages/backend/test/thread-mute.mjs index d4ca80af6..aa2b4679d 100644 --- a/packages/backend/test/thread-mute.ts +++ b/packages/backend/test/thread-mute.mjs @@ -2,16 +2,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils.js'; +import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils.mjs'; describe('Note thread mute', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; - let alice: any; - let bob: any; - let carol: any; + let alice, bob, carol; before(async () => { p = await startServer(); @@ -37,9 +35,9 @@ describe('Note thread mute', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); - assert.strictEqual(res.body.some((note: any) => note.id === carolReply.id), false); - assert.strictEqual(res.body.some((note: any) => note.id === carolReplyWithoutMention.id), false); + assert.strictEqual(res.body.some((note) => note.id === bobNote.id), false); + assert.strictEqual(res.body.some((note) => note.id === carolReply.id), false); + assert.strictEqual(res.body.some((note) => note.id === carolReplyWithoutMention.id), false); })); it('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async(async () => { @@ -97,8 +95,8 @@ describe('Note thread mute', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReply.id), false); - assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReplyWithoutMention.id), false); + assert.strictEqual(res.body.some((notification) => notification.note.id === carolReply.id), false); + assert.strictEqual(res.body.some((notification) => notification.note.id === carolReplyWithoutMention.id), false); // NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい })); diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json deleted file mode 100644 index 3f9020d46..000000000 --- a/packages/backend/test/tsconfig.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "noEmitOnError": false, - "noImplicitAny": true, - "noImplicitReturns": true, - "noUnusedParameters": false, - "noUnusedLocals": true, - "noFallthroughCasesInSwitch": true, - "declaration": false, - "sourceMap": true, - "target": "es2017", - "module": "es2020", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "removeComments": false, - "noLib": false, - "strict": true, - "strictNullChecks": true, - "strictPropertyInitialization": false, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "isolatedModules": true, - "baseUrl": "./", - "paths": { - "@/*": ["../src/*"] - }, - "typeRoots": [ - "../node_modules/@types", - "../src/@types" - ], - "lib": [ - "esnext" - ] - }, - "compileOnSave": false, - "include": [ - "./**/*.ts" - ] -} diff --git a/packages/backend/test/user-notes.ts b/packages/backend/test/user-notes.mjs similarity index 73% rename from packages/backend/test/user-notes.ts rename to packages/backend/test/user-notes.mjs index 9d11d2304..4a0e87c27 100644 --- a/packages/backend/test/user-notes.ts +++ b/packages/backend/test/user-notes.mjs @@ -2,17 +2,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, uploadUrl, startServer, shutdownServer } from './utils.js'; +import { async, signup, request, post, uploadUrl, startServer, shutdownServer } from './utils.mjs'; describe('users/notes', function() { this.timeout(20*60*1000); - let p: childProcess.ChildProcess; + let p; - let alice: any; - let jpgNote: any; - let pngNote: any; - let jpgPngNote: any; + let alice, jpgNote, pngNote, jpgPngNote; before(async () => { p = await startServer(); @@ -43,8 +40,8 @@ describe('users/notes', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); assert.strictEqual(res.body.length, 2); - assert.strictEqual(res.body.some((note: any) => note.id === jpgNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === jpgPngNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === jpgNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === jpgPngNote.id), true); })); it('ファイルタイプ指定 (jpg or png)', async(async () => { @@ -56,8 +53,8 @@ describe('users/notes', function() { assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); assert.strictEqual(res.body.length, 3); - assert.strictEqual(res.body.some((note: any) => note.id === jpgNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === pngNote.id), true); - assert.strictEqual(res.body.some((note: any) => note.id === jpgPngNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === jpgNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === pngNote.id), true); + assert.strictEqual(res.body.some((note) => note.id === jpgPngNote.id), true); })); }); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.mjs similarity index 74% rename from packages/backend/test/utils.ts rename to packages/backend/test/utils.mjs index 64a3b8b8b..e0af3ac46 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.mjs @@ -10,8 +10,8 @@ import * as foundkey from 'foundkey-js'; import fetch from 'node-fetch'; import FormData from 'form-data'; import { DataSource } from 'typeorm'; -import { loadConfig } from '../src/config/load.js'; -import { entities } from '../src/db/postgre.js'; +import { loadConfig } from '../built/config/load.js'; +import { entities } from '../built/db/postgre.js'; import got from 'got'; const _filename = fileURLToPath(import.meta.url); @@ -20,20 +20,20 @@ const _dirname = dirname(_filename); const config = loadConfig(); export const port = config.port; -export const async = (fn: Function) => (done: Function) => { +export const async = (fn) => (done) => { fn().then(() => { done(); - }, (err: Error) => { + }, (err) => { done(err); }); }; -export const api = async (endpoint: string, params: any, me?: any) => { +export const api = async (endpoint, params, me) => { endpoint = endpoint.replace(/^\//, ''); const auth = me ? { authorization: `Bearer ${me.token}` } : {}; - const res = await got(`http://localhost:${port}/api/${endpoint}`, { + const res = await got(`http://localhost:${port}/api/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -63,7 +63,7 @@ export const api = async (endpoint: string, params: any, me?: any) => { }; }; -export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => { +export const request = async (endpoint, params, me) => { const auth = me ? { authorization: `Bearer ${me.token}` } : {}; const res = await fetch(`http://localhost:${port}/api${endpoint}`, { @@ -83,7 +83,7 @@ export const request = async (endpoint: string, params: any, me?: any): Promise< }; }; -export const signup = async (params?: any): Promise => { +export const signup = async (params) => { const q = Object.assign({ username: 'test', password: 'test', @@ -94,7 +94,7 @@ export const signup = async (params?: any): Promise => { return res.body; }; -export const post = async (user: any, params?: foundkey.Endpoints['notes/create']['req']): Promise => { +export const post = async (user, params) => { const q = Object.assign({ text: 'test', }, params); @@ -104,7 +104,7 @@ export const post = async (user: any, params?: foundkey.Endpoints['notes/create' return res.body ? res.body.createdNote : null; }; -export const react = async (user: any, note: any, reaction: string): Promise => { +export const react = async (user, note, reaction) => { await api('notes/reactions/create', { noteId: note.id, reaction: reaction, @@ -116,10 +116,10 @@ export const react = async (user: any, note: any, reaction: string): Promise => { +export const uploadFile = async (user, _path) => { const absPath = _path == null ? `${_dirname}/resources/Lenna.jpg` : path.isAbsolute(_path) ? _path : `${_dirname}/resources/${_path}`; - const formData = new FormData() as any; + const formData = new FormData(); formData.append('i', user.token); formData.append('file', fs.createReadStream(absPath)); formData.append('force', 'true'); @@ -137,8 +137,8 @@ export const uploadFile = async (user: any, _path?: string): Promise => { return body; }; -export const uploadUrl = async (user: any, url: string) => { - let file: any; +export const uploadUrl = async (user, url) => { + let file; const ws = await connectStream(user, 'main', (msg) => { if (msg.type === 'driveFileCreated') { @@ -157,7 +157,7 @@ export const uploadUrl = async (user: any, url: string) => { return file; }; -export function connectStream(user: any, channel: string, listener: (message: Record) => any, params?: any): Promise { +export function connectStream(user, channel, listener, params) { return new Promise((res, rej) => { const ws = new WebSocket(`ws://localhost:${port}/streaming?i=${user.token}`); @@ -184,11 +184,11 @@ export function connectStream(user: any, channel: string, listener: (message: Re }); } -export const waitFire = async (user: any, channel: string, trgr: () => any, cond: (msg: Record) => boolean, params?: any) => { - return new Promise(async (res, rej) => { - let timer: NodeJS.Timeout; +export const waitFire = async (user, channel, trgr, cond, params) => { + return new Promise(async (res, rej) => { + let timer; - let ws: WebSocket; + let ws; try { ws = await connectStream(user, channel, msg => { if (cond(msg)) { @@ -201,7 +201,7 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond rej(e); } - if (!ws!) return; + if (!ws) return; timer = setTimeout(() => { ws.close(); @@ -218,7 +218,7 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond }) }; -export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?: number, type?: string, location?: string }> => { +export const simpleGet = async (path, accept = '*/*') => { // node-fetchだと3xxを取れない return await new Promise((resolve, reject) => { const req = http.request(`http://localhost:${port}${path}`, { @@ -226,7 +226,7 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? Accept: accept, }, }, res => { - if (res.statusCode! >= 400) { + if (res.statusCode >= 400) { reject(res); } else { resolve({ @@ -241,8 +241,8 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? }); }; -export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProcess) => void, moreProcess: () => Promise = async () => {}) { - return (done: (err?: Error) => any) => { +export function launchServer(callbackSpawnedProcess, moreProcess = async () => {}) { + return (done) => { const p = childProcess.spawn('node', [_dirname + '/../index.js'], { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env: { NODE_ENV: 'test', PATH: process.env.PATH }, @@ -254,7 +254,7 @@ export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProce }; } -export async function initTestDb(justBorrow = false, initEntities?: any[]) { +export async function initTestDb(justBorrow = false, initEntities) { if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; const db = new DataSource({ @@ -274,7 +274,7 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) { return db; } -export function startServer(timeout = 60 * 1000): Promise { +export function startServer(timeout = 60 * 1000) { return new Promise((res, rej) => { const t = setTimeout(() => { p.kill(SIGKILL); @@ -297,7 +297,7 @@ export function startServer(timeout = 60 * 1000): Promise { const t = setTimeout(() => { p.kill(SIGKILL); @@ -313,8 +313,8 @@ export function shutdownServer(p: childProcess.ChildProcess, timeout = 20 * 1000 }); } -export function sleep(msec: number) { - return new Promise(res => { +export function sleep(msec) { + return new Promise(res => { setTimeout(() => { res(); }, msec); From ac482e6eecde2ae9c13a1f407bf68ace6dadb0d5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 1 Jun 2023 23:24:11 +0200 Subject: [PATCH 03/30] fix lockfile --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index e4e95a074..f002af72c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3717,7 +3717,6 @@ __metadata: cli-highlight: 2.1.11 color-convert: 2.0.1 content-disposition: 0.5.4 - cross-env: 7.0.3 date-fns: 2.28.0 deep-email-validator: 0.1.21 escape-regexp: 0.0.1 From f181a8805d409c77ba2ac9c8967953aa2db2e881 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Mon, 5 Jun 2023 22:20:13 +0200 Subject: [PATCH 04/30] docs: remove bannerColor This is a fixup for commit a673647fba673626a70b66ca8465140a6ef6e070. --- packages/backend/src/models/schema/user.ts | 10 ---------- packages/foundkey-js/src/entities.ts | 1 - 2 files changed, 11 deletions(-) diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts index 8c5d747bd..bfba6ec86 100644 --- a/packages/backend/src/models/schema/user.ts +++ b/packages/backend/src/models/schema/user.ts @@ -32,11 +32,6 @@ export const packedUserLiteSchema = { type: 'any', nullable: true, optional: false, }, - avatarColor: { - type: 'any', - nullable: true, optional: false, - default: null, - }, isAdmin: { type: 'boolean', nullable: false, optional: true, @@ -122,11 +117,6 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'any', nullable: true, optional: false, }, - bannerColor: { - type: 'any', - nullable: true, optional: false, - default: null, - }, isLocked: { type: 'boolean', nullable: false, optional: false, diff --git a/packages/foundkey-js/src/entities.ts b/packages/foundkey-js/src/entities.ts index 173f0f28f..2ba798064 100644 --- a/packages/foundkey-js/src/entities.ts +++ b/packages/foundkey-js/src/entities.ts @@ -33,7 +33,6 @@ export type UserLite = { export type UserDetailed = UserLite & { bannerBlurhash: string | null; - bannerColor: string | null; bannerUrl: string | null; birthday: string | null; createdAt: DateString; From cc0915775bcd5a3d6024db5cbf3e9a463d558256 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Mon, 5 Jun 2023 22:23:59 +0200 Subject: [PATCH 05/30] server: add webhook stat to nodeinfo This will show the number of active web hooks in the node info. This is desired to be able to gauge webhook usage in Foundkey. Changelog: Added --- packages/backend/src/server/nodeinfo.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index 6ce55239c..380383ee1 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -2,7 +2,7 @@ import Router from '@koa/router'; import { IsNull, MoreThan } from 'typeorm'; import config from '@/config/index.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, Notes } from '@/models/index.js'; +import { Users, Notes, Webhooks } from '@/models/index.js'; import { MONTH, DAY } from '@/const.js'; const router = new Router(); @@ -52,12 +52,14 @@ const nodeinfo2 = async (): Promise => { activeHalfyear, activeMonth, localPosts, + activeWebhooks, ] = await Promise.all([ fetchMeta(true), Users.count({ where: { host: IsNull() } }), Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 180 * DAY)) } }), Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - MONTH)) } }), Notes.count({ where: { userHost: IsNull() } }), + Webhooks.countBy({ active: true }), ]); const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; @@ -100,6 +102,7 @@ const nodeinfo2 = async (): Promise => { enableEmail: meta.enableEmail, proxyAccountName: proxyAccount?.username ?? null, themeColor: meta.themeColor || '#86b300', + activeWebhooks, }, }; }; From e9862d480c639035bae8b4f4e23d7bab14f2c34c Mon Sep 17 00:00:00 2001 From: kazari <6c577a54-aac9-482a-955e-745c858445e3@simplelogin.com> Date: Sun, 4 Jun 2023 12:58:10 +0000 Subject: [PATCH 06/30] Translated using Weblate (Japanese) Currently translated at 99.8% (1203 of 1205 strings) Co-authored-by: kazari <6c577a54-aac9-482a-955e-745c858445e3@simplelogin.com> Translate-URL: http://translate.akkoma.dev/projects/foundkey/foundkey/ja/ Translation: Foundkey/foundkey --- locales/ja-JP.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c10755320..d6a5ad9d5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -748,6 +748,7 @@ _ffVisibility: followers: "フォロワーだけに公開" private: "非公開" + nobody: 誰にも見せない (あなたにさえも) _signup: almostThere: "ほとんど完了です" emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。" From bbd35054e65ef0f3e50f7c735a938267d928b99a Mon Sep 17 00:00:00 2001 From: Norm Date: Wed, 7 Jun 2023 16:20:52 -0400 Subject: [PATCH 07/30] change my name in mailmap --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 51a2eb9dd..5fd89868a 100644 --- a/.mailmap +++ b/.mailmap @@ -6,7 +6,7 @@ Chloe Kudryavtsev Chloe Kudryavtsev Dr. Gutfuck LLC <40531868+gutfuckllc@users.noreply.github.com> Ehsan Javadynia <31900907+ehsanjavadynia@users.noreply.github.com> -Francis Dinh +Norm Hakaba Hitoyo Hakaba Hitoyo Johann150 Michcio From 777b981bf116bb4a2d917a73a4cb9a10f70e7fd8 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 9 Jun 2023 17:58:44 +0200 Subject: [PATCH 08/30] client: disable sound for received note by default Lessen the sound pollution. closes https://akkoma.dev/FoundKeyGang/FoundKey/pulls/394 Changelog: Changed Co-authored-by: Jeder --- packages/client/src/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index a267a3634..42c89bed9 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -243,7 +243,7 @@ export class ColdDeviceStorage { plugins: [] as Plugin[], mediaVolume: 0.5, sound_masterVolume: 0.3, - sound_note: { type: 'syuilo/down', volume: 1 }, + sound_note: { type: 'syuilo/down', volume: 0 }, sound_noteMy: { type: 'syuilo/up', volume: 1 }, sound_notification: { type: 'syuilo/pope2', volume: 1 }, sound_chat: { type: 'syuilo/pope1', volume: 1 }, From 693dd3ad9709be23e6d92225e91acf0dacb9bdc1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 21 Jun 2023 22:26:39 +0200 Subject: [PATCH 09/30] remove unused parameter from MFM component --- packages/client/src/components/MkNoteSub.vue | 2 +- packages/client/src/components/note-detailed.vue | 6 +++--- packages/client/src/components/note-preview.vue | 2 +- packages/client/src/components/note-simple.vue | 2 +- packages/client/src/components/note.vue | 6 +++--- packages/client/src/components/sub-note-content.vue | 2 +- packages/client/src/components/user-info.vue | 2 +- packages/client/src/components/user-preview.vue | 2 +- packages/client/src/pages/channel.vue | 2 +- packages/client/src/pages/clip.vue | 2 +- packages/client/src/pages/follow-requests.vue | 2 +- .../client/src/pages/messaging/messaging-room.message.vue | 2 +- packages/client/src/pages/user/home.vue | 4 ++-- packages/client/src/pages/welcome.timeline.vue | 2 +- 14 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index 7f6d90080..a023c19c2 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -6,7 +6,7 @@

- +

diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index 83d8b02e2..152130f68 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -49,19 +49,19 @@

- +

- + RN:
{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: - +
diff --git a/packages/client/src/components/note-preview.vue b/packages/client/src/components/note-preview.vue index 73fd48384..1c8e435d2 100644 --- a/packages/client/src/components/note-preview.vue +++ b/packages/client/src/components/note-preview.vue @@ -7,7 +7,7 @@
- +
diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index 2b876b4c2..eb16b96c5 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -5,7 +5,7 @@

- +

diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index 8324f083a..f87c17ea3 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -37,19 +37,19 @@

- +

- + RN:
{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: - +
diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue index 078197e9b..99c13a52c 100644 --- a/packages/client/src/components/sub-note-content.vue +++ b/packages/client/src/components/sub-note-content.vue @@ -3,7 +3,7 @@
({{ i18n.ts.deleted }}) - + RN: ...
diff --git a/packages/client/src/components/user-info.vue b/packages/client/src/components/user-info.vue index a885f6696..8f5da858d 100644 --- a/packages/client/src/components/user-info.vue +++ b/packages/client/src/components/user-info.vue @@ -8,7 +8,7 @@
- +
{{ i18n.ts.noAccountDescription }}
diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue index 3cf7c49e5..9245c752d 100644 --- a/packages/client/src/components/user-preview.vue +++ b/packages/client/src/components/user-preview.vue @@ -11,7 +11,7 @@

- +
diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue index fa706434b..12697d1af 100644 --- a/packages/client/src/pages/channel.vue +++ b/packages/client/src/pages/channel.vue @@ -19,7 +19,7 @@
- +
diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue index 1abe072e3..8300c4d87 100644 --- a/packages/client/src/pages/clip.vue +++ b/packages/client/src/pages/clip.vue @@ -5,7 +5,7 @@
- +
diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 95ee40552..d4a766cf0 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -17,7 +17,7 @@

@{{ acct(req.follower) }}

- +
diff --git a/packages/client/src/pages/messaging/messaging-room.message.vue b/packages/client/src/pages/messaging/messaging-room.message.vue index 85f919208..583bc1aad 100644 --- a/packages/client/src/pages/messaging/messaging-room.message.vue +++ b/packages/client/src/pages/messaging/messaging-room.message.vue @@ -7,7 +7,7 @@ Delete
- +

{{ i18n.ts.noAccountDescription }}

@@ -74,7 +74,7 @@
- +
diff --git a/packages/client/src/pages/welcome.timeline.vue b/packages/client/src/pages/welcome.timeline.vue index 00de07d31..c9fe60c5a 100644 --- a/packages/client/src/pages/welcome.timeline.vue +++ b/packages/client/src/pages/welcome.timeline.vue @@ -5,7 +5,7 @@
- + RN: ...
From f7904a240aab7b03f2bf60f20ab0380dbe7f749c Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 21 Jun 2023 23:25:16 +0200 Subject: [PATCH 10/30] client: fix MFM overflow closes https://akkoma.dev/FoundKeyGang/FoundKey/issues/397 Changelog: Fixed --- packages/client/src/components/MkNoteSub.vue | 1 + packages/client/src/components/note-detailed.vue | 3 +++ packages/client/src/components/note.vue | 3 +++ 3 files changed, 7 insertions(+) diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index a023c19c2..83a575aa2 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -53,6 +53,7 @@ const replies: foundkey.entities.Note[] = props.conversation?.filter(item => ite .wrpstxzv { padding: 16px 32px; font-size: 0.9em; + overflow: clip; &.max-width_450px { padding: 14px 16px; diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index 152130f68..f5d73e3cb 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -390,6 +390,7 @@ if (appearNote.replyId) { > .article { padding: 32px; font-size: 1.1em; + overflow: clip; > .header { display: flex; @@ -450,6 +451,8 @@ if (appearNote.replyId) { > .content { > .text { overflow-wrap: break-word; + position: relative; + z-index: -1; > .reply { color: var(--accent); diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index f87c17ea3..979d60e8f 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -403,6 +403,7 @@ function focusAfter() { > .article { display: flex; padding: 28px 32px 18px; + overflow: clip; > .avatar { flex-shrink: 0; @@ -483,6 +484,8 @@ function focusAfter() { > .text { overflow-wrap: break-word; + position: relative; + z-index: -1; > .reply { color: var(--accent); From 51a319e8caa4105f2541c5920a6154c589436147 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 23 Jun 2023 22:00:31 +0200 Subject: [PATCH 11/30] use extractDbHost --- packages/backend/src/queue/processors/inbox.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 066e92ca3..87b1a421e 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -6,7 +6,7 @@ import Logger from '@/services/logger.js'; import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; import { Instances } from '@/models/index.js'; import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js'; -import { toPuny, extractDbHost } from '@/misc/convert-host.js'; +import { extractDbHost } from '@/misc/convert-host.js'; import { getApId } from '@/remote/activitypub/type.js'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; @@ -34,7 +34,7 @@ export default async (job: Bull.Job): Promise => { return `Old keyId is no longer supported. ${keyIdLower}`; } - const host = toPuny(new URL(keyIdLower).hostname); + const host = extractDbHost(keyIdLower) // Stop if the host is blocked. if (await shouldBlockInstance(host)) { From 1125a623a72b93dc0e329b1ec1afee483dd39e35 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 25 Jun 2023 18:40:48 +0200 Subject: [PATCH 12/30] Revert "client: fix MFM overflow" This reverts commit f7904a240aab7b03f2bf60f20ab0380dbe7f749c. Changelog: Fixed --- packages/client/src/components/MkNoteSub.vue | 1 - packages/client/src/components/note-detailed.vue | 3 --- packages/client/src/components/note.vue | 3 --- 3 files changed, 7 deletions(-) diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index 83a575aa2..a023c19c2 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -53,7 +53,6 @@ const replies: foundkey.entities.Note[] = props.conversation?.filter(item => ite .wrpstxzv { padding: 16px 32px; font-size: 0.9em; - overflow: clip; &.max-width_450px { padding: 14px 16px; diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index f5d73e3cb..152130f68 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -390,7 +390,6 @@ if (appearNote.replyId) { > .article { padding: 32px; font-size: 1.1em; - overflow: clip; > .header { display: flex; @@ -451,8 +450,6 @@ if (appearNote.replyId) { > .content { > .text { overflow-wrap: break-word; - position: relative; - z-index: -1; > .reply { color: var(--accent); diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index 979d60e8f..f87c17ea3 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -403,7 +403,6 @@ function focusAfter() { > .article { display: flex; padding: 28px 32px 18px; - overflow: clip; > .avatar { flex-shrink: 0; @@ -484,8 +483,6 @@ function focusAfter() { > .text { overflow-wrap: break-word; - position: relative; - z-index: -1; > .reply { color: var(--accent); From ecca5a164ebb858f6ec429af9b318e7435f1f516 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 25 Jun 2023 18:41:08 +0200 Subject: [PATCH 13/30] client: always forbid MFM overflow Some MFM overlays UI components. This should remove any possibility that rendered MFM escapes a Note's body. Changelog: Changed --- .../client/src/components/global/misskey-flavored-markdown.vue | 1 + packages/client/src/components/mfm.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index eb0c5f34b..d3d381521 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -175,6 +175,7 @@ withDefaults(defineProps<{ From 5c3e7c132ab19476943b0fb07d53f93abcc8e588 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 2 Jun 2023 15:44:30 +0200 Subject: [PATCH 24/30] add function to update a note --- .../src/remote/activitypub/models/note.ts | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index e80f83728..19cbaeda2 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -1,14 +1,14 @@ import promiseLimit from 'promise-limit'; - +import * as foundkey from 'foundkey-js'; import config from '@/config/index.js'; import post from '@/services/note/create.js'; -import { IRemoteUser } from '@/models/entities/user.js'; +import { User, IRemoteUser } from '@/models/entities/user.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; import { vote } from '@/services/note/polls/vote.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { extractPunyHost } from '@/misc/convert-host.js'; -import { Polls, MessagingMessages } from '@/models/index.js'; +import { Polls, MessagingMessages, Notes } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { Emoji } from '@/models/entities/emoji.js'; import { genId } from '@/misc/gen-id.js'; @@ -27,6 +27,7 @@ import { resolveImage } from './image.js'; import { extractApHashtags, extractQuoteUrl, extractEmojis } from './tag.js'; import { extractPollFromQuestion } from './question.js'; import { extractApMentions } from './mention.js'; +import { normalizeForSearch } from '@/misc/normalize-for-search.js'; export function validateNote(object: IObject): Error | null { if (object == null) { @@ -324,3 +325,43 @@ export async function resolveNote(value: string | IObject, resolver: Resolver): unlock(); } } + +/** + * Update a note. + * + * If the target Note is not registered, it will be ignored. + */ +export async function updateNote(value: IPost, actor: User, resolver: Resolver): Promise { + const err = validateNote(value); + if (err) { + apLogger.error(`${err.message}`); + throw new Error('invalid updated note'); + } + + const uri = getApId(value); + const exists = await Notes.findOneBy({ uri }); + if (exists == null) return null; + + let quoteUri = null; + if (exists.renoteId && !foundkey.entities.isPureRenote(exists)) { + const quote = await Notes.findOneBy({ id: exists.renoteId }); + quoteUri = quote.uri; + } + + // process content and update attached files (e.g. also image descriptions) + const processedContent = await processContent(actor, value, quoteUri, resolver); + + // update note content itself + await Notes.update(exists.id, { + updatedAt: new Date(), + + cw: processedContent.cw, + fileIds: processedContent.files.map(file => file.id), + attachedFileTypes: processedContent.files.map(file => file.type), + text: processedContent.text, + emojis: processedContent.apEmoji, + tags: processedContent.apHashtags.map(tag => normalizeForSearch(tag)), + url: processedContent.url, + name: processedContent.name, + }); +} From 42b555e5e429b42aea8ed9ae129722191b349fd1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 2 Jun 2023 15:47:13 +0200 Subject: [PATCH 25/30] activitypub: handle incoming Update Note activities Changelog: Added --- .../remote/activitypub/kernel/update/index.ts | 5 +- .../remote/activitypub/kernel/update/note.ts | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/remote/activitypub/kernel/update/note.ts diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index d34965db2..96367af0a 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,9 +1,10 @@ import { IRemoteUser } from '@/models/entities/user.js'; -import { getApId, getApType, IUpdate, isActor } from '@/remote/activitypub/type.js'; +import { getApId, getOneApId, getApType, IUpdate, isActor, isPost } from '@/remote/activitypub/type.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { updateQuestion } from '@/remote/activitypub/models/question.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; import { updatePerson } from '@/remote/activitypub/models/person.js'; +import { update as updateNote } from '@/remote/activitypub/kernel/update/note.js'; /** * Updateアクティビティを捌きます @@ -30,6 +31,8 @@ export default async (actor: IRemoteUser, activity: IUpdate, resolver: Resolver) } else if (getApType(object) === 'Question') { await updateQuestion(object, resolver).catch(e => console.log(e)); return 'ok: Question updated'; + } else if (isPost(object)) { + return await updateNote(actor, object, resolver); } else { return `skip: Unknown type: ${getApType(object)}`; } diff --git a/packages/backend/src/remote/activitypub/kernel/update/note.ts b/packages/backend/src/remote/activitypub/kernel/update/note.ts new file mode 100644 index 000000000..1c1140241 --- /dev/null +++ b/packages/backend/src/remote/activitypub/kernel/update/note.ts @@ -0,0 +1,50 @@ +import { IRemoteUser } from '@/models/entities/user.js'; +import { getApId } from '@/remote/activitypub/type.js'; +import { Resolver } from '@/remote/activitypub/resolver.js'; +import { Notes } from '@/models/index.js'; +import createNote from '@/remote/activitypub/kernel/create/note.js'; +import { getApLock } from '@/misc/app-lock.js'; +import { updateNote } from '@/remote/activitypub/models/note.js'; + +export async function update(actor: IRemoteUser, note: IObject, resolver: Resolver): Promise { + // check whether note exists + const uri = getApId(note); + const exists = await Notes.findOneBy({ uri }); + + if (exists == null) { + // does not yet exist, handle as if this was a create activity + // and since this is not a direct creation, handle it silently + createNote(resolver, actor, note, true); + + const unlock = await getApLock(uri); + try { + // if creating was successful... + const existsNow = await Notes.findOneByOrFail({ uri }); + // set the updatedAt timestamp since the note was changed + await Notes.update(existsNow.id, { updatedAt: new Date() }); + return 'ok: unknown note created and marked as updated'; + } catch (e) { + return `skip: updated note unknown and creating rejected: ${e.message}`; + } finally { + unlock(); + } + } else { + // check that actor is authorized to update this note + if (actor.id !== exists.userId) { + return 'skip: actor not authorized to update Note'; + } + // this does not redo the checks from the Create Note kernel + // since if the note made it into the database, we assume + // those checks must have been passed before. + + const unlock = await getApLock(uri); + try { + await updateNote(note, actor, resolver); + return 'ok: note updated'; + } catch (e) { + return `skip: update note rejected: ${e.message}`; + } finally { + unlock(); + } + } +} From c1268c04f8d0eb914ddf9f0a78e1368a6d7303b5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Mon, 5 Jun 2023 22:26:05 +0200 Subject: [PATCH 26/30] server: add noteStream handling for updated notes --- .../backend/src/server/api/stream/index.ts | 45 ++++++++++++++++--- .../backend/src/server/api/stream/types.ts | 3 ++ packages/foundkey-js/src/streaming.types.ts | 6 +++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index ace59d32d..bb0b17310 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -3,7 +3,7 @@ import { WebSocket } from 'ws'; import { readNote } from '@/services/note/read.js'; import { User } from '@/models/entities/user.js'; import { Channel as ChannelModel } from '@/models/entities/channel.js'; -import { Followings, Mutings, RenoteMutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; +import { Followings, Mutings, Notes, RenoteMutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { UserProfile } from '@/models/entities/user-profile.js'; import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js'; @@ -14,6 +14,7 @@ import { channels } from './channels/index.js'; import Channel from './channel.js'; import { StreamEventEmitter, StreamMessages } from './types.js'; import Logger from '@/services/logger.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; const logger = new Logger('streaming'); @@ -254,11 +255,43 @@ export class Connection { } private async onNoteStreamMessage(data: StreamMessages['note']['payload']) { - this.sendMessageToWs('noteUpdated', { - id: data.body.id, - type: data.type, - body: data.body.body, - }); + if (data.type === 'updated') { + const note = data.body.body.note; + // FIXME analogous to Channel.withPackedNote, but for some reason, the note + // stream is not handled as a channel but instead handled at the top level + // so this code is duplicated here I guess. + try { + // because `note` was previously JSON.stringify'ed, the fields that + // were objects before are now strings and have to be restored or + // removed from the object + note.createdAt = new Date(note.createdAt); + note.reply = null; + note.renote = null; + note.user = null; + note.channel = null; + + const packed = await Notes.pack(note, this.user, { detail: true }); + + this.sendMessageToWs('noteUpdated', { + id: data.body.id, + type: 'updated', + body: { note: packed }, + }); + } catch (err) { + if (err instanceof IdentifiableError && err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') { + // skip: note not visible to user + return; + } else { + logger.error(err); + } + } + } else { + this.sendMessageToWs('noteUpdated', { + id: data.body.id, + type: data.type, + body: data.body.body, + }); + } } /** diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 6fab792ae..08ac6f694 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -128,6 +128,9 @@ export interface NoteStreamTypes { reaction: string; userId: User['id']; }; + updated: { + note: Note; + }; } type NoteStreamEventTypes = { [key in keyof NoteStreamTypes]: { diff --git a/packages/foundkey-js/src/streaming.types.ts b/packages/foundkey-js/src/streaming.types.ts index 84c948538..942b1975c 100644 --- a/packages/foundkey-js/src/streaming.types.ts +++ b/packages/foundkey-js/src/streaming.types.ts @@ -142,6 +142,12 @@ export type NoteUpdatedEvent = { choice: number; userId: User['id']; }; +} | { + id: Note['id']; + type: 'updated'; + body: { + note: Note; + }; }; export type BroadcastEvents = { From ff8b9b6651c88a240f54f9481448dc68b04b0826 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Tue, 6 Jun 2023 20:47:17 +0200 Subject: [PATCH 27/30] refactor note creation side effects Refactoring the side effects in this way should allow them to be reused for updating notes as well. --- packages/backend/src/services/note/create.ts | 411 +-------------- .../backend/src/services/note/side-effects.ts | 474 ++++++++++++++++++ 2 files changed, 478 insertions(+), 407 deletions(-) create mode 100644 packages/backend/src/services/note/side-effects.ts diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 9c0500bda..0d9c1a3a4 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -1,109 +1,21 @@ -import { ArrayOverlap, Not, In } from 'typeorm'; import * as mfm from 'mfm-js'; import { db } from '@/db/postgre.js'; -import { publishMainStream, publishNotesStream } 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'; -import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { resolveUser } from '@/remote/resolve-user.js'; import { concat } from '@/prelude/array.js'; -import { insertNoteUnread } from '@/services/note/unread.js'; import { extractMentions } from '@/misc/extract-mentions.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; import { Note } from '@/models/entities/note.js'; -import { Mutings, Users, NoteWatchings, Notes, Instances, MutedNotes, Channels, ChannelFollowings, NoteThreadMutings } from '@/models/index.js'; +import { Users, Channels } from '@/models/index.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { App } from '@/models/entities/app.js'; -import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; +import { User } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; -import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '@/services/chart/index.js'; import { Poll, IPoll } from '@/models/entities/poll.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { checkHitAntenna } from '@/misc/check-hit-antenna.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { countSameRenotes } from '@/misc/count-same-renotes.js'; import { Channel } from '@/models/entities/channel.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { getAntennas } from '@/misc/antenna-cache.js'; -import { endedPollNotificationQueue } from '@/queue/queues.js'; -import { webhookDeliver } from '@/queue/index.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { getActiveWebhooks } from '@/misc/webhook-cache.js'; -import { IActivity } from '@/remote/activitypub/type.js'; -import { renderNoteOrRenoteActivity } from '@/remote/activitypub/renderer/note-or-renote.js'; -import { updateHashtags } from '../update-hashtag.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; -import { createNotification } from '../create-notification.js'; -import { addNoteToAntenna } from '../add-note-to-antenna.js'; -import { deliverToRelays } from '../relay.js'; -import { mutedWordsCache, index } from './index.js'; - -type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; - -class NotificationManager { - private notifier: { id: User['id']; }; - private note: Note; - private queue: { - target: ILocalUser['id']; - reason: NotificationType; - }[]; - - constructor(notifier: { id: User['id']; }, note: Note) { - this.notifier = notifier; - this.note = note; - this.queue = []; - } - - public push(notifiee: ILocalUser['id'], reason: NotificationType): void { - // No notification to yourself. - if (this.notifier.id === notifiee) return; - - const exist = this.queue.find(x => x.target === notifiee); - - if (exist) { - // If you have been "mentioned and replied to," make the notification as a reply, not as a mention. - if (reason !== 'mention') { - exist.reason = reason; - } - } else { - this.queue.push({ - reason, - target: notifiee, - }); - } - } - - public async deliver(): Promise { - for (const x of this.queue) { - // check if the sender or thread are muted - const userMuted = await Mutings.countBy({ - muterId: x.target, - muteeId: this.notifier.id, - }); - - const threadMuted = await NoteThreadMutings.countBy({ - userId: x.target, - threadId: In([ - // replies - this.note.threadId ?? this.note.id, - // renotes - this.note.renoteId ?? undefined, - ]), - mutingNotificationTypes: ArrayOverlap([x.reason]), - }); - - if (!userMuted && !threadMuted) { - createNotification(x.target, x.reason, { - notifierId: this.notifier.id, - noteId: this.note.id, - }); - } - } - } -} +import { sideEffects } from './side-effects.js'; type MinimumUser = { id: User['id']; @@ -238,253 +150,9 @@ export default async (user: { id: User['id']; username: User['username']; host: res(note); - // Update Statistics - notesChart.update(note, true); - perUserNotesChart.update(user, note, true); - - // Register host - if (Users.isRemoteUser(user)) { - registerOrFetchInstanceDoc(user.host).then(i => { - Instances.increment({ id: i.id }, 'notesCount', 1); - instanceChart.updateNote(i.host, note, true); - }); - } - - // Hashtag Update - if (data.visibility === 'public' || data.visibility === 'home') { - updateHashtags(user, tags); - } - - // Increment notes count (user) - incNotesCountOfUser(user); - - // Word mute - mutedWordsCache.fetch('').then(us => { - for (const u of us) { - checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => { - if (shouldMute) { - MutedNotes.insert({ - id: genId(), - userId: u.userId, - noteId: note.id, - reason: 'word', - }); - } - }); - } - }); - - // Antenna - for (const antenna of (await getAntennas())) { - checkHitAntenna(antenna, note, user).then(hit => { - if (hit) { - addNoteToAntenna(antenna, note, user); - } - }); - } - - // Channel - if (note.channelId) { - ChannelFollowings.findBy({ followeeId: note.channelId }).then(followings => { - for (const following of followings) { - insertNoteUnread(following.followerId, note, { - isSpecified: false, - isMentioned: false, - }); - } - }); - } - - if (data.reply) { - saveReply(data.reply, note); - } - - // When there is no re-note of the specified note by the specified user except for this post - if (data.renote && (await countSameRenotes(user.id, data.renote.id, note.id) === 0)) { - incRenoteCount(data.renote); - } - - if (data.poll && data.poll.expiresAt) { - const delay = data.poll.expiresAt.getTime() - Date.now(); - endedPollNotificationQueue.add({ - noteId: note.id, - }, { - delay, - removeOnComplete: true, - }); - } - - if (!silent) { - if (Users.isLocalUser(user)) activeUsersChart.write(user); - - // Create unread notifications - if (data.visibility === 'specified') { - if (data.visibleUsers == null) throw new Error('invalid param'); - - for (const u of data.visibleUsers) { - // Local users only - if (!Users.isLocalUser(u)) continue; - - insertNoteUnread(u.id, note, { - isSpecified: true, - isMentioned: false, - }); - } - } else { - for (const u of mentionedUsers) { - // Local users only - if (!Users.isLocalUser(u)) continue; - - insertNoteUnread(u.id, note, { - isSpecified: false, - isMentioned: true, - }); - } - } - - publishNotesStream(note); - - const webhooks = await getActiveWebhooks().then(webhooks => webhooks.filter(x => x.userId === user.id && x.on.includes('note'))); - - for (const webhook of webhooks) { - webhookDeliver(webhook, 'note', { - note: await Notes.pack(note, user), - }); - } - - const nm = new NotificationManager(user, note); - const nmRelatedPromises = []; - - await createMentionedEvents(mentionedUsers, note, nm); - - // If has in reply to note - if (data.reply) { - // Fetch watchers - nmRelatedPromises.push(notifyToWatchersOfReplyee(data.reply, user, nm)); - - // 通知 - if (data.reply.userHost === null) { - const threadMuted = await NoteThreadMutings.countBy({ - userId: data.reply.userId, - threadId: data.reply.threadId || data.reply.id, - }); - - if (!threadMuted) { - nm.push(data.reply.userId, 'reply'); - - const packedReply = await Notes.pack(note, { id: data.reply.userId }); - publishMainStream(data.reply.userId, 'reply', packedReply); - - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply')); - for (const webhook of webhooks) { - webhookDeliver(webhook, 'reply', { - note: packedReply, - }); - } - } - } - } - - // If it is renote - if (data.renote) { - const type = data.text ? 'quote' : 'renote'; - - // Notify - if (data.renote.userHost === null) { - nm.push(data.renote.userId, type); - } - - // Fetch watchers - nmRelatedPromises.push(notifyToWatchersOfRenotee(data.renote, user, nm, type)); - - // Publish event - if ((user.id !== data.renote.userId) && data.renote.userHost === null) { - const packedRenote = await Notes.pack(note, { id: data.renote.userId }); - publishMainStream(data.renote.userId, 'renote', packedRenote); - - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote')); - for (const webhook of webhooks) { - webhookDeliver(webhook, 'renote', { - note: packedRenote, - }); - } - } - } - - Promise.all(nmRelatedPromises).then(() => { - nm.deliver(); - }); - - //#region AP deliver - if (Users.isLocalUser(user) && !data.localOnly) { - (async () => { - const noteActivity = renderActivity(await renderNoteOrRenoteActivity(note)); - const dm = new DeliverManager(user, noteActivity); - - // Delivered to remote users who have been mentioned - for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) { - dm.addDirectRecipe(u as IRemoteUser); - } - - // If the post is a reply and the poster is a local user and the poster of the post to which you are replying is a remote user, deliver - if (data.reply && data.reply.userHost !== null) { - const u = await Users.findOneBy({ id: data.reply.userId }); - if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); - } - - // If the post is a Renote and the poster is a local user and the poster of the original Renote post is a remote user, deliver - if (data.renote && data.renote.userHost !== null) { - const u = await Users.findOneBy({ id: data.renote.userId }); - if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); - } - - // Deliver to followers - if (['public', 'home', 'followers'].includes(note.visibility)) { - dm.addFollowersRecipe(); - } - - if (['public'].includes(note.visibility)) { - deliverToRelays(user, noteActivity); - } - - dm.execute(); - })(); - } - //#endregion - } - - if (data.channel) { - Channels.increment({ id: data.channel.id }, 'notesCount', 1); - Channels.update(data.channel.id, { - lastNotedAt: new Date(), - }); - - const count = await Notes.countBy({ - userId: user.id, - channelId: data.channel.id, - }); - - // This process takes place after the note is created, so if there is only one note, you can determine that it is the first submission. - // TODO: but there's also the messiness of deleting a note and posting it multiple times, which is incremented by the number of times it's posted, so I'd like to do something about that. - if (count === 1) { - Channels.increment({ id: data.channel.id }, 'usersCount', 1); - } - } - - // Register to search database - index(note); + sideEffects(user, note, silent, true); }); -function incRenoteCount(renote: Note): void { - Notes.createQueryBuilder().update() - .set({ - renoteCount: () => '"renoteCount" + 1', - score: () => '"score" + 1', - }) - .where('id = :id', { id: renote.id }) - .execute(); -} - async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]): Promise { const createdAt = data.createdAt ?? new Date(); @@ -563,77 +231,6 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O } } -async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType): Promise { - const watchers = await NoteWatchings.findBy({ - noteId: renote.id, - userId: Not(user.id), - }); - - for (const watcher of watchers) { - nm.push(watcher.userId, type); - } -} - -async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, nm: NotificationManager): Promise { - const watchers = await NoteWatchings.findBy({ - noteId: reply.id, - userId: Not(user.id), - }); - - for (const watcher of watchers) { - nm.push(watcher.userId, 'reply'); - } -} - -async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager): Promise { - for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { - const threadMuted = await NoteThreadMutings.countBy({ - userId: u.id, - threadId: note.threadId || note.id, - }); - - if (threadMuted) { - continue; - } - - // note with "specified" visibility might not be visible to mentioned users - try { - const detailPackedNote = await Notes.pack(note, u, { - detail: true, - }); - - publishMainStream(u.id, 'mention', detailPackedNote); - - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention')); - for (const webhook of webhooks) { - webhookDeliver(webhook, 'mention', { - note: detailPackedNote, - }); - } - } catch (err) { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') continue; - throw err; - } - - // Create notification - nm.push(u.id, 'mention'); - } -} - -function saveReply(reply: Note): void { - Notes.increment({ id: reply.id }, 'repliesCount', 1); -} - -function incNotesCountOfUser(user: { id: User['id']; }): void { - Users.createQueryBuilder().update() - .set({ - updatedAt: new Date(), - notesCount: () => '"notesCount" + 1', - }) - .where('id = :id', { id: user.id }) - .execute(); -} - async function extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { if (tokens.length === 0) return []; diff --git a/packages/backend/src/services/note/side-effects.ts b/packages/backend/src/services/note/side-effects.ts new file mode 100644 index 000000000..d3d787bd7 --- /dev/null +++ b/packages/backend/src/services/note/side-effects.ts @@ -0,0 +1,474 @@ +import { ArrayOverlap, Not, In } from 'typeorm'; +import * as mfm from 'mfm-js'; +import { publishMainStream, publishNoteStream, publishNotesStream } from '@/services/stream.js'; +import { DeliverManager } from '@/remote/activitypub/deliver-manager.js'; +import { renderActivity } from '@/remote/activitypub/renderer/index.js'; +import { resolveUser } from '@/remote/resolve-user.js'; +import { insertNoteUnread } from '@/services/note/unread.js'; +import { extractMentions } from '@/misc/extract-mentions.js'; +import { extractHashtags } from '@/misc/extract-hashtags.js'; +import { Note } from '@/models/entities/note.js'; +import { AntennaNotes, Mutings, Users, NoteWatchings, Notes, Instances, MutedNotes, Channels, ChannelFollowings, NoteThreadMutings } from '@/models/index.js'; +import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; +import { genId } from '@/misc/gen-id.js'; +import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '@/services/chart/index.js'; +import { checkHitAntenna } from '@/misc/check-hit-antenna.js'; +import { checkWordMute } from '@/misc/check-word-mute.js'; +import { countSameRenotes } from '@/misc/count-same-renotes.js'; +import { getAntennas } from '@/misc/antenna-cache.js'; +import { endedPollNotificationQueue } from '@/queue/queues.js'; +import { webhookDeliver } from '@/queue/index.js'; +import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import { renderNoteOrRenoteActivity } from '@/remote/activitypub/renderer/note-or-renote.js'; +import { updateHashtags } from '../update-hashtag.js'; +import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; +import { createNotification } from '../create-notification.js'; +import { addNoteToAntenna } from '../add-note-to-antenna.js'; +import { deliverToRelays } from '../relay.js'; +import { mutedWordsCache, index } from './index.js'; +import { Polls } from '@/models/index.js'; +import { Poll } from '@/models/entities/poll.js'; + + +type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'update'; + +class NotificationManager { + private notifier: { id: User['id']; }; + private note: Note; + private queue: { + target: ILocalUser['id']; + reason: NotificationType; + }[]; + + constructor(notifier: { id: User['id']; }, note: Note) { + this.notifier = notifier; + this.note = note; + this.queue = []; + } + + public push(notifiee: ILocalUser['id'], reason: NotificationType): void { + // No notification to yourself. + if (this.notifier.id === notifiee) return; + + const exist = this.queue.find(x => x.target === notifiee); + + if (exist) { + // If you have been "mentioned and replied to," make the notification as a reply, not as a mention. + if (reason !== 'mention') { + exist.reason = reason; + } + } else { + this.queue.push({ + reason, + target: notifiee, + }); + } + } + + public async deliver(): Promise { + for (const x of this.queue) { + // check if the sender or thread are muted + const userMuted = await Mutings.countBy({ + muterId: x.target, + muteeId: this.notifier.id, + }); + + const threadMuted = await NoteThreadMutings.countBy({ + userId: x.target, + threadId: In([ + // replies + this.note.threadId ?? this.note.id, + // renotes + this.note.renoteId ?? undefined, + ]), + mutingNotificationTypes: ArrayOverlap([x.reason]), + }); + + if (!userMuted && !threadMuted) { + createNotification(x.target, x.reason, { + notifierId: this.notifier.id, + noteId: this.note.id, + }); + } + } + } +} + +/** + * Perform side effects for a Note such as incrementing statistics, updating hashtag usage etc. + * + * @param user The author of the note. + * @param note The note for which the side effects should be performed. + * @param silent Whether notifications and similar side effects should be suppressed. + * @param created Whether statistics should be incremented (i.e. the note was inserted and not updated in the database) + */ +export async function sideEffects(user: User, note: Note, silent = false, created = true): Promise { + if (created) { + // Update Statistics + notesChart.update(note, true); + perUserNotesChart.update(user, note, true); + Users.createQueryBuilder().update() + .set({ + updatedAt: new Date(), + notesCount: () => '"notesCount" + 1', + }) + .where('id = :id', { id: user.id }) + .execute(); + + if (Users.isLocalUser(user)) { + activeUsersChart.write(user); + } else { + // Remote user, register host + registerOrFetchInstanceDoc(user.host).then(i => { + Instances.increment({ id: i.id }, 'notesCount', 1); + instanceChart.updateNote(i.host, note, true); + }); + } + + // Channel + if (note.channelId) { + ChannelFollowings.findBy({ followeeId: note.channelId }).then(followings => { + for (const following of followings) { + insertNoteUnread(following.followerId, note, { + isSpecified: false, + isMentioned: false, + }); + } + }); + + Channels.increment({ id: note.channelId }, 'notesCount', 1); + Channels.update(note.channelId, { + lastNotedAt: new Date(), + }); + + const count = await Notes.countBy({ + userId: user.id, + channelId: note.channelId, + }); + + // This process takes place after the note is created, so if there is only one note, you can determine that it is the first submission. + // TODO: but there's also the messiness of deleting a note and posting it multiple times, which is incremented by the number of times it's posted, so I'd like to do something about that. + if (count === 1) { + Channels.increment({ id: note.channelId }, 'usersCount', 1); + } + } + + if (note.replyId) { + Notes.increment({ id: note.replyId }, 'repliesCount', 1); + } + + // When there is no re-note of the specified note by the specified user except for this post + if (note.renoteId && (await countSameRenotes(user.id, note.renoteId, note.id) === 0)) { + Notes.createQueryBuilder().update() + .set({ + renoteCount: () => '"renoteCount" + 1', + score: () => '"score" + 1', + }) + .where('id = :id', { id: note.renoteId }) + .execute(); + } + + // create job for ended poll notifications + if (note.hasPoll) { + Polls.findOneByOrFail({ noteId: note.id }) + .then((poll: Poll) => { + if (poll.expiresAt) { + const delay = poll.expiresAt.getTime() - Date.now(); + endedPollNotificationQueue.add({ + noteId: note.id, + }, { + delay, + removeOnComplete: true, + }); + } + }) + } + } + + const { mentionedUsers, tags } = await extractFromMfm(user, note); + + // Hashtag Update + if (note.visibility === 'public' || note.visibility === 'home') { + updateHashtags(user, tags); + } + + // Word mute + mutedWordsCache.fetch('').then(us => { + for (const u of us) { + checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => { + if (shouldMute) { + MutedNotes.insert({ + id: genId(), + userId: u.userId, + noteId: note.id, + reason: 'word', + }); + } + }); + } + }); + + // Antenna + if (!created) { + // Provisionally remove from antenna, it may be added again in the next step. + // But if it is not removed, it can cause duplicate key errors when trying to + // add it to the same antenna again. + await AntennaNotes.delete({ + noteId: note.id, + }); + } + getAntennas() + .then(async antennas => { + await Promise.all(antennas.map(antenna => { + return checkHitAntenna(antenna, note, user) + .then(hit => { if (hit) return addNoteToAntenna(antenna, note, user); }); + })); + }); + + // Notifications + if (!silent && created) { + // Create unread notifications + if (note.visibility === 'specified') { + if (note.visibleUserIds == null) { + throw new Error('specified note but does not have any visible user ids'); + } + + Users.findBy({ + id: In(note.visibleUserIds), + }).then((visibleUsers: User[]) => { + visibleUsers + .filter(u => Users.isLocalUser(u)) + .forEach(u => { + insertNoteUnread(u.id, note, { + isSpecified: true, + isMentioned: false, + }); + }); + }) + } else { + mentionedUsers + .filter(u => Users.isLocalUser(u)) + .forEach(u => { + insertNoteUnread(u.id, note, { + isSpecified: false, + isMentioned: true, + }); + }) + } + + publishNotesStream(note); + + const webhooks = await getActiveWebhooks().then(webhooks => webhooks.filter(x => x.userId === user.id && x.on.includes('note'))); + for (const webhook of webhooks) { + webhookDeliver(webhook, 'note', { + note: await Notes.pack(note, user), + }); + } + + const nm = new NotificationManager(user, note); + const nmRelatedPromises = []; + + await createMentionedEvents(mentionedUsers, note, nm); + + // If it is in reply to another note + if (note.replyId) { + const reply = await Notes.findOneByOrFail({ id: note.replyId }); + + // Fetch watchers + nmRelatedPromises.push(notifyWatchers(note.replyId, user, nm, 'reply')); + + // Notifications + if (reply.userHost === null) { + const threadMuted = await NoteThreadMutings.countBy({ + userId: reply.userId, + threadId: reply.threadId ?? reply.id, + }); + + if (!threadMuted) { + nm.push(reply.userId, 'reply'); + + const packedReply = await Notes.pack(note, { id: reply.userId }); + publishMainStream(reply.userId, 'reply', packedReply); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === reply.userId && x.on.includes('reply')); + for (const webhook of webhooks) { + webhookDeliver(webhook, 'reply', { + note: packedReply, + }); + } + } + } + } + + // If it is a renote + if (note.renoteId) { + const type = note.text ? 'quote' : 'renote'; + const renote = await Notes.findOneByOrFail({ id : note.renoteId }); + + // Notify + if (renote.userHost === null) { + nm.push(renote.userId, type); + } + + // Fetch watchers + nmRelatedPromises.push(notifyWatchers(note.renoteId, user, nm, type)); + + // Publish event + if ((user.id !== renote.userId) && renote.userHost === null) { + const packedRenote = await Notes.pack(note, { id: renote.userId }); + publishMainStream(renote.userId, 'renote', packedRenote); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === renote.userId && x.on.includes('renote')); + for (const webhook of webhooks) { + webhookDeliver(webhook, 'renote', { + note: packedRenote, + }); + } + } + } + + Promise.all(nmRelatedPromises).then(() => { + nm.deliver(); + }); + + //#region AP deliver + if (Users.isLocalUser(user) && !note.localOnly && created) { + (async () => { + const noteActivity = renderActivity(await renderNoteOrRenoteActivity(note)); + const dm = new DeliverManager(user, noteActivity); + + // Delivered to remote users who have been mentioned + for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) { + dm.addDirectRecipe(u as IRemoteUser); + } + + // If the post is a reply and the poster is a local user and the poster of the post to which you are replying is a remote user, deliver + if (note.replyId) { + const subquery = Notes.createaQueryBuilder() + .select('userId') + .where('"id" = :replyId', { replyId: note.replyId }); + const u = await Users.createQueryBuilder() + .where('"id" IN (' + subquery.getQuery() + ')') + .andWhere('"userHost" IS NOT NULL') + .getOne(); + if (u != null) dm.addDirectRecipe(u); + } + + // If the post is a Renote and the poster is a local user and the poster of the original Renote post is a remote user, deliver + if (note.renoteId) { + const subquery = Notes.createaQueryBuilder() + .select('userId') + .where('"id" = :renoteId', { renoteId: note.renoteId }); + const u = await Users.createQueryBuilder() + .where('"id" IN (' + subquery.getQuery() + ')') + .andWhere('"userHost" IS NOT NULL') + .getOne(); + if (u != null) dm.addDirectRecipe(u); + } + + // Deliver to followers + if (['public', 'home', 'followers'].includes(note.visibility)) { + dm.addFollowersRecipe(); + } + + if (['public'].includes(note.visibility)) { + deliverToRelays(user, noteActivity); + } + + dm.execute(); + })(); + } + //#endregion + } else if (!created) { + // updating a note does not change its un-/read status + // updating does not trigger notifications for replied to or renoted notes + // updating does not trigger notifications for mentioned users (since mentions cannot be changed) + + // TODO publish to streaming API + publishNoteStream(note.id, 'updated', { note }); + + const nm = new NotificationManager(user, note); + notifyWatchers(note.id, user, nm, 'update'); + await nm.deliver(); + + // TODO AP deliver + } + + // Register to search database + index(note); +} + +async function notifyWatchers(noteId: Note['id'], user: { id: User['id']; }, nm: NotificationManager, type: NotificationType): Promise { + const watchers = await NoteWatchings.findBy({ + noteId, + userId: Not(user.id), + }); + + for (const watcher of watchers) { + nm.push(watcher.userId, type); + } +} + +async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager): Promise { + for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { + const threadMuted = await NoteThreadMutings.countBy({ + userId: u.id, + threadId: note.threadId || note.id, + }); + + if (threadMuted) { + continue; + } + + // note with "specified" visibility might not be visible to mentioned users + try { + const detailPackedNote = await Notes.pack(note, u, { + detail: true, + }); + + publishMainStream(u.id, 'mention', detailPackedNote); + + const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention')); + for (const webhook of webhooks) { + webhookDeliver(webhook, 'mention', { + note: detailPackedNote, + }); + } + } catch (err) { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') continue; + throw err; + } + + // Create notification + nm.push(u.id, 'mention'); + } +} + +async function extractFromMfm(user: { host: User['host']; }, note: Note): Promise<{ mentionedUsers: User[], tags: string[] }> { + const tokens = mfm.parse(note.text ?? '').concat(mfm.parse(note.cw ?? '')); + + const tags = extractHashtags(tokens); + + if (tokens.length === 0) { + return { + mentionedUsers: [], + tags, + }; + } + + const mentions = extractMentions(tokens); + + let mentionedUsers = (await Promise.all(mentions.map(m => + resolveUser(m.username, m.host || user.host).catch(() => null), + ))).filter(x => x != null) as User[]; + + // Drop duplicate users + mentionedUsers = mentionedUsers.filter((u, i, self) => + i === self.findIndex(u2 => u.id === u2.id), + ); + + return { + mentionedUsers, + tags, + }; +} From 796adc85990271925482b87d93755ab84a918c8c Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 8 Jun 2023 17:35:29 +0200 Subject: [PATCH 28/30] trigger side effects after updating note --- packages/backend/src/remote/activitypub/models/note.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 19cbaeda2..02e2eca6c 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -28,6 +28,7 @@ import { extractApHashtags, extractQuoteUrl, extractEmojis } from './tag.js'; import { extractPollFromQuestion } from './question.js'; import { extractApMentions } from './mention.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { sideEffects } from '@/services/note/side-effects.js'; export function validateNote(object: IObject): Error | null { if (object == null) { @@ -364,4 +365,6 @@ export async function updateNote(value: IPost, actor: User, resolver: Resolver): url: processedContent.url, name: processedContent.name, }); + + await sideEffects(actor, await Notes.findOneByOrFail({ id: exists.id }), false, false); } From 62cd1e7ed6b44faad4d14f71c56466c8302f9a98 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 2 Jul 2023 09:11:00 +0000 Subject: [PATCH 29/30] Translated using Weblate (German) Currently translated at 100.0% (1206 of 1206 strings) Co-authored-by: Johann Translate-URL: http://translate.akkoma.dev/projects/foundkey/foundkey/de/ Translation: Foundkey/foundkey --- locales/de-DE.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/de-DE.yml b/locales/de-DE.yml index fb39f8141..01f92364a 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1309,6 +1309,7 @@ _notification: groupInvited: "Erhaltene Gruppeneinladungen" app: "Benachrichtigungen von Apps" move: Account-Umzüge + updated: Beobachtete Notiz wurde bearbeitet _actions: followBack: "folgt dir nun auch" reply: "Antworten" From 77358c8f4bdca4fade9352676a4a9d8ca6557716 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 2 Jul 2023 11:13:19 +0200 Subject: [PATCH 30/30] 13.0.0-preview6 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ package.json | 2 +- packages/backend/package.json | 2 +- packages/client/package.json | 2 +- packages/foundkey-js/package.json | 2 +- packages/sw/package.json | 2 +- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e1099f2b..666b03ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,33 @@ Unreleased changes should not be listed in this file. Instead, run `git shortlog --format='%h %s' --group=trailer:changelog ..` to see unreleased changes; replace `` with the tag you wish to compare from. If you are a contributor, please read [CONTRIBUTING.md, section "Changelog Trailer"](./CONTRIBUTING.md#changelog-trailer) on what to do instead. +## 13.0.0-preview6 - 2023-07-02 + +## Added +- **BREAKING** activitypub: validate fetch signatures + Fetching the ActivityPub representation of something now requires a valid HTTP signature. +- client: add MFM functions `position`, `scale`, `fg`, `bg` +- server: add webhook stat to nodeinfo +- activitypub: handle incoming Update Note activities + +## Changed +- client: change followers only icon to closed lock +- client: disable sound for received note by default +- client: always forbid MFM overflow +- make mutes case insensitive +- activitypub: improve JSON-LD context + The context now properly notes the `@type`s of defined attributes. +- docker: only publish port on localhost + +## Fixed +- server: fix internal download in emoji import +- server: replace unzipper with decompress + +## Removed +- migrate note favorites to clips + If you previously had favorites they will now be in a clip called "⭐". + If you want to add a note as a "favorite" you can use the menu item "Clip". + ## 13.0.0-preview5 - 2023-05-23 This release contains 6 breaking changes and 1 security update. diff --git a/package.json b/package.json index 831964a0f..f1a1e1ae9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foundkey", - "version": "13.0.0-preview5", + "version": "13.0.0-preview6", "repository": { "type": "git", "url": "https://akkoma.dev/FoundKeyGang/FoundKey.git" diff --git a/packages/backend/package.json b/packages/backend/package.json index c80bcb6bd..2c19d11f9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "13.0.0-preview5", + "version": "13.0.0-preview6", "main": "./index.js", "private": true, "type": "module", diff --git a/packages/client/package.json b/packages/client/package.json index 545c58c9f..791aa0230 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "13.0.0-preview5", + "version": "13.0.0-preview6", "private": true, "scripts": { "watch": "vite build --watch --mode development", diff --git a/packages/foundkey-js/package.json b/packages/foundkey-js/package.json index b01ff78e5..60151848c 100644 --- a/packages/foundkey-js/package.json +++ b/packages/foundkey-js/package.json @@ -1,6 +1,6 @@ { "name": "foundkey-js", - "version": "13.0.0-preview5", + "version": "13.0.0-preview6", "description": "Fork of misskey-js for Foundkey", "type": "module", "main": "./built/index.js", diff --git a/packages/sw/package.json b/packages/sw/package.json index c4023cb30..025fc2d45 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -1,6 +1,6 @@ { "name": "sw", - "version": "13.0.0-preview5", + "version": "13.0.0-preview6", "private": true, "scripts": { "watch": "node build.js watch",