Merge branch 'misskey-dev:develop' into develop

This commit is contained in:
老兄 2023-10-04 23:13:13 +08:00 committed by GitHub
commit 7584661318
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 187 additions and 163 deletions

View File

@ -25,6 +25,8 @@
- Feat: ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました
- Feat: ユーザーリスト内のメンバーごとに他ユーザーへの返信をユーザーリストタイムラインに含めるか設定可能になりました
- Enhance: ソフトワードミュートとハードワードミュートは統合されました
- Enhance: モデレーションログ機能の強化
- Enhance: ローカリゼーションの更新
### Client
- Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に

View File

@ -1248,8 +1248,6 @@ _sfx:
note: "الملاحظات"
noteMy: "ملاحظتي"
notification: "الإشعارات"
chat: "المحادثة"
chatBg: "المحادثة (الخلفية)"
antenna: "الهوائيات"
channel: "إشعارات القنات"
_ago:

View File

@ -1020,8 +1020,6 @@ _sfx:
note: "নোটগুলি"
noteMy: "নোট (আপনার)"
notification: "বিজ্ঞপ্তি"
chat: "চ্যাট"
chatBg: "চ্যাট (ব্যাকগ্রাউন্ড)"
antenna: "অ্যান্টেনাগুলি"
channel: "চ্যানেলের বিজ্ঞপ্তি"
_ago:

View File

@ -398,7 +398,6 @@ _theme:
_sfx:
note: "Notes"
notification: "Notificacions"
chat: "Xat"
antenna: "Antenes"
_2fa:
renewTOTPCancel: "No, gràcies"

View File

@ -1647,8 +1647,6 @@ _sfx:
note: "Poznámky"
noteMy: "Moje poznámka"
notification: "Oznámení"
chat: "Zprávy"
chatBg: "Chat (Pozadí)"
antenna: "Antény"
channel: "Oznámení kanálu"
_ago:

View File

@ -1697,8 +1697,6 @@ _sfx:
note: "Notizen"
noteMy: "Meine Notizen"
notification: "Benachrichtigungen"
chat: "Chat"
chatBg: "Chat (Hintergrund)"
antenna: "Antennen"
channel: "Kanalbenachrichtigung"
_ago:

View File

@ -303,8 +303,6 @@ _theme:
_sfx:
note: "Σημειώματα"
notification: "Ειδοποιήσεις"
chat: "Συνομιλία"
chatBg: "Συνομιλία (Παρασκήνιο)"
antenna: "Αντένες"
channel: "Ειδοποιήσεις καναλιών"
_ago:

View File

@ -1697,8 +1697,6 @@ _sfx:
note: "New note"
noteMy: "Own note"
notification: "Notifications"
chat: "Chat"
chatBg: "Chat (Background)"
antenna: "Antennas"
channel: "Channel notifications"
_ago:

View File

@ -1691,8 +1691,6 @@ _sfx:
note: "Notas"
noteMy: "Nota (a mí mismo)"
notification: "Notificaciones"
chat: "Chat"
chatBg: "Chat (Fondo)"
antenna: "Antena receptora"
channel: "Notificaciones del canal"
_ago:

View File

@ -1355,8 +1355,6 @@ _sfx:
note: "Nouvelle note"
noteMy: "Ma note"
notification: "Notifications"
chat: "Discuter"
chatBg: "Discussion (arrière-plan)"
antenna: "Réception de lantenne"
channel: "Notifications de canal"
_ago:

View File

@ -1652,8 +1652,6 @@ _sfx:
note: "Catatan"
noteMy: "Catatan (Saya)"
notification: "Notifikasi"
chat: "Pesan"
chatBg: "Obrolan (Latar Belakang)"
antenna: "Penerimaan Antenna"
channel: "Notifikasi Kanal"
_ago:

2
locales/index.d.ts vendored
View File

