Skip to content

Commit

Permalink
Merge branch 'develop' into pr/12526
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Dec 3, 2023
2 parents 8c67ae3 + 2eb86e0 commit 069c210
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
- Fix: 招待コードが使い回せる問題を修正
- Fix: 特定の条件下でチャンネルやユーザーのノート一覧に最新のノートが表示されなくなる問題を修正
- Fix: 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正
- Fix: リストタイムラインにてミュートが機能しないケースがある問題と、チャンネル投稿がストリーミングで流れてきてしまう問題を修正 #10443

## 2023.11.1

Expand Down
9 changes: 5 additions & 4 deletions packages/backend/src/misc/is-instance-muted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { MiNote } from '@/models/Note.js';
import type { Packed } from './json-schema.js';

export function isInstanceMuted(note: Packed<'Note'>, mutedInstances: Set<string>): boolean {
if (mutedInstances.has(note.user.host ?? '')) return true;
if (mutedInstances.has(note.reply?.user.host ?? '')) return true;
if (mutedInstances.has(note.renote?.user.host ?? '')) return true;
export function isInstanceMuted(note: Packed<'Note'> | MiNote, mutedInstances: Set<string>): boolean {
if (mutedInstances.has(note.user?.host ?? '')) return true;
if (mutedInstances.has(note.reply?.user?.host ?? '')) return true;
if (mutedInstances.has(note.renote?.user?.host ?? '')) return true;

return false;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/server/ServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ export class ServerService implements OnApplicationShutdown {
return;
}

const name = path.split('@')[0].replace('.webp', '');
const host = path.split('@')[1]?.replace('.webp', '');
const name = path.split('@')[0].replace(/\.webp$/i, '');
const host = path.split('@')[1]?.replace(/\.webp$/i, '');

const emoji = await this.emojisRepository.findOneBy({
// `@.` is the spec of ReactionService.decodeReaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { QueryService } from '@/core/QueryService.js';
import { MiLocalUser } from '@/models/User.js';
import { MetaService } from '@/core/MetaService.js';
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import { ApiError } from '../../error.js';

export const meta = {
Expand Down Expand Up @@ -124,10 +125,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userIdsWhoMeMuting,
userIdsWhoMeMutingRenotes,
userIdsWhoBlockingMe,
userMutedInstances,
] = await Promise.all([
this.cacheService.userMutingsCache.fetch(me.id),
this.cacheService.renoteMutingsCache.fetch(me.id),
this.cacheService.userBlockedCache.fetch(me.id),
this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)),
]);

const timeline = await this.fanoutTimelineEndpointService.timeline({
Expand All @@ -150,6 +153,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.withRenotes === false) return false;
}
}
if (isInstanceMuted(note, userMutedInstances)) return false;

return true;
},
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/server/api/endpoints/users/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
redisTimelines,
useDbFallback: true,
noteFilter: note => {
if (ps.withFiles && note.fileIds.length === 0) {
return false;
}
if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false;

if (note.renoteId) {
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/stream/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default class Connection {
public userIdsWhoMeMuting: Set<string> = new Set();
public userIdsWhoBlockingMe: Set<string> = new Set();
public userIdsWhoMeMutingRenotes: Set<string> = new Set();
public userMutedInstances: Set<string> = new Set();
private fetchIntervalId: NodeJS.Timeout | null = null;

constructor(
Expand Down Expand Up @@ -69,6 +70,7 @@ export default class Connection {
this.userIdsWhoMeMuting = userIdsWhoMeMuting;
this.userIdsWhoBlockingMe = userIdsWhoBlockingMe;
this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes;
this.userMutedInstances = new Set(userProfile.mutedInstances);
}

@bindThis
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/server/api/stream/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export default abstract class Channel {
return this.connection.userIdsWhoBlockingMe;
}

protected get userMutedInstances() {
return this.connection.userMutedInstances;
}

protected get followingChannels() {
return this.connection.followingChannels;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/backend/src/server/api/stream/channels/user-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

import { Inject, Injectable } from '@nestjs/common';
import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import Channel from '../channel.js';

class UserListChannel extends Channel {
Expand Down Expand Up @@ -80,6 +80,9 @@ class UserListChannel extends Channel {
private async onNote(note: Packed<'Note'>) {
const isMe = this.user!.id === note.userId;

// チャンネル投稿は無視する
if (note.channelId) return;

if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;

if (!Object.hasOwn(this.membershipsMap, note.userId)) return;
Expand Down Expand Up @@ -115,6 +118,9 @@ class UserListChannel extends Channel {
}
}

// 流れてきたNoteがミュートしているインスタンスに関わるものだったら無視する
if (isInstanceMuted(note, this.userMutedInstances)) return;

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
108 changes: 107 additions & 1 deletion packages/backend/test/e2e/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ process.env.NODE_ENV = 'test';

import * as assert from 'assert';
import { MiFollowing } from '@/models/Following.js';
import { connectStream, signup, api, post, startServer, initTestDb, waitFire } from '../utils.js';
import { signup, api, post, startServer, initTestDb, waitFire } from '../utils.js';
import type { INestApplicationContext } from '@nestjs/common';
import type * as misskey from 'misskey-js';

Expand All @@ -34,12 +34,16 @@ describe('Streaming', () => {
let ayano: misskey.entities.MeSignup;
let kyoko: misskey.entities.MeSignup;
let chitose: misskey.entities.MeSignup;
let kanako: misskey.entities.MeSignup;

// Remote users
let akari: misskey.entities.MeSignup;
let chinatsu: misskey.entities.MeSignup;
let takumi: misskey.entities.MeSignup;

let kyokoNote: any;
let kanakoNote: any;
let takumiNote: any;
let list: any;

beforeAll(async () => {
Expand All @@ -50,18 +54,25 @@ describe('Streaming', () => {
ayano = await signup({ username: 'ayano' });
kyoko = await signup({ username: 'kyoko' });
chitose = await signup({ username: 'chitose' });
kanako = await signup({ username: 'kanako' });

akari = await signup({ username: 'akari', host: 'example.com' });
chinatsu = await signup({ username: 'chinatsu', host: 'example.com' });
takumi = await signup({ username: 'takumi', host: 'example.com' });

kyokoNote = await post(kyoko, { text: 'foo' });
kanakoNote = await post(kanako, { text: 'hoge' });
takumiNote = await post(takumi, { text: 'piyo' });

// Follow: ayano => kyoko
await api('following/create', { userId: kyoko.id }, ayano);

// Follow: ayano => akari
await follow(ayano, akari);

// Mute: chitose => kanako
await api('mute/create', { userId: kanako.id }, chitose);

// List: chitose => ayano, kyoko
list = await api('users/lists/create', {
name: 'my list',
Expand All @@ -76,6 +87,11 @@ describe('Streaming', () => {
listId: list.id,
userId: kyoko.id,
}, chitose);

await api('users/lists/push', {
listId: list.id,
userId: takumi.id,
}, chitose);
}, 1000 * 60 * 2);

afterAll(async () => {
Expand Down Expand Up @@ -452,6 +468,96 @@ describe('Streaming', () => {

assert.strictEqual(fired, false);
});

// #10443
test('チャンネル投稿は流れない', async () => {
// リスインしている kyoko が 任意のチャンネルに投降した時の動きを見たい
const fired = await waitFire(
chitose, 'userList',
() => api('notes/create', { text: 'foo', channelId: 'dummy' }, kyoko),
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
{ listId: list.id },
);

assert.strictEqual(fired, false);
});

// #10443
test('ミュートしているユーザへのリプライがリストTLに流れない', async () => {
// chitose が kanako をミュートしている状態で、リスインしている kyoko が kanako にリプライした時の動きを見たい
const fired = await waitFire(
chitose, 'userList',
() => api('notes/create', { text: 'foo', replyId: kanakoNote.id }, kyoko),
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
{ listId: list.id },
);

assert.strictEqual(fired, false);
});

// #10443
test('ミュートしているユーザの投稿をリノートしたときリストTLに流れない', async () => {
// chitose が kanako をミュートしている状態で、リスインしている kyoko が kanako のノートをリノートした時の動きを見たい
const fired = await waitFire(
chitose, 'userList',
() => api('notes/create', { renoteId: kanakoNote.id }, kyoko),
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
{ listId: list.id },
);

assert.strictEqual(fired, false);
});

// #10443
test('ミュートしているサーバのノートがリストTLに流れない', async () => {
await api('/i/update', {
mutedInstances: ['example.com'],
}, chitose);

// chitose が example.com をミュートしている状態で、リスインしている takumi が ノートした時の動きを見たい
const fired = await waitFire(
chitose, 'userList',
() => api('notes/create', { text: 'foo' }, takumi),
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
{ listId: list.id },
);

assert.strictEqual(fired, false);
});

// #10443
test('ミュートしているサーバのノートに対するリプライがリストTLに流れない', async () => {
await api('/i/update', {
mutedInstances: ['example.com'],
}, chitose);

// chitose が example.com をミュートしている状態で、リスインしている kyoko が takumi のノートにリプライした時の動きを見たい
const fired = await waitFire(
chitose, 'userList',
() => api('notes/create', { text: 'foo', replyId: takumiNote.id }, kyoko),
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
{ listId: list.id },
);

assert.strictEqual(fired, false);
});

// #10443
test('ミュートしているサーバのノートに対するリノートがリストTLに流れない', async () => {
await api('/i/update', {
mutedInstances: ['example.com'],
}, chitose);

// chitose が example.com をミュートしている状態で、リスインしている kyoko が takumi のノートをリノートした時の動きを見たい
const fired = await waitFire(
chitose, 'userList',
() => api('notes/create', { renoteId: takumiNote.id }, kyoko),
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
{ listId: list.id },
);

assert.strictEqual(fired, false);
});
});

// XXX: QueryFailedError: duplicate key value violates unique constraint "IDX_347fec870eafea7b26c8a73bac"
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/scripts/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const getBuiltinThemes = () => Promise.all(
'd-cherry',
'd-ice',
'd-u0',
].map(name => import(`${__dirname}/../themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
].map(name => import(`../themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
);

export const getBuiltinThemesRef = () => {
Expand Down

0 comments on commit 069c210

Please sign in to comment.