From fb3338029bdb72543345c9a69cbe5318b5676233 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2023 17:48:10 +0900 Subject: [PATCH 01/59] refactor --- .../server/api/endpoints/antennas/notes.ts | 12 +++---- .../api/endpoints/notes/hybrid-timeline.ts | 32 ++++++++----------- .../api/endpoints/notes/local-timeline.ts | 16 ++++------ .../server/api/endpoints/notes/timeline.ts | 16 ++++------ .../api/endpoints/notes/user-list-timeline.ts | 16 ++++------ .../src/server/api/endpoints/roles/notes.ts | 12 +++---- 6 files changed, 40 insertions(+), 64 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 63e542cb6..b563f704a 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -86,17 +86,13 @@ export default class extends Endpoint { // eslint- }); const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - const noteIdsRes = await this.redisForTimelines.xrevrange( + + const noteIds = await this.redisForTimelines.xrevrange( `antennaTimeline:${antenna.id}`, ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit); - - if (noteIdsRes.length === 0) { - return []; - } - - const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); if (noteIds.length === 0) { return []; 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 8e7f2a2a9..33ffd42a7 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -92,26 +92,22 @@ export default class extends Endpoint { // eslint- let timeline: MiNote[] = []; const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - let htlNoteIdsRes: [string, string[]][] = []; - let ltlNoteIdsRes: [string, string[]][] = []; - if (!ps.sinceId && !ps.sinceDate) { - [htlNoteIdsRes, ltlNoteIdsRes] = await Promise.all([ - this.redisForTimelines.xrevrange( - ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit), - this.redisForTimelines.xrevrange( - ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit), - ]); - } + const [htlNoteIds, ltlNoteIds] = await Promise.all([ + this.redisForTimelines.xrevrange( + ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)), + this.redisForTimelines.xrevrange( + ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)), + ]); - const htlNoteIds = htlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); - const ltlNoteIds = ltlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds])); noteIds.sort((a, b) => a > b ? -1 : 1); noteIds = noteIds.slice(0, ps.limit); 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 ac8f8d610..b9305e8e2 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -88,17 +88,13 @@ export default class extends Endpoint { // eslint- let timeline: MiNote[] = []; const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - let noteIdsRes: [string, string[]][] = []; - if (!ps.sinceId && !ps.sinceDate) { - noteIdsRes = await this.redisForTimelines.xrevrange( - ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit); - } - - const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); + const noteIds = await this.redisForTimelines.xrevrange( + ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); if (noteIds.length === 0) { return []; diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 744235697..cd2ec8fe2 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -79,17 +79,13 @@ export default class extends Endpoint { // eslint- let timeline: MiNote[] = []; const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - let noteIdsRes: [string, string[]][] = []; - if (!ps.sinceId && !ps.sinceDate) { - noteIdsRes = await this.redisForTimelines.xrevrange( - ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit); - } - - const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); + const noteIds = await this.redisForTimelines.xrevrange( + ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); if (noteIds.length === 0) { return []; 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 d11e9751b..2eec6297c 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 @@ -103,17 +103,13 @@ export default class extends Endpoint { // eslint- let timeline: MiNote[] = []; const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - let noteIdsRes: [string, string[]][] = []; - if (!ps.sinceId && !ps.sinceDate) { - noteIdsRes = await this.redisForTimelines.xrevrange( - ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit); - } - - const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); + const noteIds = await this.redisForTimelines.xrevrange( + ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); if (noteIds.length === 0) { return []; diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index f2533efa3..3f2b31c02 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -79,17 +79,13 @@ export default class extends Endpoint { // eslint- return []; } const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - const noteIdsRes = await this.redisForTimelines.xrevrange( + + const noteIds = await this.redisForTimelines.xrevrange( `roleTimeline:${role.id}`, ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit); - - if (noteIdsRes.length === 0) { - return []; - } - - const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId); + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)); if (noteIds.length === 0) { return []; From 69de8cad7cc0664db10f8459ebb752b75f669a81 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2023 17:57:15 +0900 Subject: [PATCH 02/59] refactor --- .../src/server/api/endpoints/users/notes.ts | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 23c2811fb..4c0f0cc58 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -81,38 +81,36 @@ export default class extends Endpoint { // eslint- let timeline: MiNote[] = []; const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - let noteIdsRes: [string, string[]][] = []; - let repliesNoteIdsRes: [string, string[]][] = []; - let channelNoteIdsRes: [string, string[]][] = []; - if (!ps.sinceId && !ps.sinceDate) { - [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([ - this.redisForTimelines.xrevrange( - ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, + const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([ + this.redisForTimelines.xrevrange( + ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)), + ps.withReplies + ? this.redisForTimelines.xrevrange( + `userTimelineWithReplies:${ps.userId}`, ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit), - ps.withReplies - ? this.redisForTimelines.xrevrange( - `userTimelineWithReplies:${ps.userId}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit) - : Promise.resolve([]), - ps.withChannelNotes - ? this.redisForTimelines.xrevrange( - `userTimelineWithChannel:${ps.userId}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', - 'COUNT', limit) - : Promise.resolve([]), - ]); - } + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)) + : Promise.resolve([]), + ps.withChannelNotes + ? this.redisForTimelines.xrevrange( + `userTimelineWithChannel:${ps.userId}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', + 'COUNT', limit, + ).then(res => res.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId)) + : Promise.resolve([]), + ]); let noteIds = Array.from(new Set([ - ...noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId), - ...repliesNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId), - ...channelNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId), + ...noteIdsRes, + ...repliesNoteIdsRes, + ...channelNoteIdsRes, ])); noteIds.sort((a, b) => a > b ? -1 : 1); noteIds = noteIds.slice(0, ps.limit); From 8c684d539181f030d03db1a6e0f408ae5d8a93ce Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2023 18:00:56 +0900 Subject: [PATCH 03/59] =?UTF-8?q?enhance(backend):=20User=20TL=E3=82=92Red?= =?UTF-8?q?is=E3=81=AB=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=82=8B=E4=BB=A5=E5=89=8D=E3=81=BE=E3=81=A7?= =?UTF-8?q?=E9=81=A1=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #11958 --- .../src/server/api/endpoints/users/notes.ts | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 4c0f0cc58..becc6debb 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -10,10 +10,10 @@ import type { MiNote, NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; -import { GetterService } from '@/server/api/GetterService.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; +import { QueryService } from '@/core/QueryService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -67,7 +67,7 @@ export default class extends Endpoint { // eslint- private notesRepository: NotesRepository, private noteEntityService: NoteEntityService, - private getterService: GetterService, + private queryService: QueryService, private cacheService: CacheService, private idService: IdService, ) { @@ -148,6 +148,43 @@ export default class extends Endpoint { // eslint- timeline.sort((a, b) => a.id > b.id ? -1 : 1); + // fallback to database + if (timeline.length === 0) { + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.userId = :userId', { userId: ps.userId }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('note.channel', 'channel') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + query.andWhere(new Brackets(qb => { + qb.orWhere('note.channelId IS NULL'); + qb.orWhere('channel.isSensitive = false'); + })); + + this.queryService.generateVisibilityQuery(query, me); + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :userId', { userId: ps.userId }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + //#endregion + + timeline = await query.limit(ps.limit).getMany(); + } + return await this.noteEntityService.packMany(timeline, me); }); } From 986623dbdca61473fb4e60ff746c6c77e3a9d442 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2023 18:21:16 +0900 Subject: [PATCH 04/59] fix(backend): fix sql error when featured notes is zero --- .../src/server/api/endpoints/users/featured-notes.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 fdf36a6ae..dec0b7a12 100644 --- a/packages/backend/src/server/api/endpoints/users/featured-notes.ts +++ b/packages/backend/src/server/api/endpoints/users/featured-notes.ts @@ -50,16 +50,16 @@ export default class extends Endpoint { // eslint- super(meta, paramDef, async (ps, me) => { let noteIds = await this.featuredService.getPerUserNotesRanking(ps.userId, 50); - if (noteIds.length === 0) { - return []; - } - noteIds.sort((a, b) => a > b ? -1 : 1); if (ps.untilId) { noteIds = noteIds.filter(id => id < ps.untilId!); } noteIds = noteIds.slice(0, ps.limit); + if (noteIds.length === 0) { + return []; + } + const query = this.notesRepository.createQueryBuilder('note') .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') From 6d5e18aa8dbfc5fbf15b12d0f711921df03e9372 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2023 18:21:30 +0900 Subject: [PATCH 05/59] 2023.10.0-beta.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df51a05f8..d298fbed9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.10.0-beta.6", + "version": "2023.10.0-beta.7", "codename": "nasubi", "repository": { "type": "git", From 04c8a7077fc60c415b3004a38416c5346e62b9bb Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2023 20:27:35 +0900 Subject: [PATCH 06/59] fix of 8c684d5391 --- .../src/server/api/endpoints/users/notes.ts | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index becc6debb..cd0570373 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -78,8 +78,6 @@ export default class extends Endpoint { // eslint- this.cacheService.userMutingsCache.fetch(me.id), ]) : [new Set()]; - let timeline: MiNote[] = []; - const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([ @@ -115,41 +113,40 @@ export default class extends Endpoint { // eslint- noteIds.sort((a, b) => a > b ? -1 : 1); noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (noteIds.length > 0) { + const isFollowing = me ? Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId) : false; - const isFollowing = me ? Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId) : false; + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - timeline = await query.getMany(); + timeline = timeline.filter(note => { + if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false; - timeline = timeline.filter(note => { - if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false; - - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (ps.withRenotes === false) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (ps.withRenotes === false) return false; + } } - } - if (note.visibility === 'followers' && !isFollowing) return false; + if (note.visibility === 'followers' && !isFollowing) return false; - return true; - }); + return true; + }); - timeline.sort((a, b) => a.id > b.id ? -1 : 1); + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + return await this.noteEntityService.packMany(timeline, me); + } else { + // fallback to database - // fallback to database - if (timeline.length === 0) { //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.userId = :userId', { userId: ps.userId }) @@ -182,10 +179,10 @@ export default class extends Endpoint { // eslint- } //#endregion - timeline = await query.limit(ps.limit).getMany(); - } + const timeline = await query.limit(ps.limit).getMany(); - return await this.noteEntityService.packMany(timeline, me); + return await this.noteEntityService.packMany(timeline, me); + } }); } } From 7cd9a90f2677681bc697148db41a8270baf6346d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Oct 2023 07:51:10 +0900 Subject: [PATCH 07/59] perf(backend): use HyperLogLog instead of Set to improve hashtag chart performance --- packages/backend/src/core/HashtagService.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 1a06767da..ddff28359 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -174,16 +174,15 @@ export class HashtagService { const redisPipeline = this.redisClient.pipeline(); - // TODO: これらの Set は Bloom Filter を使うようにしても良さそう - // チャート用 - redisPipeline.sadd(`hashtagUsers:${hashtag}:${window}`, userId); + redisPipeline.pfadd(`hashtagUsers:${hashtag}:${window}`, userId); redisPipeline.expire(`hashtagUsers:${hashtag}:${window}`, 60 * 60 * 24 * 3, // 3日間 'NX', // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定 ); // ユニークカウント用 + // TODO: Bloom Filter を使うようにしても良さそう redisPipeline.sadd(`hashtagUsers:${hashtag}`, userId); redisPipeline.expire(`hashtagUsers:${hashtag}`, 60 * 60, // 1時間 @@ -202,7 +201,7 @@ export class HashtagService { for (let i = 0; i < range; i++) { const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`; - redisPipeline.scard(`hashtagUsers:${hashtag}:${window}`); + redisPipeline.pfcount(`hashtagUsers:${hashtag}:${window}`); now.setMinutes(now.getMinutes() - (i * 10), 0, 0); } @@ -223,7 +222,7 @@ export class HashtagService { for (let i = 0; i < range; i++) { const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`; for (const hashtag of hashtags) { - redisPipeline.scard(`hashtagUsers:${hashtag}:${window}`); + redisPipeline.pfcount(`hashtagUsers:${hashtag}:${window}`); } now.setMinutes(now.getMinutes() - (i * 10), 0, 0); } From 2859cbab913dc836f4beb55cf967d1feeb87e6c3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Oct 2023 08:10:28 +0900 Subject: [PATCH 08/59] =?UTF-8?q?perf(backend):=20WebSocket=E6=8E=A5?= =?UTF-8?q?=E7=B6=9A=E3=81=8C=E5=A4=9A=E3=81=84=E5=A0=B4=E5=90=88=E3=81=AE?= =?UTF-8?q?=E3=83=91=E3=83=95=E3=82=A9=E3=83=BC=E3=83=9E=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=82=92=E5=90=91=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https: //github.com/MisskeyIO/misskey/pull/176 Co-Authored-By: KOBA789 --- CHANGELOG.md | 1 + .../src/core/entities/NoteEntityService.ts | 12 ++- packages/backend/src/misc/loader.ts | 52 +++++++++++ packages/backend/test/unit/misc/loader.ts | 88 +++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/misc/loader.ts create mode 100644 packages/backend/test/unit/misc/loader.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f2b8ac44..4f9ba288d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - Enhance: タイムライン取得時のパフォーマンスを大幅に向上 - Enhance: ハイライト取得時のパフォーマンスを大幅に向上 - Enhance: トレンドハッシュタグ取得時のパフォーマンスを大幅に向上 +- Enhance: WebSocket接続が多い場合のパフォーマンスを向上 - Enhance: 不要なPostgreSQLのインデックスを削除しパフォーマンスを向上 - Fix: 連合なしアンケートに投票をするとUpdateがリモートに配信されてしまうのを修正 diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index e45a7992b..abe4aafd6 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -17,6 +17,7 @@ import type { MiNoteReaction } from '@/models/NoteReaction.js'; import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; +import { DebounceLoader } from '@/misc/loader.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -29,6 +30,7 @@ export class NoteEntityService implements OnModuleInit { private driveFileEntityService: DriveFileEntityService; private customEmojiService: CustomEmojiService; private reactionService: ReactionService; + private noteLoader = new DebounceLoader(this.findNoteOrFail); constructor( private moduleRef: ModuleRef, @@ -285,7 +287,7 @@ export class NoteEntityService implements OnModuleInit { }, options); const meId = me ? me.id : null; - const note = typeof src === 'object' ? src : await this.notesRepository.findOneOrFail({ where: { id: src }, relations: ['user'] }); + const note = typeof src === 'object' ? src : await this.noteLoader.load(src); const host = note.userHost; let text = note.text; @@ -450,4 +452,12 @@ export class NoteEntityService implements OnModuleInit { } return emojis.filter(x => x.name != null && x.host != null) as { name: string; host: string; }[]; } + + @bindThis + private findNoteOrFail(id: string): Promise { + return this.notesRepository.findOneOrFail({ + where: { id }, + relations: ['user'], + }); + } } diff --git a/packages/backend/src/misc/loader.ts b/packages/backend/src/misc/loader.ts new file mode 100644 index 000000000..25f7b54d3 --- /dev/null +++ b/packages/backend/src/misc/loader.ts @@ -0,0 +1,52 @@ +export type FetchFunction = (key: K) => Promise; + +type ResolveReject = Parameters>[0]>; + +type ResolverPair = { + resolve: ResolveReject[0]; + reject: ResolveReject[1]; +}; + +export class DebounceLoader { + private resolverMap = new Map>(); + private promiseMap = new Map>(); + private resolvedPromise = Promise.resolve(); + constructor(private loadFn: FetchFunction) {} + + public load(key: K): Promise { + const promise = this.promiseMap.get(key); + if (typeof promise !== 'undefined') { + return promise; + } + + const isFirst = this.promiseMap.size === 0; + const newPromise = new Promise((resolve, reject) => { + this.resolverMap.set(key, { resolve, reject }); + }); + this.promiseMap.set(key, newPromise); + + if (isFirst) { + this.enqueueDebouncedLoadJob(); + } + + return newPromise; + } + + private runDebouncedLoad(): void { + const resolvers = [...this.resolverMap]; + this.resolverMap.clear(); + this.promiseMap.clear(); + + for (const [key, { resolve, reject }] of resolvers) { + this.loadFn(key).then(resolve, reject); + } + } + + private enqueueDebouncedLoadJob(): void { + this.resolvedPromise.then(() => { + process.nextTick(() => { + this.runDebouncedLoad(); + }); + }); + } +} diff --git a/packages/backend/test/unit/misc/loader.ts b/packages/backend/test/unit/misc/loader.ts new file mode 100644 index 000000000..fa3795095 --- /dev/null +++ b/packages/backend/test/unit/misc/loader.ts @@ -0,0 +1,88 @@ +import { DebounceLoader } from '@/misc/loader.js'; + +class Mock { + loadCountByKey = new Map(); + load = async (key: number): Promise => { + const count = this.loadCountByKey.get(key); + if (typeof count === 'undefined') { + this.loadCountByKey.set(key, 1); + } else { + this.loadCountByKey.set(key, count + 1); + } + return key * 2; + }; + reset() { + this.loadCountByKey.clear(); + } +} + +describe(DebounceLoader, () => { + describe('single request', () => { + it('loads once', async () => { + const mock = new Mock(); + const loader = new DebounceLoader(mock.load); + expect(await loader.load(7)).toBe(14); + expect(mock.loadCountByKey.size).toBe(1); + expect(mock.loadCountByKey.get(7)).toBe(1); + }); + }); + + describe('two duplicated requests at same time', () => { + it('loads once', async () => { + const mock = new Mock(); + const loader = new DebounceLoader(mock.load); + const [v1, v2] = await Promise.all([ + loader.load(7), + loader.load(7), + ]); + expect(v1).toBe(14); + expect(v2).toBe(14); + expect(mock.loadCountByKey.size).toBe(1); + expect(mock.loadCountByKey.get(7)).toBe(1); + }); + }); + + describe('two different requests at same time', () => { + it('loads twice', async () => { + const mock = new Mock(); + const loader = new DebounceLoader(mock.load); + const [v1, v2] = await Promise.all([ + loader.load(7), + loader.load(13), + ]); + expect(v1).toBe(14); + expect(v2).toBe(26); + expect(mock.loadCountByKey.size).toBe(2); + expect(mock.loadCountByKey.get(7)).toBe(1); + expect(mock.loadCountByKey.get(13)).toBe(1); + }); + }); + + describe('non-continuous same two requests', () => { + it('loads twice', async () => { + const mock = new Mock(); + const loader = new DebounceLoader(mock.load); + expect(await loader.load(7)).toBe(14); + expect(mock.loadCountByKey.size).toBe(1); + expect(mock.loadCountByKey.get(7)).toBe(1); + mock.reset(); + expect(await loader.load(7)).toBe(14); + expect(mock.loadCountByKey.size).toBe(1); + expect(mock.loadCountByKey.get(7)).toBe(1); + }); + }); + + describe('non-continuous different two requests', () => { + it('loads twice', async () => { + const mock = new Mock(); + const loader = new DebounceLoader(mock.load); + expect(await loader.load(7)).toBe(14); + expect(mock.loadCountByKey.size).toBe(1); + expect(mock.loadCountByKey.get(7)).toBe(1); + mock.reset(); + expect(await loader.load(13)).toBe(26); + expect(mock.loadCountByKey.size).toBe(1); + expect(mock.loadCountByKey.get(13)).toBe(1); + }); + }); +}); From cd8fda50c81defd8e6700dbb38c1281e914fb34b Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Oct 2023 08:25:37 +0900 Subject: [PATCH 09/59] fix(backend): fix of 8c684d5391 --- packages/backend/src/server/api/endpoints/users/notes.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index cd0570373..71aa6e661 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -157,10 +157,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - query.andWhere(new Brackets(qb => { - qb.orWhere('note.channelId IS NULL'); - qb.orWhere('channel.isSensitive = false'); - })); + if (!ps.withChannelNotes) { + query.andWhere('note.channelId IS NULL'); + } this.queryService.generateVisibilityQuery(query, me); From 308745f6de00380f14ea57a99f798967a180f174 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Oct 2023 08:42:56 +0900 Subject: [PATCH 10/59] 2023.10.0-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d298fbed9..2255c1c37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.10.0-beta.7", + "version": "2023.10.0-beta.8", "codename": "nasubi", "repository": { "type": "git", From 8e0fb230685178d56c4d51a81e9147e7d043c59c Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Oct 2023 13:46:02 +0900 Subject: [PATCH 11/59] =?UTF-8?q?fix(backend):=20=E5=90=8C=E3=81=98?= =?UTF-8?q?=E7=A8=AE=E9=A1=9E=E3=81=AETL=E3=81=AE=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=AA=E3=83=BC=E3=83=9F=E3=83=B3=E3=82=B0=E3=82=92=E8=A4=87?= =?UTF-8?q?=E6=95=B0=E6=8E=A5=E7=B6=9A=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #11985 --- CHANGELOG.md | 1 + .../backend/src/server/api/stream/channels/global-timeline.ts | 2 +- .../backend/src/server/api/stream/channels/home-timeline.ts | 2 +- .../backend/src/server/api/stream/channels/hybrid-timeline.ts | 2 +- .../backend/src/server/api/stream/channels/local-timeline.ts | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f9ba288d..11118b10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ - Enhance: WebSocket接続が多い場合のパフォーマンスを向上 - Enhance: 不要なPostgreSQLのインデックスを削除しパフォーマンスを向上 - Fix: 連合なしアンケートに投票をするとUpdateがリモートに配信されてしまうのを修正 +- Fix: 同じ種類のTLのストリーミングを複数接続できない問題を修正 ## 2023.9.3 ### General diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index f0ac50349..552506fbb 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -16,7 +16,7 @@ import Channel from '../channel.js'; class GlobalTimelineChannel extends Channel { public readonly chName = 'globalTimeline'; - public static shouldShare = true; + public static shouldShare = false; public static requireCredential = false; private withRenotes: boolean; diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 1c1b1c2ae..e377246f3 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -14,7 +14,7 @@ import Channel from '../channel.js'; class HomeTimelineChannel extends Channel { public readonly chName = 'homeTimeline'; - public static shouldShare = true; + public static shouldShare = false; public static requireCredential = true; private withRenotes: boolean; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index e2f4817bf..348be9c7e 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -16,7 +16,7 @@ import Channel from '../channel.js'; class HybridTimelineChannel extends Channel { public readonly chName = 'hybridTimeline'; - public static shouldShare = true; + public static shouldShare = false; public static requireCredential = true; private withRenotes: boolean; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index ca563b5d1..849cbfa56 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -15,7 +15,7 @@ import Channel from '../channel.js'; class LocalTimelineChannel extends Channel { public readonly chName = 'localTimeline'; - public static shouldShare = true; + public static shouldShare = false; public static requireCredential = false; private withRenotes: boolean; From bb9f04d586a07668ff325fd906e4e77a9dea9ff4 Mon Sep 17 00:00:00 2001 From: Srgr0 <66754887+Srgr0@users.noreply.github.com> Date: Sun, 8 Oct 2023 13:47:45 +0900 Subject: [PATCH 12/59] Set http header for CORS in nodeinfo page (#11988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add Access-Control-Allow-Origin header * WellKnownServerService.tsに合わせる * update changelog --------- Co-authored-by: syuilo --- CHANGELOG.md | 1 + packages/backend/src/server/NodeinfoServerService.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11118b10b..e72777d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ - Enhance: WebSocket接続が多い場合のパフォーマンスを向上 - Enhance: 不要なPostgreSQLのインデックスを削除しパフォーマンスを向上 - Fix: 連合なしアンケートに投票をするとUpdateがリモートに配信されてしまうのを修正 +- Fix: nodeinfoにおいてCORS用のヘッダーが設定されていないのを修正 - Fix: 同じ種類のTLのストリーミングを複数接続できない問題を修正 ## 2023.9.3 diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index dd2b7882a..79b0a57f2 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -135,7 +135,11 @@ export class NodeinfoServerService { .type( 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"', ) - .header('Cache-Control', 'public, max-age=600'); + .header('Cache-Control', 'public, max-age=600') + .header('Access-Control-Allow-Headers', 'Accept') + .header('Access-Control-Allow-Methods', 'GET, OPTIONS') + .header('Access-Control-Allow-Origin', '*') + .header('Access-Control-Expose-Headers', 'Vary'); return { version: '2.1', ...base }; }); @@ -148,7 +152,11 @@ export class NodeinfoServerService { .type( 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"', ) - .header('Cache-Control', 'public, max-age=600'); + .header('Cache-Control', 'public, max-age=600') + .header('Access-Control-Allow-Headers', 'Accept') + .header('Access-Control-Allow-Methods', 'GET, OPTIONS') + .header('Access-Control-Allow-Origin', '*') + .header('Access-Control-Expose-Headers', 'Vary'); return { version: '2.0', ...base }; }); From f37a3eff796c515efa51ab035f1096b124bdff9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 8 Oct 2023 13:48:12 +0900 Subject: [PATCH 13/59] =?UTF-8?q?(fix)=20=E3=83=A1=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=82=A2=E3=83=89=E3=83=AC=E3=82=B9=E8=AA=8D=E8=A8=BC=E5=A4=B1?= =?UTF-8?q?=E6=95=97=E6=99=82=E3=81=AB=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=82=92=E8=A1=A8=E7=A4=BA=20(#11986)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/ServerService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 0e4a5ece3..e598b91e5 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -199,10 +199,10 @@ export class ServerService implements OnApplicationShutdown { includeSecrets: true, })); - reply.code(200); - return 'Verify succeeded!'; + reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。'); + return; } else { - reply.code(404); + reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください'); return; } }); From 774bf6a55e12b68597b99d725c637aff1ea4d4d9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Oct 2023 14:01:40 +0900 Subject: [PATCH 14/59] enhance(frontend): make default volume of video 30% --- CHANGELOG.md | 1 + .../frontend/src/components/MkMediaBanner.vue | 15 ++++++--------- packages/frontend/src/components/MkMediaVideo.vue | 11 ++++++++++- packages/frontend/src/scripts/sound.ts | 4 ---- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e72777d23..ab09b9a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ ### Client - Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に +- Enhance: 動画再生時のデフォルトボリュームを30%に - Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正 ### Server diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 10b2ac9ec..69da1a746 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -17,7 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only :title="media.name" controls preload="metadata" - @volumechange="volumechange" /> diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 751b5f757..43c64b4c8 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only