@ -1808,8 +1808,6 @@ export interface Locale {
"note": string;
"noteMy": string;
"notification": string;
"chat": string;
"chatBg": string;
"antenna": string;
"channel": string;
};

View File

@ -1692,8 +1692,6 @@ _sfx:
note: "Nota"
noteMy: "Mia nota"
notification: "Notifiche"
chat: "Messaggi"
chatBg: "Chat (sfondo)"
antenna: "Ricezione dell'antenna"
channel: "Notifiche di canale"
_ago:

View File

@ -1725,8 +1725,6 @@ _sfx:
note: "ノート"
noteMy: "ノート(自分)"
notification: "通知"
chat: "チャット"
chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信"
channel: "チャンネル通知"

View File

@ -1674,8 +1674,6 @@ _sfx:
note: "ノート"
noteMy: "ノート(自分)"
notification: "通知"
chat: "チャット"
chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信"
channel: "チャンネル通知"
_ago:

View File

@ -1688,8 +1688,6 @@ _sfx:
note: "새 노트"
noteMy: "내 노트"
notification: "알림"
chat: "대화"
chatBg: "대화 (백그라운드)"
antenna: "안테나 수신"
channel: "채널 알림"
_ago:

View File

@ -407,7 +407,6 @@ _theme:
_sfx:
note: "ບັນທຶກ"
notification: "ການແຈ້ງເຕືອນ"
chat: "ແຊ໋ດ"
_2fa:
renewTOTPCancel: "ບໍ່​ແມ່ນ​ຕອນ​ນີ້"
_widgets:

View File

@ -438,7 +438,6 @@ _theme:
_sfx:
note: "Notities"
notification: "Meldingen"
chat: "Chat"
_2fa:
renewTOTPCancel: "Nee, bedankt"
_widgets:

View File

@ -1066,8 +1066,6 @@ _sfx:
note: "Wpisy"
noteMy: "Mój wpis"
notification: "Powiadomienia"
chat: "Wiadomości"
chatBg: "Rozmowy (tło)"
antenna: "Anteny"
channel: "Powiadomienia kanału"
_ago:

View File

@ -1320,7 +1320,6 @@ _theme:
_sfx:
note: "Posts"
notification: "Notificações"
chat: "Chat"
_ago:
invalid: "Não há nada aqui"
_timelineTutorial:

View File

@ -647,7 +647,6 @@ _theme:
_sfx:
note: "Note"
notification: "Notificări"
chat: "Chat"
_ago:
invalid: "Nu e nimic de văzut aici"
_widgets:

View File

@ -1576,8 +1576,6 @@ _sfx:
note: "Заметки"
noteMy: "Собственные заметки"
notification: "Уведомления"
chat: "Сообщения"
chatBg: "Сообщения (фон)"
antenna: "Антенна"
channel: "Канал"
_ago:

View File

@ -1127,8 +1127,6 @@ _sfx:
note: "Poznámky"
noteMy: "Vlastná poznámka"
notification: "Oznámenia"
chat: "Chat"
chatBg: "Chat (pozadie)"
antenna: "Antény"
channel: "Upozornenia kanála"
_ago:

View File

@ -507,7 +507,6 @@ _theme:
_sfx:
note: "Noter"
notification: "Notifikationer"
chat: "Chatt"
antenna: "Antenner"
_2fa:
renewTOTPCancel: "Nej tack"

View File

@ -1686,8 +1686,6 @@ _sfx:
note: "หมายเหตุ"
noteMy: "โน้ตของตัวเอง"
notification: "การเเจ้งเตือน"
chat: "แชท"
chatBg: "แชท (พื้นหลัง)"
antenna: "เสาอากาศ"
channel: "การแจ้งเตือนช่อง"
_ago:

View File

@ -386,7 +386,6 @@ _theme:
_sfx:
note: "notlar"
notification: "Bildirim"
chat: "Mesajlar"
_2fa:
renewTOTPCancel: "Hayır, teşekkürler"
_permissions:

View File

