From 2cd3fbf1a34b0e2506edfe30d0eaa8f2836b003c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 29 Apr 2025 08:58:11 +0000 Subject: [PATCH 01/30] Bump version to 2025.4.1-beta.9 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e6f4522525..dc9b6ee162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.4.1-beta.8", + "version": "2025.4.1-beta.9", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 3cf6827f9c..49915489dc 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.4.1-beta.8", + "version": "2025.4.1-beta.9", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 583df3ec63e25a1fd34def0dac13405396b8b663 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 29 Apr 2025 05:06:39 -0400 Subject: [PATCH 02/30] Merge commit from fork none of our endpoints will ever contain `..` (they might, maybe, at some point, contain `.`, as in `something/get.html`?), so every `Mk:api()` call to an endpoint that contains `..` can't work: let's reject it outright Co-authored-by: dakkar --- packages/frontend/src/aiscript/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/aiscript/api.ts b/packages/frontend/src/aiscript/api.ts index e7e396023d..08ba89dd9d 100644 --- a/packages/frontend/src/aiscript/api.ts +++ b/packages/frontend/src/aiscript/api.ts @@ -68,7 +68,7 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string }) }), 'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { utils.assertString(ep); - if (ep.value.includes('://')) { + if (ep.value.includes('://') || ep.value.includes('..')) { throw new errors.AiScriptRuntimeError('invalid endpoint'); } if (token) { From 81739b1f822a049a78488326fd150eb4edaca357 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:22:01 +0900 Subject: [PATCH 03/30] chore: follow up on fixing Chromatic CI diff strategy (#15912) --- .github/workflows/storybook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index ba745b6a2a..af54a0b32b 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -78,7 +78,7 @@ jobs: if: github.event_name == 'pull_request_target' id: chromatic_pull_request run: | - CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF} | xargs))" + CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff --name-only origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF} | xargs))" if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then echo "skip=true" >> $GITHUB_OUTPUT fi From 2bd4faafbc35b4a7d39e7834722a446749623dca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 00:15:24 +0000 Subject: [PATCH 04/30] Bump version to 2025.4.1-rc.0 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dc9b6ee162..e2f2d62e00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.4.1-beta.9", + "version": "2025.4.1-rc.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 49915489dc..4537d84ff6 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.4.1-beta.9", + "version": "2025.4.1-rc.0", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 979cfc1bcd6399afa0477cbc6f7457a211f26cdc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 09:01:43 +0000 Subject: [PATCH 05/30] Release: 2025.4.1 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e2f2d62e00..8649d1eac7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.4.1-rc.0", + "version": "2025.4.1", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 4537d84ff6..63fbebd5b5 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.4.1-rc.0", + "version": "2025.4.1", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 70232d3d73e7fb9922cfa519b2fc927cc8040fb0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 09:01:49 +0000 Subject: [PATCH 06/30] [skip ci] Update CHANGELOG.md (prepend template) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2705d33fb5..211e6ce201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +### General +- + +### Client +- + +### Server +- + + ## 2025.4.1 ### General From 2fcb50273d69a798c1368b05bd2de46fae2227bf Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 1 May 2025 17:56:24 +0900 Subject: [PATCH 07/30] Exclude suspended users note from most timelines (#15775) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: exclude notes by suspended user from FTT timeline endpoint * feat: exclude notes by suspended user from DB based timelines * chore: fix types * chore: fix types * chore: fix non-reply / renote * chore: fix non-reply / renote * test: update test * docs(changelog): 凍結されたユーザのノートが各種タイムラインで表示されないように * Exclude suspended users note from featured * fix: join user * Update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 3 +-- .../src/core/FanoutTimelineEndpointService.ts | 18 +++++++++++++++ packages/backend/src/core/QueryService.ts | 22 +++++++++++++++++++ packages/backend/src/core/SearchService.ts | 9 +++++++- packages/backend/src/models/Note.ts | 1 - .../server/api/endpoints/antennas/notes.ts | 1 + .../server/api/endpoints/channels/timeline.ts | 1 + .../src/server/api/endpoints/clips/notes.ts | 3 ++- .../server/api/endpoints/notes/children.ts | 1 + .../server/api/endpoints/notes/featured.ts | 1 + .../api/endpoints/notes/hybrid-timeline.ts | 1 + .../api/endpoints/notes/local-timeline.ts | 1 + .../server/api/endpoints/notes/mentions.ts | 1 + .../src/server/api/endpoints/notes/renotes.ts | 1 + .../src/server/api/endpoints/notes/replies.ts | 1 + .../api/endpoints/notes/search-by-tag.ts | 1 + .../server/api/endpoints/notes/timeline.ts | 1 + .../api/endpoints/notes/user-list-timeline.ts | 1 + .../src/server/api/endpoints/roles/notes.ts | 1 + .../api/endpoints/users/featured-notes.ts | 1 + .../src/server/api/endpoints/users/notes.ts | 2 ++ .../server/api/endpoints/users/reactions.ts | 8 ++++++- packages/backend/test/e2e/clips.ts | 4 +--- 23 files changed, 75 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 211e6ce201..bc3a322d2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,7 @@ - ### Server -- - +- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775` ## 2025.4.1 diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 1ffeb4b3a4..6253f792ed 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -36,6 +36,7 @@ type TimelineOptions = { excludeNoFiles?: boolean; excludeReplies?: boolean; excludePureRenotes: boolean; + ignoreAuthorFromUserSuspension?: boolean; dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise, }; @@ -139,6 +140,23 @@ export class FanoutTimelineEndpointService { }; } + { + const parentFilter = filter; + filter = (note) => { + const noteJoined = note as MiNote & { + renoteUser: MiUser | null; + replyUser: MiUser | null; + }; + if (!ps.ignoreAuthorFromUserSuspension) { + if (note.user!.isSuspended) return false; + } + if (note.userId !== note.renoteUserId && noteJoined.renoteUser?.isSuspended) return false; + if (note.userId !== note.replyUserId && noteJoined.replyUser?.isSuspended) return false; + + return parentFilter(note); + }; + } + const redisTimeline: MiNote[] = []; let readFromRedis = 0; let lastSuccessfulRate = 1; // rateをキャッシュする? diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 119eb49c02..e219efaf3d 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -287,4 +287,26 @@ export class QueryService { .andWhere(instanceSuspension('renoteUser')); } } + + // Requirements: user replyUser renoteUser must be joined + @bindThis + public generateSuspendedUserQueryForNote(q: SelectQueryBuilder, excludeAuthor?: boolean): void { + if (excludeAuthor) { + const brakets = (user: string) => new Brackets(qb => qb + .where(`note.${user}Id IS NULL`) + .orWhere(`user.id = ${user}.id`) + .orWhere(`${user}.isSuspended = FALSE`)); + q + .andWhere(brakets('replyUser')) + .andWhere(brakets('renoteUser')); + } else { + const brakets = (user: string) => new Brackets(qb => qb + .where(`note.${user}Id IS NULL`) + .orWhere(`${user}.isSuspended = FALSE`)); + q + .andWhere('user.isSuspended = FALSE') + .andWhere(brakets('replyUser')) + .andWhere(brakets('renoteUser')); + } + } } diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index d94281920e..20a776ded8 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -235,6 +235,7 @@ export class SearchService { this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); @@ -297,11 +298,17 @@ export class SearchService { ]) : [new Set(), new Set()]; - const query = this.notesRepository.createQueryBuilder('note'); + const query = this.notesRepository.createQueryBuilder('note') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); query.where('note.id IN (:...noteIds)', { noteIds: res.hits.map(x => x.id) }); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index c5ca2b5776..abaf615bcf 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -229,7 +229,6 @@ export class MiNote { comment: '[Denormalized]', }) public renoteUserHost: string | null; - //#endregion constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 4708dab73c..f37cdc6658 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -112,6 +112,7 @@ export default class extends Endpoint { // eslint- // https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255 this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 620cdb0f5d..2401ab8208 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -122,6 +122,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.channel', 'channel'); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) { this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 2b65407cea..33f32d1d8a 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -85,9 +85,10 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser') .andWhere('clipNote.clipId = :clipId', { clipId: clip.id }); + this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + // this.queryService.generateSuspendedUserQueryForNote(query); // To avoid problems with removing notes, ignoring suspended user for now if (me) { - this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); } diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 218a3c1a4c..712a86eb13 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -71,6 +71,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) { this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index e7aba2d306..a57c84d432 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -97,6 +97,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.channel', 'channel'); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 39b519a599..6a3ee817e4 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -244,6 +244,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 8b2d5397b2..d1dc22f233 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -157,6 +157,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index f5cddd5bad..c3722b1b5a 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -73,6 +73,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateMutedNoteThreadQuery(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 178e311ed1..ce2435b8eb 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -73,6 +73,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index d9aaed2f10..f491cc38ab 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -57,6 +57,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 079231d432..d0781bd8dd 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -82,6 +82,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 42752eaeec..e6d6a1b629 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -200,6 +200,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 58a4223207..ec7c4b0f97 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -185,6 +185,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); this.queryService.generateMutedUserRenotesQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index b0d3f6d2f9..16b0783a01 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -103,6 +103,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/featured-notes.ts b/packages/backend/src/server/api/endpoints/users/featured-notes.ts index 053fd60548..90bd11bc25 100644 --- a/packages/backend/src/server/api/endpoints/users/featured-notes.ts +++ b/packages/backend/src/server/api/endpoints/users/featured-notes.ts @@ -88,6 +88,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.channel', 'channel'); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe, false)) return false; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index b0585f75fc..0c64df569d 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -130,6 +130,7 @@ export default class extends Endpoint { // eslint- useDbFallback: true, ignoreAuthorFromMute: true, ignoreAuthorFromInstanceBlock: true, + ignoreAuthorFromUserSuspension: true, excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files excludePureRenotes: !ps.withRenotes, @@ -186,6 +187,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query, true); + this.queryService.generateSuspendedUserQueryForNote(query, true); if (me) { this.queryService.generateMutedUserQueryForNotes(query, me, { id: ps.userId }); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index bb9000a7a0..d6f1ecd8ed 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -99,10 +99,16 @@ export default class extends Endpoint { // eslint- const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('reaction.userId = :userId', { userId: ps.userId }) - .leftJoinAndSelect('reaction.note', 'note'); + .leftJoinAndSelect('reaction.note', 'note') + .leftJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); const reactions = (await query .limit(ps.limit) diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 7ae1ee4523..570cc61c4b 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -909,7 +909,7 @@ describe('クリップ', () => { assert.deepStrictEqual(res.map(x => x.id), [aliceNote.id]); }); - test('はPublicなクリップなら認証なしでも取得できる。(非公開ノートはhideされて返ってくる)', async () => { + test('はPublicなクリップなら認証なしでも取得できる。(非公開ノートは含まれない)', async () => { const publicClip = await create({ isPublic: true }); await addNote({ clipId: publicClip.id, noteId: aliceNote.id }); await addNote({ clipId: publicClip.id, noteId: aliceHomeNote.id }); @@ -919,8 +919,6 @@ describe('クリップ', () => { const res = await notes({ clipId: publicClip.id }, { user: undefined }); const expects = [ aliceNote, aliceHomeNote, - // 認証なしだと非公開ノートは結果には含むけどhideされる。 - hiddenNote(aliceFollowersNote), hiddenNote(aliceSpecifiedNote), ]; assert.deepStrictEqual( res.sort(compareBy(s => s.id)).map(x => x.id), From 795b8366b50324a92c39816fb4a3189177175d75 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 1 May 2025 17:58:34 +0900 Subject: [PATCH 08/30] Block deliver by software (#15727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(backend): suspend instance by software * feat(frontend): suspend instance by software * docs(chaangelog): 連合先のソフトウェア及びバージョン名により配信停止を行えるようになりました * chore: 例で使うバージョン名を変える * fix: broken lockfile * fix: broken lock file * fix broken lock file * update changelog * fix dependencies * Update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + locales/index.d.ts | 16 ++++ locales/ja-JP.yml | 4 + .../1743403874305-DeliverSuspendedSoftware.js | 16 ++++ packages/backend/package.json | 1 + packages/backend/src/core/UtilityService.ts | 20 ++++- .../core/entities/InstanceEntityService.ts | 5 +- packages/backend/src/models/Meta.ts | 10 +++ .../models/json-schema/federation-instance.ts | 2 +- .../processors/DeliverProcessorService.ts | 13 +++- .../src/server/api/endpoints/admin/meta.ts | 19 +++++ .../server/api/endpoints/admin/update-meta.ts | 15 ++++ .../frontend/src/pages/admin/settings.vue | 76 +++++++++++++++++++ packages/frontend/src/pages/instance-info.vue | 4 +- packages/frontend/src/use/use-form.ts | 3 +- packages/frontend/tsconfig.json | 1 + packages/misskey-js/src/autogen/types.ts | 10 ++- pnpm-lock.yaml | 12 +-- 18 files changed, 208 insertions(+), 20 deletions(-) create mode 100644 packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3a322d2b..aba43b9e3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Server - Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775` +- Enhance: 連合先のソフトウェア及びバージョン名により配信停止を行えるように `#15727` ## 2025.4.1 diff --git a/locales/index.d.ts b/locales/index.d.ts index 1ee01d52f4..08b1624e25 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -898,6 +898,10 @@ export interface Locale extends ILocale { * ソフトウェア */ "software": string; + /** + * ソフトウェア名 + */ + "softwareName": string; /** * バージョン */ @@ -5871,6 +5875,10 @@ export interface Locale extends ILocale { * サーバー応答なしのため停止中 */ "autoSuspendedForNotResponding": string; + /** + * 配信停止中のソフトウェアであるため停止中 + */ + "softwareSuspended": string; }; }; "_bubbleGame": { @@ -6356,6 +6364,14 @@ export interface Locale extends ILocale { * 一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。 */ "thisSettingWillAutomaticallyOffWhenModeratorsInactive": string; + /** + * 配信停止中のソフトウェア + */ + "deliverSuspendedSoftware": string; + /** + * 脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。 + */ + "deliverSuspendedSoftwareDescription": string; }; "_accountMigration": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index edb7914110..d962369799 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -220,6 +220,7 @@ silenceThisInstance: "サーバーをサイレンス" mediaSilenceThisInstance: "サーバーをメディアサイレンス" operations: "操作" software: "ソフトウェア" +softwareName: "ソフトウェア名" version: "バージョン" metadata: "メタデータ" withNFiles: "{n}つのファイル" @@ -1477,6 +1478,7 @@ _delivery: manuallySuspended: "手動停止中" goneSuspended: "サーバー削除のため停止中" autoSuspendedForNotResponding: "サーバー応答なしのため停止中" + softwareSuspended: "配信停止中のソフトウェアであるため停止中" _bubbleGame: howToPlay: "遊び方" @@ -1615,6 +1617,8 @@ _serverSettings: openRegistration: "アカウントの作成をオープンにする" openRegistrationWarning: "登録を開放することはリスクが伴います。サーバーを常に監視し、トラブルが発生した際にすぐに対応できる体制がある場合のみオンにすることを推奨します。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。" + deliverSuspendedSoftware: "配信停止中のソフトウェア" + deliverSuspendedSoftwareDescription: "脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" diff --git a/packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js b/packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js new file mode 100644 index 0000000000..19983a72bd --- /dev/null +++ b/packages/backend/migration/1743403874305-DeliverSuspendedSoftware.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class DeliverSuspendedSoftware1743403874305 { + name = 'DeliverSuspendedSoftware1743403874305' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "deliverSuspendedSoftware" jsonb NOT NULL DEFAULT '[]'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deliverSuspendedSoftware"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index c4de44df18..3c6dcc6523 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -169,6 +169,7 @@ "sanitize-html": "2.16.0", "secure-json-parse": "3.0.2", "sharp": "0.34.1", + "semver": "7.7.1", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 23fb928ac9..67ec6cc7b0 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -6,10 +6,12 @@ import { URL, domainToASCII } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import RE2 from 're2'; +import semver from 'semver'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { MiMeta } from '@/models/Meta.js'; +import { MiMeta, SoftwareSuspension } from '@/models/Meta.js'; +import { MiInstance } from '@/models/Instance.js'; @Injectable() export class UtilityService { @@ -143,4 +145,20 @@ export class UtilityService { const host = this.extractDbHost(uri); return this.isFederationAllowedHost(host); } + + @bindThis + public isDeliverSuspendedSoftware(software: Pick): SoftwareSuspension | undefined { + if (software.softwareName == null) return undefined; + if (software.softwareVersion == null) { + // software version is null; suspend iff versionRange is * + return this.meta.deliverSuspendedSoftware.find(x => + x.software === software.softwareName + && x.versionRange.trim() === '*'); + } else { + const softwareVersion = software.softwareVersion; + return this.meta.deliverSuspendedSoftware.find(x => + x.software === software.softwareName + && semver.satisfies(softwareVersion, x.versionRange, { includePrerelease: true })); + } + } } diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 284537b986..3688cfb363 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -31,6 +31,7 @@ export class InstanceEntityService { me?: { id: MiUser['id']; } | null | undefined, ): Promise> { const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; + const softwareSuspended = this.utilityService.isDeliverSuspendedSoftware(instance); return { id: instance.id, @@ -41,8 +42,8 @@ export class InstanceEntityService { followingCount: instance.followingCount, followersCount: instance.followersCount, isNotResponding: instance.isNotResponding, - isSuspended: instance.suspensionState !== 'none', - suspensionState: instance.suspensionState, + isSuspended: instance.suspensionState !== 'none' || Boolean(softwareSuspended), + suspensionState: instance.suspensionState === 'none' && softwareSuspended ? 'softwareSuspended' : instance.suspensionState, isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 1fbf5371bc..46f3b2e3c0 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -664,4 +664,14 @@ export class MiMeta { nullable: true, }) public googleAnalyticsMeasurementId: string | null; + + @Column('jsonb', { + default: [], + }) + public deliverSuspendedSoftware: SoftwareSuspension[]; } + +export type SoftwareSuspension = { + software: string, + versionRange: string, +}; diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index 912a0399d8..85f84952f1 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -48,7 +48,7 @@ export const packedFederationInstanceSchema = { suspensionState: { type: 'string', nullable: false, optional: false, - enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'], + enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding', 'softwareSuspended'], }, isBlocked: { type: 'boolean', diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 5a16496011..391ccdac05 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -71,6 +71,15 @@ export class DeliverProcessorService { return 'skip (suspended)'; } + const i = await (this.meta.enableStatsForFederatedInstances + ? this.federatedInstanceService.fetchOrRegister(host) + : this.federatedInstanceService.fetch(host)); + + // suspend server by software + if (i != null && this.utilityService.isDeliverSuspendedSoftware(i)) { + return 'skip (software suspended)'; + } + try { await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest); @@ -79,10 +88,6 @@ export class DeliverProcessorService { // Update instance stats process.nextTick(async () => { - const i = await (this.meta.enableStatsForFederatedInstances - ? this.federatedInstanceService.fetchOrRegister(host) - : this.federatedInstanceService.fetch(host)); - if (i == null) return; if (i.isNotResponding) { diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 53e2b2b237..4a106e7175 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -528,6 +528,24 @@ export const meta = { optional: false, nullable: false, }, }, + deliverSuspendedSoftware: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + software: { + type: 'string', + optional: false, nullable: false, + }, + versionRange: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + }, }, }, } as const; @@ -672,6 +690,7 @@ export default class extends Endpoint { // eslint- urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, federation: instance.federation, federationHosts: instance.federationHosts, + deliverSuspendedSoftware: instance.deliverSuspendedSoftware, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index bc05587668..31eeaa5e38 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -185,6 +185,17 @@ export const paramDef = { type: 'string', }, }, + deliverSuspendedSoftware: { + type: 'array', + items: { + type: 'object', + properties: { + software: { type: 'string' }, + versionRange: { type: 'string' }, + }, + required: ['software', 'versionRange'], + }, + }, }, required: [], } as const; @@ -671,6 +682,10 @@ export default class extends Endpoint { // eslint- set.federation = ps.federation; } + if (ps.deliverSuspendedSoftware !== undefined) { + set.deliverSuspendedSoftware = ps.deliverSuspendedSoftware; + } + if (Array.isArray(ps.federationHosts)) { set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase()); } diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index d7454882b2..5914e243c6 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -230,6 +230,31 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + +
+ {{ i18n.ts._serverSettings.deliverSuspendedSoftwareDescription }} +
+ +
+ + + + + + +
+
+
+
@@ -368,10 +393,12 @@ const urlPreviewForm = useForm({ const federationForm = useForm({ federation: meta.federation, federationHosts: meta.federationHosts.join('\n'), + deliverSuspendedSoftware: meta.deliverSuspendedSoftware, }, async (state) => { await os.apiWithDialog('admin/update-meta', { federation: state.federation, federationHosts: state.federationHosts.split('\n'), + deliverSuspendedSoftware: state.deliverSuspendedSoftware, }); fetchInstance(true); }); @@ -398,4 +425,53 @@ definePage(() => ({ font-size: 0.85em; color: color(from var(--MI_THEME-fg) srgb r g b / 0.75); } + +.metadataRoot { + container-type: inline-size; +} + +.fieldDragItem { + display: flex; + padding: 10px; + align-items: flex-end; + border-radius: 6px; + + /* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */ + @container (max-width: 452px) { + align-items: center; + } +} + +.dragItemHandle { + cursor: grab; + width: 32px; + height: 32px; + margin: 0 8px 0 0; + opacity: 0.5; + flex-shrink: 0; + + &:active { + cursor: grabbing; + } +} + +.dragItemRemove { + @extend .dragItemHandle; + + color: #ff2a2a; + opacity: 1; + cursor: pointer; + + &:hover, &:focus { + opacity: .7; + } + + &:active { + cursor: pointer; + } +} + +.dragItemForm { + flex-grow: 1; +} diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 28ce02b87c..96a43f67e8 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._delivery.stop }} - {{ i18n.ts._delivery.resume }} + {{ i18n.ts._delivery.resume }} {{ i18n.ts.blockThisInstance }} {{ i18n.ts.silenceThisInstance }} {{ i18n.ts.mediaSilenceThisInstance }} @@ -164,7 +164,7 @@ const tab = ref('overview'); const chartSrc = ref('instance-requests'); const meta = ref(null); const instance = ref(null); -const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'>('none'); +const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding' | 'softwareSuspended'>('none'); const isBlocked = ref(false); const isSilenced = ref(false); const isMediaSilenced = ref(false); diff --git a/packages/frontend/src/use/use-form.ts b/packages/frontend/src/use/use-form.ts index 26cca839c3..1c93557413 100644 --- a/packages/frontend/src/use/use-form.ts +++ b/packages/frontend/src/use/use-form.ts @@ -5,6 +5,7 @@ import { computed, reactive, watch } from 'vue'; import type { Reactive } from 'vue'; +import { deepEqual } from '@/utility/deep-equal'; function copy(v: T): T { return JSON.parse(JSON.stringify(v)); @@ -27,7 +28,7 @@ export function useForm>(initialState: T, save: (n watch([currentState, previousState], () => { for (const key in modifiedStates) { - modifiedStates[key] = currentState[key] !== previousState[key]; + modifiedStates[key] = !deepEqual(currentState[key], previousState[key]); } }, { deep: true }); diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index ee200d6890..9c7c611e5b 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -46,6 +46,7 @@ }, "compileOnSave": false, "include": [ + "./lib/**/*.ts", "./src/**/*.ts", "./src/**/*.vue", "./test/**/*.ts", diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index b9d48f02ae..c83e1f1fbe 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4989,7 +4989,7 @@ export type components = { isNotResponding: boolean; isSuspended: boolean; /** @enum {string} */ - suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'; + suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding' | 'softwareSuspended'; isBlocked: boolean; /** @example misskey */ softwareName: string | null; @@ -8765,6 +8765,10 @@ export type operations = { /** @enum {string} */ federation: 'all' | 'specified' | 'none'; federationHosts: string[]; + deliverSuspendedSoftware: { + software: string; + versionRange: string; + }[]; }; }; }; @@ -11431,6 +11435,10 @@ export type operations = { /** @enum {string} */ federation?: 'all' | 'none' | 'specified'; federationHosts?: string[]; + deliverSuspendedSoftware?: { + software: string; + versionRange: string; + }[]; }; }; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b3c95d703..0b21d85a28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -390,6 +390,9 @@ importers: secure-json-parse: specifier: 3.0.2 version: 3.0.2 + semver: + specifier: 7.7.1 + version: 7.7.1 sharp: specifier: 0.34.1 version: 0.34.1 @@ -9564,11 +9567,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -17051,7 +17049,7 @@ snapshots: natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 - semver: 7.6.3 + semver: 7.7.1 vue-eslint-parser: 10.1.3(eslint@9.25.1) xml-name-validator: 4.0.0 @@ -20987,8 +20985,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.6.3: {} - semver@7.7.1: {} send@0.19.0: From c5235a7b2f87d1978be1836708fb4110d6ba9a85 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 2 May 2025 20:25:51 +0900 Subject: [PATCH 09/30] perf(frontend): improve timeline page performance --- packages/frontend/src/router.definition.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts index a0a22b4338..d8bdbb7d02 100644 --- a/packages/frontend/src/router.definition.ts +++ b/packages/frontend/src/router.definition.ts @@ -9,6 +9,7 @@ import type { RouteDef } from '@/lib/nirax.js'; import { $i, iAmModerator } from '@/i.js'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; +import PageTimeline from '@/pages/timeline.vue'; export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({ loader: loader, @@ -21,6 +22,13 @@ function chatPage(...args: Parameters) { } export const ROUTE_DEF = [{ + name: 'index', + path: '/', + component: $i ? PageTimeline : page(() => import('@/pages/welcome.vue')), +}, { + path: '/timeline', + component: PageTimeline, +}, { path: '/@:username/pages/:pageName(*)', component: page(() => import('@/pages/page.vue')), }, { @@ -579,13 +587,6 @@ export const ROUTE_DEF = [{ path: '/reversi/g/:gameId', component: page(() => import('@/pages/reversi/game.vue')), loginRequired: false, -}, { - path: '/timeline', - component: page(() => import('@/pages/timeline.vue')), -}, { - name: 'index', - path: '/', - component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')), }, { // テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする path: '/redirect-test', From df1a3742dd27c80220692f78edb201f17a6bfc58 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 3 May 2025 10:26:40 +0900 Subject: [PATCH 10/30] =?UTF-8?q?feat(frontend):=20=E3=83=9E=E3=82=A6?= =?UTF-8?q?=E3=82=B9=E3=81=A7=E3=82=82=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=92=E5=BC=95=E3=81=A3=E5=BC=B5=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E6=9B=B4=E6=96=B0=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20&=20MkPullToRefresh=E3=81=AE=E3=83=91?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=9E=E3=83=B3=E3=82=B9=E5=90=91?= =?UTF-8?q?=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +- locales/index.d.ts | 4 + locales/ja-JP.yml | 1 + .../src/components/MkNotifications.vue | 4 +- .../src/components/MkPullToRefresh.vue | 167 +++++++++--------- .../frontend/src/components/MkTimeline.vue | 7 +- .../src/pages/settings/preferences.vue | 11 ++ packages/frontend/src/preferences/def.ts | 3 + 8 files changed, 109 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aba43b9e3a..92c3fada72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ - ### Client -- +- Feat: マウスでもタイムラインを引っ張って更新できるように + - アクセシビリティ設定からオフにすることもできます +- Enhance: タイムラインのパフォーマンスを向上 ### Server - Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775` diff --git a/locales/index.d.ts b/locales/index.d.ts index 08b1624e25..f04ec26828 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5709,6 +5709,10 @@ export interface Locale extends ILocale { * デバイス間でインストールしたテーマを同期 */ "enableSyncThemesBetweenDevices": string; + /** + * ひっぱって更新 + */ + "enablePullToRefresh": string; "_chat": { /** * 送信者の名前を表示 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d962369799..11f586e98b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1427,6 +1427,7 @@ _settings: ifOn: "オンのとき" ifOff: "オフのとき" enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期" + enablePullToRefresh: "ひっぱって更新" _chat: showSenderName: "送信者の名前を表示" diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index b8fada1020..177ae0219c 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 8ca690f2ce..bbf1a03c32 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -4,8 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only -->