feat: user-level instance mute (#7712)
* Update ja-JP.yml * Added settable config for muted instances * added psql query for removal of muted notes * Added filtering and trimming for instance mutes * cleaned up filtering of bad instance mutes and added a refresh at the end for the list on the client * Added notification & streaming timeline muting * Updated changelog * Added missing semicolon * Apply japanese string suggestions from robflop Co-authored-by: Robin B. <robflop98@outlook.com> * Changed Ja-JP instance mute title string to one suggested by sousuke Co-authored-by: sousuke0422 <sousuke20xx@gmail.com> * Update ja-JP instanceMuteDescription based on sousuke's suggestion Co-authored-by: sousuke0422 <sousuke20xx@gmail.com> * added notification mute * added notification and note children muting * Fixed a bug where local notifications were getting filtered on cold start * Fixed instance mute imports * Fixed not saving/loading instance mutes * removed en-US translations for instance mute * moved instance mute migration to js * changed settings index back to spaces * removed destructuring assignment from notification stream in instance mute check call Co-authored-by: tamaina <tamaina@hotmail.co.jp> * added .note accessor for checking note data instead of notification data * changed note to use Packed<'Note'> instead of any and removed usage of snake case Co-authored-by: tamaina <tamaina@hotmail.co.jp> * changed notification mute check to check specifically for notification host * changed to using single quotes * moved @click to the end for the linter * revert unnecessary changes * restored newlines * whitespace removal Co-authored-by: syuilo <syuilotan@yahoo.co.jp> Co-authored-by: Robin B. <robflop98@outlook.com> Co-authored-by: sousuke0422 <sousuke20xx@gmail.com> Co-authored-by: puffaboo <emilis@jigglypuff.club> Co-authored-by: tamaina <tamaina@hotmail.co.jp>
This commit is contained in:
parent
b9095995eb
commit
054417354c
20 changed files with 214 additions and 0 deletions
|
@ -234,6 +234,9 @@
|
||||||
|
|
||||||
## 12.89.1 (2021/08/24)
|
## 12.89.1 (2021/08/24)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Added a user-level instance mute in user settings
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
- クライアントのデザインの調整
|
- クライアントのデザインの調整
|
||||||
|
|
||||||
|
|
|
@ -592,6 +592,7 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
||||||
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
||||||
testEmail: "配信テスト"
|
testEmail: "配信テスト"
|
||||||
wordMute: "ワードミュート"
|
wordMute: "ワードミュート"
|
||||||
|
instanceMute: "インスタンスミュート"
|
||||||
userSaysSomething: "{name}が何かを言いました"
|
userSaysSomething: "{name}が何かを言いました"
|
||||||
makeActive: "アクティブにする"
|
makeActive: "アクティブにする"
|
||||||
display: "表示"
|
display: "表示"
|
||||||
|
@ -1021,6 +1022,12 @@ _wordMute:
|
||||||
hard: "ハード"
|
hard: "ハード"
|
||||||
mutedNotes: "ミュートされたノート"
|
mutedNotes: "ミュートされたノート"
|
||||||
|
|
||||||
|
_instanceMute:
|
||||||
|
instanceMuteDescription: "ミュートしたインスタンスのユーザーからの返信を含めて、設定したインスタンスの全てのノートとRenoteをミュートします。"
|
||||||
|
instanceMuteDescription2: "改行で区切って設定します"
|
||||||
|
title: "設定したインスタンスのノートを隠します。"
|
||||||
|
heading: "ミュートするインスタンス"
|
||||||
|
|
||||||
_theme:
|
_theme:
|
||||||
explore: "テーマを探す"
|
explore: "テーマを探す"
|
||||||
install: "テーマのインストール"
|
install: "テーマのインストール"
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
class userInstanceBlocks1629968054000 {
|
||||||
|
constructor() {
|
||||||
|
this.name = 'userInstanceBlocks1629968054000';
|
||||||
|
}
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutedInstances" jsonb NOT NULL DEFAULT '[]'`);
|
||||||
|
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."mutedInstances" IS 'List of instances muted by the user.'`);
|
||||||
|
}
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutedInstances"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.userInstanceBlocks1629968054000 = userInstanceBlocks1629968054000;
|
15
packages/backend/src/misc/is-instance-muted.ts
Normal file
15
packages/backend/src/misc/is-instance-muted.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Packed } from "./schema";
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUserFromMutedInstance(notif: Packed<'Notification'>, mutedInstances: Set<string>): boolean {
|
||||||
|
if (mutedInstances.has(notif?.user?.host ?? '')) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -189,6 +189,11 @@ export class UserProfile {
|
||||||
})
|
})
|
||||||
public mutedWords: string[][];
|
public mutedWords: string[][];
|
||||||
|
|
||||||
|
@Column('jsonb', {
|
||||||
|
default: []
|
||||||
|
})
|
||||||
|
public mutedInstances: string[];
|
||||||
|
|
||||||
@Column('enum', {
|
@Column('enum', {
|
||||||
enum: notificationTypes,
|
enum: notificationTypes,
|
||||||
array: true,
|
array: true,
|
||||||
|
|
|
@ -288,6 +288,7 @@ export class UserRepository extends Repository<User> {
|
||||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||||
integrations: profile!.integrations,
|
integrations: profile!.integrations,
|
||||||
mutedWords: profile!.mutedWords,
|
mutedWords: profile!.mutedWords,
|
||||||
|
mutedInstances: profile!.mutedInstances,
|
||||||
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
||||||
emailNotificationTypes: profile!.emailNotificationTypes,
|
emailNotificationTypes: profile!.emailNotificationTypes,
|
||||||
} : {}),
|
} : {}),
|
||||||
|
@ -623,6 +624,10 @@ export const packedUserSchema = {
|
||||||
type: 'array' as const,
|
type: 'array' as const,
|
||||||
nullable: false as const, optional: true as const
|
nullable: false as const, optional: true as const
|
||||||
},
|
},
|
||||||
|
mutedInstances: {
|
||||||
|
type: 'array' as const,
|
||||||
|
nullable: false as const, optional: true as const
|
||||||
|
},
|
||||||
mutingNotificationTypes: {
|
mutingNotificationTypes: {
|
||||||
type: 'array' as const,
|
type: 'array' as const,
|
||||||
nullable: false as const, optional: true as const
|
nullable: false as const, optional: true as const
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { User } from '@/models/entities/user';
|
||||||
|
import { id } from '@/models/id';
|
||||||
|
import { UserProfiles } from '@/models/index';
|
||||||
|
import { SelectQueryBuilder, Brackets } from 'typeorm';
|
||||||
|
|
||||||
|
function createMutesQuery(id: string) {
|
||||||
|
return UserProfiles.createQueryBuilder('user_profile')
|
||||||
|
.select('user_profile.mutedInstances')
|
||||||
|
.where('user_profile.userId = :muterId', { muterId: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMutedInstanceQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }) {
|
||||||
|
const mutingQuery = createMutesQuery(me.id);
|
||||||
|
|
||||||
|
q
|
||||||
|
.andWhere(new Brackets(qb => { qb
|
||||||
|
.andWhere('note.userHost IS NULL')
|
||||||
|
.orWhere(`NOT((${ mutingQuery.getQuery() })::jsonb ? note.userHost)`);
|
||||||
|
}))
|
||||||
|
.andWhere(new Brackets(qb => { qb
|
||||||
|
.where(`note.replyUserHost IS NULL`)
|
||||||
|
.orWhere(`NOT ((${ mutingQuery.getQuery() })::jsonb ? note.replyUserHost)`);
|
||||||
|
}))
|
||||||
|
.andWhere(new Brackets(qb => { qb
|
||||||
|
.where(`note.renoteUserHost IS NULL`)
|
||||||
|
.orWhere(`NOT ((${ mutingQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
|
||||||
|
}));
|
||||||
|
q.setParameters(mutingQuery.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMutedInstanceNotificationQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }) {
|
||||||
|
const mutingQuery = createMutesQuery(me.id);
|
||||||
|
|
||||||
|
q.andWhere(new Brackets(qb => { qb
|
||||||
|
.andWhere('notifier.host IS NULL')
|
||||||
|
.orWhere(`NOT (( ${mutingQuery.getQuery()} )::jsonb ? notifier.host)`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
q.setParameters(mutingQuery.getParameters());
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { ID } from '@/misc/cafy-id';
|
||||||
import { readNotification } from '../../common/read-notification';
|
import { readNotification } from '../../common/read-notification';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||||
|
import { generateMutedInstanceNotificationQuery } from '../../common/generate-muted-instance-query';
|
||||||
import { Notifications, Followings, Mutings, Users } from '@/models/index';
|
import { Notifications, Followings, Mutings, Users } from '@/models/index';
|
||||||
import { notificationTypes } from '@/types';
|
import { notificationTypes } from '@/types';
|
||||||
import read from '@/services/note/read';
|
import read from '@/services/note/read';
|
||||||
|
@ -101,6 +102,8 @@ export default define(meta, async (ps, user) => {
|
||||||
}));
|
}));
|
||||||
query.setParameters(mutingQuery.getParameters());
|
query.setParameters(mutingQuery.getParameters());
|
||||||
|
|
||||||
|
generateMutedInstanceNotificationQuery(query, user);
|
||||||
|
|
||||||
query.andWhere(new Brackets(qb => { qb
|
query.andWhere(new Brackets(qb => { qb
|
||||||
.where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
|
.where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
|
||||||
.orWhere('notification.notifierId IS NULL');
|
.orWhere('notification.notifierId IS NULL');
|
||||||
|
|
|
@ -116,6 +116,10 @@ export const meta = {
|
||||||
validator: $.optional.arr($.arr($.str))
|
validator: $.optional.arr($.arr($.str))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mutedInstances: {
|
||||||
|
validator: $.optional.arr($.str)
|
||||||
|
},
|
||||||
|
|
||||||
mutingNotificationTypes: {
|
mutingNotificationTypes: {
|
||||||
validator: $.optional.arr($.str.or(notificationTypes as unknown as string[]))
|
validator: $.optional.arr($.str.or(notificationTypes as unknown as string[]))
|
||||||
},
|
},
|
||||||
|
@ -185,6 +189,7 @@ export default define(meta, async (ps, _user, token) => {
|
||||||
profileUpdates.mutedWords = ps.mutedWords;
|
profileUpdates.mutedWords = ps.mutedWords;
|
||||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||||
}
|
}
|
||||||
|
if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
|
||||||
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][];
|
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][];
|
||||||
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||||
if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable;
|
if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Notes } from '@/models/index';
|
import { Notes } from '@/models/index';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query';
|
||||||
|
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
@ -65,6 +66,7 @@ export default define(meta, async (ps, user) => {
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
if (user) generateMutedUserQuery(query, user);
|
if (user) generateMutedUserQuery(query, user);
|
||||||
if (user) generateBlockedUserQuery(query, user);
|
if (user) generateBlockedUserQuery(query, user);
|
||||||
|
if (user) generateMutedInstanceQuery(query, user);
|
||||||
|
|
||||||
const notes = await query.take(ps.limit!).getMany();
|
const notes = await query.take(ps.limit!).getMany();
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ApiError } from '../../error';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||||
import { Notes } from '@/models/index';
|
import { Notes } from '@/models/index';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||||
|
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query';
|
||||||
import { activeUsersChart } from '@/services/chart/index';
|
import { activeUsersChart } from '@/services/chart/index';
|
||||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
||||||
|
@ -83,6 +84,7 @@ export default define(meta, async (ps, user) => {
|
||||||
if (user) generateMutedUserQuery(query, user);
|
if (user) generateMutedUserQuery(query, user);
|
||||||
if (user) generateMutedNoteQuery(query, user);
|
if (user) generateMutedNoteQuery(query, user);
|
||||||
if (user) generateBlockedUserQuery(query, user);
|
if (user) generateBlockedUserQuery(query, user);
|
||||||
|
if (user) generateMutedInstanceQuery(query, user);
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
query.andWhere('note.fileIds != \'{}\'');
|
query.andWhere('note.fileIds != \'{}\'');
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Followings, Notes } from '@/models/index';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||||
|
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query';
|
||||||
import { activeUsersChart } from '@/services/chart/index';
|
import { activeUsersChart } from '@/services/chart/index';
|
||||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
||||||
|
@ -108,6 +109,7 @@ export default define(meta, async (ps, user) => {
|
||||||
generateRepliesQuery(query, user);
|
generateRepliesQuery(query, user);
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
|
generateMutedInstanceQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||||
import { Notes, Followings } from '@/models/index';
|
import { Notes, Followings } from '@/models/index';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||||
|
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query';
|
||||||
import { activeUsersChart } from '@/services/chart/index';
|
import { activeUsersChart } from '@/services/chart/index';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||||
|
@ -100,6 +101,7 @@ export default define(meta, async (ps, user) => {
|
||||||
generateRepliesQuery(query, user);
|
generateRepliesQuery(query, user);
|
||||||
generateVisibilityQuery(query, user);
|
generateVisibilityQuery(query, user);
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
|
generateMutedInstanceQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Notes } from '@/models/index';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query';
|
||||||
|
import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users', 'notes'],
|
tags: ['users', 'notes'],
|
||||||
|
@ -102,6 +103,7 @@ export default define(meta, async (ps, me) => {
|
||||||
generateVisibilityQuery(query, me);
|
generateVisibilityQuery(query, me);
|
||||||
if (me) generateMutedUserQuery(query, me, user);
|
if (me) generateMutedUserQuery(query, me, user);
|
||||||
if (me) generateBlockedUserQuery(query, me);
|
if (me) generateBlockedUserQuery(query, me);
|
||||||
|
if (me) generateMutedInstanceQuery(query, me);
|
||||||
|
|
||||||
if (ps.withFiles) {
|
if (ps.withFiles) {
|
||||||
query.andWhere('note.fileIds != \'{}\'');
|
query.andWhere('note.fileIds != \'{}\'');
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { fetchMeta } from '@/misc/fetch-meta';
|
||||||
import { Notes } from '@/models/index';
|
import { Notes } from '@/models/index';
|
||||||
import { checkWordMute } from '@/misc/check-word-mute';
|
import { checkWordMute } from '@/misc/check-word-mute';
|
||||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||||
|
import { isInstanceMuted } from '@/misc/is-instance-muted';
|
||||||
import { Packed } from '@/misc/schema';
|
import { Packed } from '@/misc/schema';
|
||||||
|
|
||||||
export default class extends Channel {
|
export default class extends Channel {
|
||||||
|
@ -48,6 +49,9 @@ export default class extends Channel {
|
||||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore notes from instances the user has muted
|
||||||
|
if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||||
if (isMutedUserRelated(note, this.muting)) return;
|
if (isMutedUserRelated(note, this.muting)) return;
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Channel from '../channel';
|
||||||
import { Notes } from '@/models/index';
|
import { Notes } from '@/models/index';
|
||||||
import { checkWordMute } from '@/misc/check-word-mute';
|
import { checkWordMute } from '@/misc/check-word-mute';
|
||||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||||
|
import { isInstanceMuted } from '@/misc/is-instance-muted';
|
||||||
import { Packed } from '@/misc/schema';
|
import { Packed } from '@/misc/schema';
|
||||||
|
|
||||||
export default class extends Channel {
|
export default class extends Channel {
|
||||||
|
@ -26,6 +27,9 @@ export default class extends Channel {
|
||||||
if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return;
|
if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore notes from instances the user has muted
|
||||||
|
if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
if (['followers', 'specified'].includes(note.visibility)) {
|
if (['followers', 'specified'].includes(note.visibility)) {
|
||||||
note = await Notes.pack(note.id, this.user!, {
|
note = await Notes.pack(note.id, this.user!, {
|
||||||
detail: true
|
detail: true
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { fetchMeta } from '@/misc/fetch-meta';
|
||||||
import { Notes } from '@/models/index';
|
import { Notes } from '@/models/index';
|
||||||
import { checkWordMute } from '@/misc/check-word-mute';
|
import { checkWordMute } from '@/misc/check-word-mute';
|
||||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||||
|
import { isInstanceMuted } from '@/misc/is-instance-muted';
|
||||||
import { Packed } from '@/misc/schema';
|
import { Packed } from '@/misc/schema';
|
||||||
|
|
||||||
export default class extends Channel {
|
export default class extends Channel {
|
||||||
|
@ -57,6 +58,9 @@ export default class extends Channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore notes from instances the user has muted
|
||||||
|
if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply) {
|
if (note.reply) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import autobind from 'autobind-decorator';
|
import autobind from 'autobind-decorator';
|
||||||
import Channel from '../channel';
|
import Channel from '../channel';
|
||||||
import { Notes } from '@/models/index';
|
import { Notes } from '@/models/index';
|
||||||
|
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted';
|
||||||
|
|
||||||
export default class extends Channel {
|
export default class extends Channel {
|
||||||
public readonly chName = 'main';
|
public readonly chName = 'main';
|
||||||
|
@ -13,6 +14,8 @@ export default class extends Channel {
|
||||||
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
|
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'notification': {
|
case 'notification': {
|
||||||
|
// Ignore notifications from instances the user has muted
|
||||||
|
if (isUserFromMutedInstance(data.body, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
|
||||||
if (data.body.userId && this.muting.has(data.body.userId)) return;
|
if (data.body.userId && this.muting.has(data.body.userId)) return;
|
||||||
|
|
||||||
if (data.body.note && data.body.note.isHidden) {
|
if (data.body.note && data.body.note.isHidden) {
|
||||||
|
@ -25,6 +28,8 @@ export default class extends Channel {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'mention': {
|
case 'mention': {
|
||||||
|
if (isInstanceMuted(data.body, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
if (this.muting.has(data.body.userId)) return;
|
if (this.muting.has(data.body.userId)) return;
|
||||||
if (data.body.isHidden) {
|
if (data.body.isHidden) {
|
||||||
const note = await Notes.pack(data.body.id, this.user, {
|
const note = await Notes.pack(data.body.id, this.user, {
|
||||||
|
|
|
@ -136,6 +136,11 @@ export default defineComponent({
|
||||||
text: i18n.locale.importAndExport,
|
text: i18n.locale.importAndExport,
|
||||||
to: '/settings/import-export',
|
to: '/settings/import-export',
|
||||||
active: page.value === 'import-export',
|
active: page.value === 'import-export',
|
||||||
|
}, {
|
||||||
|
icon: 'fas fa-volume-mute',
|
||||||
|
text: i18n.locale.instanceMute,
|
||||||
|
to: '/settings/instance-mute',
|
||||||
|
active: page.value === 'instance-mute',
|
||||||
}, {
|
}, {
|
||||||
icon: 'fas fa-ban',
|
icon: 'fas fa-ban',
|
||||||
text: i18n.locale.muteAndBlock,
|
text: i18n.locale.muteAndBlock,
|
||||||
|
@ -190,6 +195,7 @@ export default defineComponent({
|
||||||
case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
|
case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
|
||||||
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
|
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
|
||||||
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
|
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
|
||||||
|
case 'instance-mute': return defineAsyncComponent(() => import('./instance-mute.vue'));
|
||||||
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
|
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
|
||||||
case 'security': return defineAsyncComponent(() => import('./security.vue'));
|
case 'security': return defineAsyncComponent(() => import('./security.vue'));
|
||||||
case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
|
case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
|
||||||
|
|
83
packages/client/src/pages/settings/instance-mute.vue
Normal file
83
packages/client/src/pages/settings/instance-mute.vue
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<FormBase>
|
||||||
|
<div class="_formItem">
|
||||||
|
<FormInfo>{{ $ts._instanceMute.title}}</FormInfo>
|
||||||
|
<FormTextarea v-model="instanceMutes">
|
||||||
|
<span>{{$ts._instanceMute.heading}}</span>
|
||||||
|
<template #desc>{{ $ts._instanceMute.instanceMuteDescription}}<br>{{$ts._instanceMute.instanceMuteDescription2}}</template>
|
||||||
|
</FormTextarea>
|
||||||
|
</div>
|
||||||
|
<FormButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
|
||||||
|
</FormBase>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import FormBase from '@/components/debobigego/base.vue';
|
||||||
|
import FormTextarea from '@/components/debobigego/textarea.vue';
|
||||||
|
import FormInfo from '@/components/debobigego/info.vue';
|
||||||
|
import FormKeyValueView from '@/components/debobigego/key-value-view.vue';
|
||||||
|
import FormButton from '@/components/debobigego/button.vue';
|
||||||
|
import * as os from '@/os';
|
||||||
|
import number from '@/filters/number';
|
||||||
|
import * as symbols from '@/symbols';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
FormBase,
|
||||||
|
FormButton,
|
||||||
|
FormTextarea,
|
||||||
|
FormKeyValueView,
|
||||||
|
FormInfo,
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['info'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
[symbols.PAGE_INFO]: {
|
||||||
|
title: this.$ts.instanceMute,
|
||||||
|
icon: 'fas fa-volume-mute'
|
||||||
|
},
|
||||||
|
tab: 'soft',
|
||||||
|
instanceMutes: '',
|
||||||
|
changed: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
instanceMutes: {
|
||||||
|
handler() {
|
||||||
|
this.changed = true;
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$emit('info', this[symbols.PAGE_INFO]);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async created() {
|
||||||
|
this.instanceMutes = this.$i.mutedInstances.join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async save() {
|
||||||
|
let mutes = this.instanceMutes.trim().split('\n').map(el => el.trim()).filter(el => el);
|
||||||
|
await os.api('i/update', {
|
||||||
|
mutedInstances: mutes,
|
||||||
|
});
|
||||||
|
this.changed = false;
|
||||||
|
|
||||||
|
// Refresh filtered list to signal to the user how they've been saved
|
||||||
|
this.instanceMutes = mutes.join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
number //?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
Loading…
Reference in a new issue