@ -1315,8 +1315,6 @@ _sfx:
note: "Нотатки"
noteMy: "Мої нотатки"
notification: "Сповіщення"
chat: "Чати"
chatBg: "Чати (фон)"
antenna: "Прийом антени"
channel: "Повідомлення каналу"
_ago:

View File

@ -910,7 +910,6 @@ _theme:
_sfx:
note: "Qaydlar"
notification: "Xabarnomalar"
chat: "Suhbat"
_ago:
minutesAgo: "{n} daqiqa oldin"
hoursAgo: "{n} soat oldin"

View File

@ -1492,8 +1492,6 @@ _sfx:
note: "Tút"
noteMy: "Tút của tôi"
notification: "Thông báo"
chat: "Trò chuyện"
chatBg: "Chat (Nền)"
antenna: "Trạm phát sóng"
channel: "Kênh"
_ago:

View File

@ -1697,8 +1697,6 @@ _sfx:
note: "帖子"
noteMy: "我的帖子"
notification: "通知"
chat: "聊天"
chatBg: "聊天背景"
antenna: "天线接收"
channel: "频道通知"
_ago:

View File

@ -1695,8 +1695,6 @@ _sfx:
note: "貼文"
noteMy: "我的貼文"
notification: "通知"
chat: "聊天"
chatBg: "聊天背景"
antenna: "天線接收"
channel: "頻道通知"
_ago:

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class CleanUp1696405744672 {
name = 'CleanUp1696405744672'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_e7c0567f5261063592f022e9b5"`);
await queryRunner.query(`DROP INDEX "public"."IDX_25dfc71b0369b003a4cd434d0b"`);
}
async down(queryRunner) {
await queryRunner.query(`CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes") `);
await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `);
}
}

View File

@ -158,9 +158,13 @@ export class AnnouncementService {
if (moderator) {
if (announcement.userId) {
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
this.moderationLogService.log(moderator, 'deleteUserAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
userId: announcement.userId,
userUsername: user.username,
userHost: user.host,
});
} else {
this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', {

View File

@ -3,16 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
export function isUserRelated(note: any, userIds: Set<string>): boolean {
if (userIds.has(note.userId)) {
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
if (userIds.has(note.userId) && !ignoreAuthor) {
return true;
}
if (note.reply != null && userIds.has(note.reply.userId)) {
if (note.reply != null && note.reply.userId !== note.userId && userIds.has(note.reply.userId)) {
return true;
}
if (note.renote != null && userIds.has(note.renote.userId)) {
if (note.renote != null && note.renote.userId !== note.userId && userIds.has(note.renote.userId)) {
return true;
}

View File

@ -18,7 +18,6 @@ export class MiNote {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the Note.',
})
@ -151,7 +150,6 @@ export class MiNote {
})
public fileIds: MiDriveFile['id'][];
@Index()
@Column('varchar', {
length: 256, array: true, default: '{}',
})

View File

@ -13,6 +13,7 @@ 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 { ApiError } from '../../error.js';
export const meta = {
@ -70,6 +71,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const [
userIdsWhoMeMuting,
] = me ? await Promise.all([
this.cacheService.userMutingsCache.fetch(me.id),
]) : [new Set<string>()];
let timeline: MiNote[] = [];
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
@ -118,6 +125,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
timeline = await query.getMany();
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;

View File

@ -171,6 +171,9 @@ export type ModerationLogPayloads = {
deleteUserAnnouncement: {
announcementId: string;
announcement: any;
userId: string;
userUsername: string;
userHost: string | null;
};
resetPassword: {
userId: string;

View File

@ -15,6 +15,10 @@ function genHost() {
return randomString() + '.example.com';
}
function waitForPushToTl() {
return sleep(300);
}
let app: INestApplicationContext;
beforeAll(async () => {
@ -32,7 +36,7 @@ describe('Timelines', () => {
const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -44,10 +48,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi' });
const carolNote = await post(carol, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -59,10 +64,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
const carolNote = await post(carol, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -75,10 +81,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -91,10 +98,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -107,10 +115,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -123,10 +132,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -140,10 +150,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/create', { userId: carol.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -158,10 +169,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/create', { userId: carol.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -173,10 +185,11 @@ describe('Timelines', () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -190,7 +203,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi' });
const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -202,10 +215,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { renoteId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -217,10 +231,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { renoteId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {
withRenotes: false,
@ -234,10 +249,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {
withRenotes: false,
@ -251,9 +267,10 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -265,10 +282,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/mute/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -282,10 +300,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await api('/mute/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -297,9 +316,10 @@ describe('Timelines', () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -310,9 +330,10 @@ describe('Timelines', () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', {}, alice);
@ -323,6 +344,7 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const [bobFile, carolFile] = await Promise.all([
uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
@ -332,7 +354,7 @@ describe('Timelines', () => {
const carolNote1 = await post(carol, { text: 'hi' });
const carolNote2 = await post(carol, { fileIds: [carolFile.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/timeline', { withFiles: true }, alice);
@ -350,7 +372,7 @@ describe('Timelines', () => {
const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -363,7 +385,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -374,13 +396,12 @@ describe('Timelines', () => {
test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/following/create', {
userId: carol.id,
}, alice);
await api('/following/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -392,10 +413,11 @@ describe('Timelines', () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/mute/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -408,10 +430,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/mute/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -425,10 +448,11 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
await api('/mute/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -443,7 +467,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', { withFiles: true }, alice);
@ -458,7 +482,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/hybrid-timeline', {}, alice);
@ -470,7 +494,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/hybrid-timeline', {}, alice);
@ -481,9 +505,10 @@ describe('Timelines', () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/hybrid-timeline', {}, alice);
@ -495,7 +520,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/local-timeline', {}, alice);
@ -506,9 +531,10 @@ describe('Timelines', () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/hybrid-timeline', {}, alice);
@ -519,9 +545,10 @@ describe('Timelines', () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/hybrid-timeline', {}, alice);
@ -535,7 +562,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/hybrid-timeline', { withFiles: true }, alice);
@ -550,9 +577,10 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -564,9 +592,10 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -579,9 +608,10 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -594,9 +624,10 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -609,10 +640,11 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -624,10 +656,11 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -641,10 +674,11 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await api('/users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -657,9 +691,10 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -672,9 +707,10 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
@ -691,7 +727,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/notes/user-list-timeline', { listId: list.id, withFiles: true }, alice);
@ -706,7 +742,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id }, alice);
@ -718,7 +754,7 @@ describe('Timelines', () => {
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id }, alice);
@ -732,7 +768,7 @@ describe('Timelines', () => {
await sleep(1000);
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id }, alice);
@ -747,7 +783,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id }, alice);
@ -762,7 +798,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
@ -777,7 +813,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
@ -792,7 +828,7 @@ describe('Timelines', () => {
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id, withFiles: true }, alice);
@ -800,17 +836,37 @@ describe('Timelines', () => {
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
}, 1000 * 10);
test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('/mute/create', { userId: carol.id }, alice);
await sleep(1000);
const carolNote = await post(carol, { text: 'hi' });
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('/mute/create', { userId: bob.id }, alice);
const bobNote = await post(bob, { text: 'hi' });
await sleep(1000);
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id });
await sleep(100); // redisに追加されるのを待つ
await waitForPushToTl();
const res = await api('/users/notes', { userId: bob.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote3.id), true);
});
});

View File

@ -29,8 +29,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'unmarkSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
<span v-else-if="log.type === 'suspendRemoteInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'unsuspendRemoteInstance'">: {{ log.info.host }}</span>
<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
</template>
@ -88,6 +92,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateGlobalAnnouncement'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateUserAnnouncement'">
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<details>
<summary>raw</summary>

View File

@ -139,21 +139,11 @@ const menuDef = computed(() => [{
text: i18n.ts.roles,
to: '/settings/roles',
active: currentPage?.route.name === 'roles',
}, {
icon: 'ti ti-planet-off',
text: i18n.ts.instanceMute,
to: '/settings/instance-mute',
active: currentPage?.route.name === 'instance-mute',
}, {
icon: 'ti ti-ban',
text: i18n.ts.muteAndBlock,
to: '/settings/mute-block',
active: currentPage?.route.name === 'mute-block',
}, {
icon: 'ti ti-message-off',
text: i18n.ts.wordMute,
to: '/settings/word-mute',
active: currentPage?.route.name === 'word-mute',
}, {
icon: 'ti ti-api',
text: 'API',

View File

@ -22,7 +22,6 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const instanceMutes = ref($i!.mutedInstances.join('\n'));
const changed = ref(false);
@ -46,13 +45,4 @@ async function save() {
watch(instanceMutes, () => {
changed.value = true;
});
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.instanceMute,
icon: 'ti ti-planet-off',
});
</script>

View File

@ -5,6 +5,20 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps_m">
<MkFolder>
<template #icon><i class="ti ti-message-off"></i></template>
<template #label>{{ i18n.ts.wordMute }}</template>
<XWordMute/>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-planet-off"></i></template>
<template #label>{{ i18n.ts.instanceMute }}</template>
<XInstanceMute/>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-repeat-off"></i></template>
<template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
@ -106,6 +120,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import XInstanceMute from './mute-block.instance-mute.vue';
import XWordMute from './mute-block.word-mute.vue';
import MkPagination from '@/components/MkPagination.vue';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';

View File

@ -91,13 +91,4 @@ async function save() {
changed.value = false;
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.wordMute,
icon: 'ti ti-message-off',
});
</script>

View File

@ -38,14 +38,12 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
const masterVolume = computed(soundConfigStore.makeGetterSetter('sound_masterVolume'));
const soundsKeys = ['note', 'noteMy', 'notification', 'chat', 'chatBg', 'antenna', 'channel'] as const;
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const;
const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
note: soundConfigStore.reactiveState.sound_note,
noteMy: soundConfigStore.reactiveState.sound_noteMy,
notification: soundConfigStore.reactiveState.sound_notification,
chat: soundConfigStore.reactiveState.sound_chat,
chatBg: soundConfigStore.reactiveState.sound_chatBg,
antenna: soundConfigStore.reactiveState.sound_antenna,
channel: soundConfigStore.reactiveState.sound_channel,
});

View File

@ -126,18 +126,10 @@ export const routes = [{
path: '/import-export',
name: 'import-export',
component: page(() => import('./pages/settings/import-export.vue')),
}, {
path: '/instance-mute',
name: 'instance-mute',
component: page(() => import('./pages/settings/instance-mute.vue')),
}, {
path: '/mute-block',
name: 'mute-block',
component: page(() => import('./pages/settings/mute-block.vue')),
}, {
path: '/word-mute',
name: 'word-mute',
component: page(() => import('./pages/settings/word-mute.vue')),
}, {
path: '/api',
name: 'api',

View File

@ -27,14 +27,6 @@ export const soundConfigStore = markRaw(new Storage('sound', {
where: 'account',
default: { type: 'syuilo/n-ea', volume: 1 },
},
sound_chat: {
where: 'account',
default: { type: 'syuilo/pope1', volume: 1 },
},
sound_chatBg: {
where: 'account',
default: { type: 'syuilo/waon', volume: 1 },
},
sound_antenna: {
where: 'account',
default: { type: 'syuilo/triple', volume: 1 },

View File

@ -189,6 +189,9 @@ export type ModerationLogPayloads = {
deleteUserAnnouncement: {
announcementId: string;
announcement: any;
userId: string;
userUsername: string;
userHost: string | null;
};
resetPassword: {
userId: string;