enhance: ソーシャルTLのDBフォールバックを改善

This commit is contained in:
tai-cha 2026-01-28 01:50:08 +09:00
parent 7ad8861c64
commit dba4a116df
3 changed files with 41 additions and 20 deletions

View File

@ -732,7 +732,7 @@ export class UserFollowingService implements OnModuleInit {
@bindThis
public getFollowees(userId: MiUser['id']) {
return this.followingsRepository.createQueryBuilder('following')
.select('following.followeeId')
.select(['following.followeeId', 'following.withReplies'])
.where('following.followerId = :followerId', { followerId: userId })
.getMany();
}

View File

@ -197,6 +197,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withReplies: boolean,
}, me: MiLocalUser) {
const followees = await this.userFollowingService.getFollowees(me.id);
const followeeIds = followees.map(f => f.followeeId);
const meOrFolloweeIds = [me.id, ...followeeIds];
const followeeWithRepliesIds = followees.filter(f => f.withReplies).map(f => f.followeeId);
const meOrFolloweeWithRepliesIds = [...meOrFolloweeIds, ...followeeWithRepliesIds];
const mutingChannelIds = await this.channelMutingService
.list({ requestUserId: me.id }, { idOnly: true })
@ -207,14 +211,39 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(new Brackets(qb => {
// 自分自身
qb.where('note.userId = :meId', { meId: me.id });
// フォローしている人
if (followees.length > 0) {
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
} else {
qb.where('note.userId = :meId', { meId: me.id });
qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
qb.orWhere(new Brackets(qb => {
qb.where('note.userId IN (:...followeeIds)', { followeeIds });
// 自身に関係ないリプライを除外
if (ps.withReplies) {
qb.andWhere(new Brackets(qb => {
qb.where('note.replyId IS NULL')
.orWhere('note.replyUserId IN (:...meOrFolloweeWithRepliesIds)', { meOrFolloweeWithRepliesIds });
if (followeeWithRepliesIds.length > 0) {
qb.orWhere(new Brackets(qb => {
qb.where('note.userId IN (:...followeeWithRepliesIds)', { followeeWithRepliesIds })
.andWhere(new Brackets(qb => {
qb.where('reply.visibility != \'followers\'')
.orWhere('note.replyUserId IN (:...followeeIds)', { followeeIds });
}));
}));
}
}));
}
}));
}
// ローカルのpublicート
qb.orWhere(new Brackets(qb => {
qb.where('note.visibility = \'public\'')
.andWhere('note.userHost IS NULL');
}));
}))
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
@ -246,7 +275,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
qb // 返信だけど投稿者自身への返信
.where('note.replyId IS NOT NULL')
.andWhere('note.replyUserId = note.userId');
}));
}))
.orWhere('note.replyUserId = :meId', { meId: me.id }); // 自分への返信
}));
}

View File

@ -1664,9 +1664,6 @@ describe('Timelines', () => {
});
test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('following/create', { userId: bob.id }, alice);
@ -1694,16 +1691,13 @@ describe('Timelines', () => {
await waitForPushToTl();
const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
});
test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('following/create', { userId: bob.id }, alice);
@ -1716,7 +1710,7 @@ describe('Timelines', () => {
await waitForPushToTl();
const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
@ -1724,9 +1718,6 @@ describe('Timelines', () => {
});
test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('following/create', { userId: bob.id }, alice);
@ -1738,7 +1729,7 @@ describe('Timelines', () => {
await waitForPushToTl();
const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);