summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/notes
diff options
context:
space:
mode:
authorMar0xy <marie@kaifa.ch>2023-10-13 19:01:17 +0200
committerMar0xy <marie@kaifa.ch>2023-10-13 19:01:17 +0200
commitf8f128b3477e14dc224c7c454f63379ac4c828dd (patch)
tree1ac7ae77a43beb4d2830e61762eb7482e12a6019 /packages/backend/src/server/api/endpoints/notes
parentmerge: timeline 1 (diff)
parentenhance(frontend): TLの返信表示オプションを記憶するように (diff)
downloadsharkey-f8f128b3477e14dc224c7c454f63379ac4c828dd.tar.gz
sharkey-f8f128b3477e14dc224c7c454f63379ac4c828dd.tar.bz2
sharkey-f8f128b3477e14dc224c7c454f63379ac4c828dd.zip
merge: all upstream changes
Diffstat (limited to 'packages/backend/src/server/api/endpoints/notes')
-rw-r--r--packages/backend/src/server/api/endpoints/notes/children.ts21
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/notes/featured.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/notes/global-timeline.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/notes/local-timeline.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/notes/mentions.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts23
10 files changed, 163 insertions, 101 deletions
diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts
index 1a82a4b5d7..1e569d9806 100644
--- a/packages/backend/src/server/api/endpoints/notes/children.ts
+++ b/packages/backend/src/server/api/endpoints/notes/children.ts
@@ -49,16 +49,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
- .andWhere(new Brackets(qb => { qb
- .where('note.replyId = :noteId', { noteId: ps.noteId })
- .orWhere(new Brackets(qb => { qb
- .where('note.renoteId = :noteId', { noteId: ps.noteId })
- .andWhere(new Brackets(qb => { qb
- .where('note.text IS NOT NULL')
- .orWhere('note.fileIds != \'{}\'')
- .orWhere('note.hasPoll = TRUE');
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.replyId = :noteId', { noteId: ps.noteId })
+ .orWhere(new Brackets(qb => {
+ qb
+ .where('note.renoteId = :noteId', { noteId: ps.noteId })
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('note.text IS NOT NULL')
+ .orWhere('note.fileIds != \'{}\'')
+ .orWhere('note.hasPoll = TRUE');
+ }));
}));
- }));
}))
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 37a0525e25..3ae4ac044a 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -57,6 +57,12 @@ export const meta = {
id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a',
},
+ cannotRenoteDueToVisibility: {
+ message: 'You can not Renote due to target visibility.',
+ code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY',
+ id: 'be9529e9-fe72-4de0-ae43-0b363c4938af',
+ },
+
noSuchReplyTarget: {
message: 'No such reply target.',
code: 'NO_SUCH_REPLY_TARGET',
@@ -231,6 +237,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
+
+ if (renote.visibility === 'followers' && renote.userId !== me.id) {
+ // 他人のfollowers noteはreject
+ throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
+ } else if (renote.visibility === 'specified') {
+ // specified / direct noteはreject
+ throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
+ }
}
let reply: MiNote | null = null;
diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts
index 5283b0e0bc..c456874309 100644
--- a/packages/backend/src/server/api/endpoints/notes/featured.ts
+++ b/packages/backend/src/server/api/endpoints/notes/featured.ts
@@ -6,9 +6,9 @@
import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
export const meta = {
tags: ['notes'],
@@ -32,7 +32,7 @@ export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
- offset: { type: 'integer', default: 0 },
+ untilId: { type: 'string', format: 'misskey:id' },
channelId: { type: 'string', nullable: true, format: 'misskey:id' },
},
required: [],
@@ -40,41 +40,53 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ private globalNotesRankingCache: string[] = [];
+ private globalNotesRankingCacheLastFetchedAt = 0;
+
constructor(
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private noteEntityService: NoteEntityService,
- private queryService: QueryService,
+ private featuredService: FeaturedService,
) {
super(meta, paramDef, async (ps, me) => {
- const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
+ let noteIds: string[];
+ if (ps.channelId) {
+ noteIds = await this.featuredService.getInChannelNotesRanking(ps.channelId, 50);
+ } else {
+ if (this.globalNotesRankingCacheLastFetchedAt !== 0 && (Date.now() - this.globalNotesRankingCacheLastFetchedAt < 1000 * 60 * 30)) {
+ noteIds = this.globalNotesRankingCache;
+ } else {
+ noteIds = await this.featuredService.getGlobalNotesRanking(100);
+ this.globalNotesRankingCache = noteIds;
+ this.globalNotesRankingCacheLastFetchedAt = Date.now();
+ }
+ }
+
+ 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);
const query = this.notesRepository.createQueryBuilder('note')
- .addSelect('note.score')
- .where('note.userHost IS NULL')
- .andWhere('note.score > 0')
- .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) })
- .andWhere('note.visibility = \'public\'')
+ .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');
-
- if (ps.channelId) query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
-
- if (me) this.queryService.generateMutedUserQuery(query, me);
- if (me) this.queryService.generateBlockedUserQuery(query, me);
-
- let notes = await query
- .orderBy('note.score', 'DESC')
- .limit(100)
- .getMany();
+ .leftJoinAndSelect('renote.user', 'renoteUser')
+ .leftJoinAndSelect('note.channel', 'channel');
- notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
+ const notes = await query.getMany();
+ notes.sort((a, b) => a.id > b.id ? -1 : 1);
- notes = notes.slice(ps.offset, ps.offset + ps.limit);
+ // TODO: ミュート等考慮
return await this.noteEntityService.packMany(notes, me);
});
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index e5a86905d6..be7557c213 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -67,8 +67,37 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.gtlDisabled);
}
- // TODO?
- return [];
+ //#region Construct query
+ const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
+ ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+ .andWhere('note.visibility = \'public\'')
+ .andWhere('note.channelId IS NULL')
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('renote.user', 'renoteUser');
+
+ if (me) {
+ this.queryService.generateMutedUserQuery(query, me);
+ this.queryService.generateBlockedUserQuery(query, me);
+ this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
+ }
+
+ if (ps.withFiles) {
+ query.andWhere('note.fileIds != \'{}\'');
+ }
+ //#endregion
+
+ const timeline = await query.limit(ps.limit).getMany();
+
+ process.nextTick(() => {
+ if (me) {
+ this.activeUsersChart.read(me);
+ }
+ });
+
+ return await this.noteEntityService.packMany(timeline, me);
});
}
}
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 d6ed3db6e3..8ac5f1b038 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -15,6 +15,7 @@ import { RoleService } from '@/core/RoleService.js';
import { IdService } from '@/core/IdService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { CacheService } from '@/core/CacheService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -54,6 +55,7 @@ export const paramDef = {
includeLocalRenotes: { type: 'boolean', default: true },
withFiles: { type: 'boolean', default: false },
withRenotes: { type: 'boolean', default: true },
+ withReplies: { type: 'boolean', default: false },
},
required: [],
} as const;
@@ -72,8 +74,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private activeUsersChart: ActiveUsersChart,
private idService: IdService,
private cacheService: CacheService,
+ private redisTimelineService: RedisTimelineService,
) {
super(meta, paramDef, async (ps, me) => {
+ const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+ const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
const policies = await this.roleService.getUserPolicies(me.id);
if (!policies.ltlAvailable) {
throw new ApiError(meta.errors.stlDisabled);
@@ -89,28 +95,29 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.cacheService.userBlockedCache.fetch(me.id),
]);
- let timeline: MiNote[] = [];
-
- const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
- let htlNoteIdsRes: [string, string[]][] = [];
- let ltlNoteIdsRes: [string, string[]][] = [];
+ let noteIds: string[];
- if (!ps.sinceId && !ps.sinceDate) {
- htlNoteIdsRes = await this.redisForTimelines.xrevrange(
- ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
- ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
- '-',
- 'COUNT', limit);
- ltlNoteIdsRes = await this.redisForTimelines.xrevrange(
- ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
- ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
- '-',
- 'COUNT', limit);
+ if (ps.withFiles) {
+ const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
+ `homeTimelineWithFiles:${me.id}`,
+ 'localTimelineWithFiles',
+ ], untilId, sinceId);
+ noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
+ } else if (ps.withReplies) {
+ const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.redisTimelineService.getMulti([
+ `homeTimeline:${me.id}`,
+ 'localTimeline',
+ 'localTimelineWithReplies',
+ ], untilId, sinceId);
+ noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds]));
+ } else {
+ const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
+ `homeTimeline:${me.id}`,
+ 'localTimeline',
+ ], untilId, sinceId);
+ noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
}
- const htlNoteIds = htlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
- const ltlNoteIds = ltlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
- let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
noteIds.sort((a, b) => a > b ? -1 : 1);
noteIds = noteIds.slice(0, ps.limit);
@@ -127,7 +134,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
- timeline = await query.getMany();
+ let timeline = await query.getMany();
timeline = timeline.filter(note => {
if (note.userId === me.id) {
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 ed57ca1a30..d10c3bedbf 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -15,6 +15,7 @@ import { RoleService } from '@/core/RoleService.js';
import { IdService } from '@/core/IdService.js';
import { CacheService } from '@/core/CacheService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -44,6 +45,7 @@ export const paramDef = {
properties: {
withFiles: { type: 'boolean', default: false },
withRenotes: { type: 'boolean', default: true },
+ withReplies: { type: 'boolean', default: false },
excludeNsfw: { type: 'boolean', default: false },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
@@ -68,8 +70,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private activeUsersChart: ActiveUsersChart,
private idService: IdService,
private cacheService: CacheService,
+ private redisTimelineService: RedisTimelineService,
) {
super(meta, paramDef, async (ps, me) => {
+ const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+ const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
if (!policies.ltlAvailable) {
throw new ApiError(meta.errors.ltlDisabled);
@@ -85,20 +91,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.cacheService.userBlockedCache.fetch(me.id),
]) : [new Set<string>(), new Set<string>(), new Set<string>()];
- let timeline: MiNote[] = [];
-
- const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
- let noteIdsRes: [string, string[]][] = [];
+ let noteIds: 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 ?? '+',
- '-',
- 'COUNT', limit);
+ if (ps.withFiles) {
+ noteIds = await this.redisTimelineService.get('localTimelineWithFiles', untilId, sinceId);
+ } else {
+ const [nonReplyNoteIds, replyNoteIds] = await this.redisTimelineService.getMulti([
+ 'localTimeline',
+ 'localTimelineWithReplies',
+ ], untilId, sinceId);
+ noteIds = Array.from(new Set([...nonReplyNoteIds, ...replyNoteIds]));
+ noteIds.sort((a, b) => a > b ? -1 : 1);
}
- const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
+ noteIds = noteIds.slice(0, ps.limit);
if (noteIds.length === 0) {
return [];
@@ -113,12 +119,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
- timeline = await query.getMany();
+ let timeline = await query.getMany();
timeline = timeline.filter(note => {
if (me && (note.userId === me.id)) {
return true;
}
+ if (!ps.withReplies && note.replyId && (me == null || note.replyUserId !== me.id)) return false;
if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
if (note.renoteId) {
@@ -131,6 +138,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return true;
});
+ // TODO: フィルタした結果件数が足りなかった場合の対応
+
timeline.sort((a, b) => a.id > b.id ? -1 : 1);
process.nextTick(() => {
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index 65e7bd8cd5..6fab024d17 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -59,9 +59,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.where('following.followerId = :followerId', { followerId: me.id });
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
- .andWhere(new Brackets(qb => { qb
- .where(`'{"${me.id}"}' <@ note.mentions`)
- .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
+ .andWhere(new Brackets(qb => {
+ qb
+ .where(`'{"${me.id}"}' <@ note.mentions`)
+ .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
}))
// Avoid scanning primary key index
.orderBy('CONCAT(note.id)', 'DESC')
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
index 29190af62a..986201e950 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -57,9 +57,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.where('poll.userHost IS NULL')
.andWhere('poll.userId != :meId', { meId: me.id })
.andWhere('poll.noteVisibility = \'public\'')
- .andWhere(new Brackets(qb => { qb
- .where('poll.expiresAt IS NULL')
- .orWhere('poll.expiresAt > :now', { now: new Date() });
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('poll.expiresAt IS NULL')
+ .orWhere('poll.expiresAt > :now', { now: new Date() });
}));
//#region exclude arleady voted polls
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index 2f25d2d7ce..760d52c9db 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -15,6 +15,7 @@ import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
import { CacheService } from '@/core/CacheService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
export const meta = {
tags: ['notes'],
@@ -62,8 +63,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private activeUsersChart: ActiveUsersChart,
private idService: IdService,
private cacheService: CacheService,
+ private redisTimelineService: RedisTimelineService,
) {
super(meta, paramDef, async (ps, me) => {
+ const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+ const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
const [
followings,
userIdsWhoMeMuting,
@@ -76,20 +81,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.cacheService.userBlockedCache.fetch(me.id),
]);
- let timeline: MiNote[] = [];
-
- const limit = ps.limit + (ps.untilId ? 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 ?? '+',
- '-',
- 'COUNT', limit);
- }
-
- const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
+ let noteIds = await this.redisTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
+ noteIds = noteIds.slice(0, ps.limit);
if (noteIds.length === 0) {
return [];
@@ -104,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
- timeline = await query.getMany();
+ let timeline = await query.getMany();
timeline = timeline.filter(note => {
if (note.userId === me.id) {
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 ae2401c4c0..f7ee58264e 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
@@ -15,6 +15,7 @@ import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -79,8 +80,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private activeUsersChart: ActiveUsersChart,
private cacheService: CacheService,
private idService: IdService,
+ private redisTimelineService: RedisTimelineService,
) {
super(meta, paramDef, async (ps, me) => {
+ const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+ const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
const list = await this.userListsRepository.findOneBy({
id: ps.listId,
userId: me.id,
@@ -100,20 +105,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.cacheService.userBlockedCache.fetch(me.id),
]);
- let timeline: MiNote[] = [];
-
- const limit = ps.limit + (ps.untilId ? 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 ?? '+',
- '-',
- 'COUNT', limit);
- }
-
- const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
+ let noteIds = await this.redisTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId);
+ noteIds = noteIds.slice(0, ps.limit);
if (noteIds.length === 0) {
return [];
@@ -128,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
- timeline = await query.getMany();
+ let timeline = await query.getMany();
timeline = timeline.filter(note => {
if (note.userId === me.id) {