Merge branch 'develop' into emoji-request

This commit is contained in:
まっちゃてぃー 2023-10-16 21:50:42 +09:00 committed by GitHub
commit 198f7748ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 541 additions and 382 deletions

View file

@ -16,6 +16,8 @@
### General ### General
- Feat: アンテナでローカルの投稿のみ収集できるようになりました - Feat: アンテナでローカルの投稿のみ収集できるようになりました
- Feat: サーバーサイレンス機能が追加されました
- Enhance: 依存関係の更新
### Client ### Client
- Enhance: TLの返信表示オプションを記憶するように - Enhance: TLの返信表示オプションを記憶するように

View file

@ -195,6 +195,7 @@ perHour: "Per Hour"
perDay: "Per Day" perDay: "Per Day"
stopActivityDelivery: "Stop sending activities" stopActivityDelivery: "Stop sending activities"
blockThisInstance: "Block this instance" blockThisInstance: "Block this instance"
silenceThisInstance: "Silence this instance"
operations: "Operations" operations: "Operations"
software: "Software" software: "Software"
version: "Version" version: "Version"
@ -213,6 +214,13 @@ clearQueueConfirmText: "Any undelivered notes remaining in the queue will not be
clearCachedFiles: "Clear cache" clearCachedFiles: "Clear cache"
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?" clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
blockedInstances: "Blocked Instances" blockedInstances: "Blocked Instances"
silencedInstances: "Silenced Instances"
silencedInstancesDescription: "List the hostnames of the instances that you want to\
\ silence. Accounts in the listed instances are treated as \"Silenced\", can only make follow requests, and cannot mention local accounts if not followed. This will not affect the blocked instances."
hiddenTags: "Hidden Hashtags"
hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\
\ to hide from trending and explore. Hidden hashtags are still discoverable via\
\ other means. Blocked instances are not affected even if listed here."
blockedInstancesDescription: "List the hostnames of the instances that you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance." blockedInstancesDescription: "List the hostnames of the instances that you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance."
muteAndBlock: "Mutes and Blocks" muteAndBlock: "Mutes and Blocks"
mutedUsers: "Muted users" mutedUsers: "Muted users"
@ -794,7 +802,7 @@ active: "Active"
offline: "Offline" offline: "Offline"
notRecommended: "Not recommended" notRecommended: "Not recommended"
botProtection: "Bot Protection" botProtection: "Bot Protection"
instanceBlocking: "Blocked Instances" instanceBlocking: "Blocked/Silenced Instances"
selectAccount: "Select account" selectAccount: "Select account"
switchAccount: "Switch account" switchAccount: "Switch account"
enabled: "Enabled" enabled: "Enabled"

3
locales/index.d.ts vendored
View file

@ -198,6 +198,7 @@ export interface Locale {
"perDay": string; "perDay": string;
"stopActivityDelivery": string; "stopActivityDelivery": string;
"blockThisInstance": string; "blockThisInstance": string;
"silenceThisInstance": string;
"operations": string; "operations": string;
"software": string; "software": string;
"version": string; "version": string;
@ -217,6 +218,8 @@ export interface Locale {
"clearCachedFilesConfirm": string; "clearCachedFilesConfirm": string;
"blockedInstances": string; "blockedInstances": string;
"blockedInstancesDescription": string; "blockedInstancesDescription": string;
"silencedInstances": string;
"silencedInstancesDescription": string;
"muteAndBlock": string; "muteAndBlock": string;
"mutedUsers": string; "mutedUsers": string;
"blockedUsers": string; "blockedUsers": string;

View file

@ -195,6 +195,7 @@ perHour: "1時間ごと"
perDay: "1日ごと" perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送を停止" stopActivityDelivery: "アクティビティの配送を停止"
blockThisInstance: "このサーバーをブロック" blockThisInstance: "このサーバーをブロック"
silenceThisInstance: "サーバーをサイレンス"
operations: "操作" operations: "操作"
software: "ソフトウェア" software: "ソフトウェア"
version: "バージョン" version: "バージョン"
@ -213,7 +214,9 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。
clearCachedFiles: "キャッシュをクリア" clearCachedFiles: "キャッシュをクリア"
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
blockedInstances: "ブロックしたサーバー" blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このサーバーとやり取りできなくなります。サブドメインもブロックされます。" blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。"
silencedInstances: "サイレンスしたサーバー"
silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
muteAndBlock: "ミュートとブロック" muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー" mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー" blockedUsers: "ブロックしたユーザー"
@ -797,7 +800,7 @@ active: "アクティブ"
offline: "オフライン" offline: "オフライン"
notRecommended: "非推奨" notRecommended: "非推奨"
botProtection: "Botプロテクション" botProtection: "Botプロテクション"
instanceBlocking: "サーバーブロック" instanceBlocking: "サーバーブロック・サイレンス"
selectAccount: "アカウントを選択" selectAccount: "アカウントを選択"
switchAccount: "アカウントを切り替え" switchAccount: "アカウントを切り替え"
enabled: "有効" enabled: "有効"

View file

@ -54,7 +54,7 @@
"@typescript-eslint/eslint-plugin": "6.7.5", "@typescript-eslint/eslint-plugin": "6.7.5",
"@typescript-eslint/parser": "6.7.5", "@typescript-eslint/parser": "6.7.5",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.3.0", "cypress": "13.3.1",
"eslint": "8.51.0", "eslint": "8.51.0",
"start-server-and-test": "2.0.1" "start-server-and-test": "2.0.1"
}, },

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class InstanceSilence1697247230117 {
name = 'InstanceSilence1697247230117'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "silencedHosts" character varying(1024) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`);
}
}

View file

@ -75,10 +75,10 @@
"@nestjs/core": "10.2.7", "@nestjs/core": "10.2.7",
"@nestjs/testing": "10.2.7", "@nestjs/testing": "10.2.7",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@simplewebauthn/server": "8.2.0", "@simplewebauthn/server": "8.3.2",
"@sinonjs/fake-timers": "11.1.0", "@sinonjs/fake-timers": "11.1.0",
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.92", "@swc/core": "1.3.93",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "6.0.1", "archiver": "6.0.1",
@ -86,7 +86,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"bullmq": "4.12.3", "bullmq": "4.12.4",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.1", "cbor": "9.0.1",
"chalk": "5.3.0", "chalk": "5.3.0",
@ -97,7 +97,7 @@
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"fastify": "4.23.2", "fastify": "4.24.2",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "18.5.0", "file-type": "18.5.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
@ -121,13 +121,13 @@
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nanoid": "5.0.1", "nanoid": "5.0.2",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.9.6", "nodemailer": "6.9.6",
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "0.10.0", "oauth": "0.10.0",
"oauth2orize": "1.11.1", "oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2", "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.1.5", "otpauth": "9.1.5",
@ -183,26 +183,26 @@
"@types/fluent-ffmpeg": "2.1.22", "@types/fluent-ffmpeg": "2.1.22",
"@types/http-link-header": "1.0.3", "@types/http-link-header": "1.0.3",
"@types/jest": "29.5.5", "@types/jest": "29.5.5",
"@types/js-yaml": "4.0.6", "@types/js-yaml": "4.0.7",
"@types/jsdom": "21.1.3", "@types/jsdom": "21.1.3",
"@types/jsonld": "1.5.10", "@types/jsonld": "1.5.10",
"@types/jsrsasign": "10.5.9", "@types/jsrsasign": "10.5.9",
"@types/mime-types": "2.1.2", "@types/mime-types": "2.1.2",
"@types/ms": "0.7.32", "@types/ms": "0.7.32",
"@types/node": "20.8.4", "@types/node": "20.8.6",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.11", "@types/nodemailer": "6.4.11",
"@types/oauth": "0.9.2", "@types/oauth": "0.9.2",
"@types/oauth2orize": "1.11.1", "@types/oauth2orize": "1.11.1",
"@types/oauth2orize-pkce": "0.1.0", "@types/oauth2orize-pkce": "0.1.0",
"@types/pg": "8.10.4", "@types/pg": "8.10.5",
"@types/pug": "2.0.7", "@types/pug": "2.0.7",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/qrcode": "1.5.2", "@types/qrcode": "1.5.2",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4", "@types/ratelimiter": "3.4.4",
"@types/rename": "1.0.5", "@types/rename": "1.0.5",
"@types/sanitize-html": "2.9.1", "@types/sanitize-html": "2.9.2",
"@types/semver": "7.5.3", "@types/semver": "7.5.3",
"@types/sharp": "0.32.0", "@types/sharp": "0.32.0",
"@types/simple-oauth2": "5.0.5", "@types/simple-oauth2": "5.0.5",
@ -211,7 +211,7 @@
"@types/tmp": "0.2.4", "@types/tmp": "0.2.4",
"@types/vary": "1.1.1", "@types/vary": "1.1.1",
"@types/web-push": "3.6.1", "@types/web-push": "3.6.1",
"@types/ws": "8.5.6", "@types/ws": "8.5.7",
"@typescript-eslint/eslint-plugin": "6.7.5", "@typescript-eslint/eslint-plugin": "6.7.5",
"@typescript-eslint/parser": "6.7.5", "@typescript-eslint/parser": "6.7.5",
"aws-sdk-client-mock": "3.0.0", "aws-sdk-client-mock": "3.0.0",

View file

@ -56,6 +56,7 @@ import { SearchService } from '@/core/SearchService.js';
import { FeaturedService } from '@/core/FeaturedService.js'; import { FeaturedService } from '@/core/FeaturedService.js';
import { RedisTimelineService } from '@/core/RedisTimelineService.js'; import { RedisTimelineService } from '@/core/RedisTimelineService.js';
import { nyaize } from '@/misc/nyaize.js'; import { nyaize } from '@/misc/nyaize.js';
import { UtilityService } from '@/core/UtilityService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -215,6 +216,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private perUserNotesChart: PerUserNotesChart, private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart, private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
private utilityService: UtilityService,
) { } ) { }
@bindThis @bindThis
@ -259,6 +261,12 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
} }
const inSilencedInstance = this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
data.visibility = 'home';
}
if (data.renote) { if (data.renote) {
switch (data.renote.visibility) { switch (data.renote.visibility) {
case 'public': case 'public':

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
@ -34,6 +34,7 @@ import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { AccountMoveService } from '@/core/AccountMoveService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js';
import { UtilityService } from '@/core/UtilityService.js';
import Logger from '../logger.js'; import Logger from '../logger.js';
const logger = new Logger('following/create'); const logger = new Logger('following/create');
@ -70,6 +71,7 @@ export class UserFollowingService implements OnModuleInit {
@Inject(DI.instancesRepository) @Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository, private instancesRepository: InstancesRepository,
private cacheService: CacheService, private cacheService: CacheService,
private utilityService: UtilityService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private idService: IdService, private idService: IdService,
private queueService: QueueService, private queueService: QueueService,
@ -119,15 +121,16 @@ export class UserFollowingService implements OnModuleInit {
} }
const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id }); const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id });
// フォロー対象が鍵アカウントである or // フォロー対象が鍵アカウントである or
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or
// フォロワーがローカルユーザーであり、フォロー対象がサイレンスされているサーバーである
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
if ( if (
followee.isLocked || followee.isLocked ||
(followeeProfile.carefulBot && follower.isBot) || (followeeProfile.carefulBot && follower.isBot) ||
(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') ||
(this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host))
) { ) {
let autoAccept = false; let autoAccept = false;

View file

@ -35,6 +35,12 @@ export class UtilityService {
return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
} }
@bindThis
public isSilencedHost(silencedHosts: string[] | undefined, host: string | null): boolean {
if (!silencedHosts || host == null) return false;
return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
}
@bindThis @bindThis
public extractDbHost(uri: string): string { public extractDbHost(uri: string): string {
const url = new URL(uri); const url = new URL(uri);

View file

@ -3,9 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js'; import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiInstance } from '@/models/Instance.js'; import type { MiInstance } from '@/models/Instance.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -43,6 +42,7 @@ export class InstanceEntityService {
description: instance.description, description: instance.description,
maintainerName: instance.maintainerName, maintainerName: instance.maintainerName,
maintainerEmail: instance.maintainerEmail, maintainerEmail: instance.maintainerEmail,
isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host),
iconUrl: instance.iconUrl, iconUrl: instance.iconUrl,
faviconUrl: instance.faviconUrl, faviconUrl: instance.faviconUrl,
themeColor: instance.themeColor, themeColor: instance.themeColor,

View file

@ -76,6 +76,11 @@ export class MiMeta {
}) })
public sensitiveWords: string[]; public sensitiveWords: string[];
@Column('varchar', {
length: 1024, array: true, default: '{}',
})
public silencedHosts: string[];
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,
nullable: true, nullable: true,

View file

@ -93,6 +93,11 @@ export const packedFederationInstanceSchema = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
isSilenced: {
type: "boolean",
optional: false,
nullable: false,
},
infoUpdatedAt: { infoUpdatedAt: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,

View file

@ -105,6 +105,16 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
silencedHosts: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false,
},
},
pinnedUsers: { pinnedUsers: {
type: 'array', type: 'array',
optional: false, nullable: false, optional: false, nullable: false,
@ -367,6 +377,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
pinnedUsers: instance.pinnedUsers, pinnedUsers: instance.pinnedUsers,
hiddenTags: instance.hiddenTags, hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts, blockedHosts: instance.blockedHosts,
silencedHosts: instance.silencedHosts,
sensitiveWords: instance.sensitiveWords, sensitiveWords: instance.sensitiveWords,
preservedUsernames: instance.preservedUsernames, preservedUsernames: instance.preservedUsernames,
hcaptchaSecretKey: instance.hcaptchaSecretKey, hcaptchaSecretKey: instance.hcaptchaSecretKey,

View file

@ -20,18 +20,26 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
disableRegistration: { type: 'boolean', nullable: true }, disableRegistration: { type: 'boolean', nullable: true },
pinnedUsers: { type: 'array', nullable: true, items: { pinnedUsers: {
type: 'array', nullable: true, items: {
type: 'string', type: 'string',
} }, },
hiddenTags: { type: 'array', nullable: true, items: { },
hiddenTags: {
type: 'array', nullable: true, items: {
type: 'string', type: 'string',
} }, },
blockedHosts: { type: 'array', nullable: true, items: { },
blockedHosts: {
type: 'array', nullable: true, items: {
type: 'string', type: 'string',
} }, },
sensitiveWords: { type: 'array', nullable: true, items: { },
sensitiveWords: {
type: 'array', nullable: true, items: {
type: 'string', type: 'string',
} }, },
},
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
mascotImageUrl: { type: 'string', nullable: true }, mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true },
@ -67,9 +75,11 @@ export const paramDef = {
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true }, proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
maintainerName: { type: 'string', nullable: true }, maintainerName: { type: 'string', nullable: true },
maintainerEmail: { type: 'string', nullable: true }, maintainerEmail: { type: 'string', nullable: true },
langs: { type: 'array', items: { langs: {
type: 'array', items: {
type: 'string', type: 'string',
} }, },
},
summalyProxy: { type: 'string', nullable: true }, summalyProxy: { type: 'string', nullable: true },
deeplAuthKey: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true },
deeplIsPro: { type: 'boolean' }, deeplIsPro: { type: 'boolean' },
@ -115,6 +125,13 @@ export const paramDef = {
perUserHomeTimelineCacheMax: { type: 'integer' }, perUserHomeTimelineCacheMax: { type: 'integer' },
perUserListTimelineCacheMax: { type: 'integer' }, perUserListTimelineCacheMax: { type: 'integer' },
notesPerOneAd: { type: 'integer' }, notesPerOneAd: { type: 'integer' },
silencedHosts: {
type: 'array',
nullable: true,
items: {
type: 'string',
},
},
}, },
required: [], required: [],
} as const; } as const;
@ -147,7 +164,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (Array.isArray(ps.sensitiveWords)) { if (Array.isArray(ps.sensitiveWords)) {
set.sensitiveWords = ps.sensitiveWords.filter(Boolean); set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
} }
if (Array.isArray(ps.silencedHosts)) {
let lastValue = '';
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
const lv = lastValue;
lastValue = h;
return h !== '' && h !== lv && !set.blockedHosts?.includes(h);
});
}
if (ps.themeColor !== undefined) { if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor; set.themeColor = ps.themeColor;
} }

View file

@ -36,6 +36,7 @@ export const paramDef = {
blocked: { type: 'boolean', nullable: true }, blocked: { type: 'boolean', nullable: true },
notResponding: { type: 'boolean', nullable: true }, notResponding: { type: 'boolean', nullable: true },
suspended: { type: 'boolean', nullable: true }, suspended: { type: 'boolean', nullable: true },
silenced: { type: "boolean", nullable: true },
federating: { type: 'boolean', nullable: true }, federating: { type: 'boolean', nullable: true },
subscribing: { type: 'boolean', nullable: true }, subscribing: { type: 'boolean', nullable: true },
publishing: { type: 'boolean', nullable: true }, publishing: { type: 'boolean', nullable: true },
@ -102,6 +103,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
} }
if (typeof ps.silenced === "boolean") {
const meta = await this.metaService.fetch(true);
if (ps.silenced) {
if (meta.silencedHosts.length === 0) {
return [];
}
query.andWhere("instance.host IN (:...silences)", {
silences: meta.silencedHosts,
});
} else if (meta.silencedHosts.length > 0) {
query.andWhere("instance.host NOT IN (:...silences)", {
silences: meta.silencedHosts,
});
}
}
if (typeof ps.federating === 'boolean') { if (typeof ps.federating === 'boolean') {
if (ps.federating) { if (ps.federating) {
query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))');

View file

@ -20,7 +20,7 @@
"@github/webauthn-json": "2.1.1", "@github/webauthn-json": "2.1.1",
"@rollup/plugin-alias": "5.0.1", "@rollup/plugin-alias": "5.0.1",
"@rollup/plugin-json": "6.0.1", "@rollup/plugin-json": "6.0.1",
"@rollup/plugin-replace": "5.0.3", "@rollup/plugin-replace": "5.0.4",
"@rollup/pluginutils": "5.0.5", "@rollup/pluginutils": "5.0.5",
"@syuilo/aiscript": "0.16.0", "@syuilo/aiscript": "0.16.0",
"@tabler/icons-webfont": "2.37.0", "@tabler/icons-webfont": "2.37.0",
@ -29,7 +29,7 @@
"@vue/compiler-sfc": "3.3.4", "@vue/compiler-sfc": "3.3.4",
"astring": "1.8.6", "astring": "1.8.6",
"autosize": "6.0.1", "autosize": "6.0.1",
"broadcast-channel": "5.3.0", "broadcast-channel": "5.4.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
"buraha": "0.0.1", "buraha": "0.0.1",
"canvas-confetti": "1.6.1", "canvas-confetti": "1.6.1",
@ -38,7 +38,7 @@
"chartjs-chart-matrix": "2.0.1", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.0.1",
"chromatic": "7.2.3", "chromatic": "7.4.0",
"compare-versions": "6.1.0", "compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4", "cropperjs": "2.0.0-beta.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
@ -57,9 +57,9 @@
"prismjs": "1.29.0", "prismjs": "1.29.0",
"punycode": "2.3.0", "punycode": "2.3.0",
"querystring": "0.2.1", "querystring": "0.2.1",
"rollup": "4.0.2", "rollup": "4.1.4",
"sanitize-html": "2.11.0", "sanitize-html": "2.11.0",
"sass": "1.69.1", "sass": "1.69.3",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.157.0", "three": "0.157.0",
@ -101,21 +101,21 @@
"@types/estree": "1.0.2", "@types/estree": "1.0.2",
"@types/matter-js": "0.19.1", "@types/matter-js": "0.19.1",
"@types/micromatch": "4.0.3", "@types/micromatch": "4.0.3",
"@types/node": "20.8.4", "@types/node": "20.8.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/sanitize-html": "2.9.1", "@types/sanitize-html": "2.9.2",
"@types/throttle-debounce": "5.0.0", "@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.4", "@types/tinycolor2": "1.4.4",
"@types/uuid": "9.0.5", "@types/uuid": "9.0.5",
"@types/websocket": "1.0.7", "@types/websocket": "1.0.7",
"@types/ws": "8.5.6", "@types/ws": "8.5.7",
"@typescript-eslint/eslint-plugin": "6.7.5", "@typescript-eslint/eslint-plugin": "6.7.5",
"@typescript-eslint/parser": "6.7.5", "@typescript-eslint/parser": "6.7.5",
"@vitest/coverage-v8": "0.34.6", "@vitest/coverage-v8": "0.34.6",
"@vue/runtime-core": "3.3.4", "@vue/runtime-core": "3.3.4",
"acorn": "8.10.0", "acorn": "8.10.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.3.0", "cypress": "13.3.1",
"eslint": "8.51.0", "eslint": "8.51.0",
"eslint-plugin-import": "2.28.1", "eslint-plugin-import": "2.28.1",
"eslint-plugin-vue": "9.17.0", "eslint-plugin-vue": "9.17.0",
@ -123,7 +123,7 @@
"happy-dom": "10.0.3", "happy-dom": "10.0.3",
"micromatch": "4.0.5", "micromatch": "4.0.5",
"msw": "1.3.2", "msw": "1.3.2",
"msw-storybook-addon": "1.8.0", "msw-storybook-addon": "1.9.0",
"nodemon": "3.0.1", "nodemon": "3.0.1",
"prettier": "3.0.3", "prettier": "3.0.3",
"react": "18.2.0", "react": "18.2.0",
@ -136,6 +136,6 @@
"vitest": "0.34.6", "vitest": "0.34.6",
"vitest-fetch-mock": "0.2.2", "vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.3.2", "vue-eslint-parser": "9.3.2",
"vue-tsc": "1.8.18" "vue-tsc": "1.8.19"
} }
} }

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]"> <div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended, blue: instance.isSilenced }]">
<img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/> <img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/>
<div class="body"> <div class="body">
<span class="host">{{ instance.name ?? instance.host }}</span> <span class="host">{{ instance.name ?? instance.host }}</span>
@ -89,6 +89,12 @@ function getInstanceIcon(instance): string {
height: 30px; height: 30px;
} }
&:global(.blue) {
--c: rgba(0, 42, 255, 0.15);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
}
&:global(.yellow) { &:global(.yellow) {
--c: rgb(255 196 0 / 15%); --c: rgb(255 196 0 / 15%);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);

View file

@ -18,6 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="subscribing">{{ i18n.ts.subscribing }}</option> <option value="subscribing">{{ i18n.ts.subscribing }}</option>
<option value="publishing">{{ i18n.ts.publishing }}</option> <option value="publishing">{{ i18n.ts.publishing }}</option>
<option value="suspended">{{ i18n.ts.suspended }}</option> <option value="suspended">{{ i18n.ts.suspended }}</option>
<option value="silenced">{{ i18n.ts.silence }}</option>
<option value="blocked">{{ i18n.ts.blocked }}</option> <option value="blocked">{{ i18n.ts.blocked }}</option>
<option value="notResponding">{{ i18n.ts.notResponding }}</option> <option value="notResponding">{{ i18n.ts.notResponding }}</option>
</MkSelect> </MkSelect>
@ -78,6 +79,7 @@ const pagination = {
state === 'publishing' ? { publishing: true } : state === 'publishing' ? { publishing: true } :
state === 'suspended' ? { suspended: true } : state === 'suspended' ? { suspended: true } :
state === 'blocked' ? { blocked: true } : state === 'blocked' ? { blocked: true } :
state === 'silenced' ? { silenced: true } :
state === 'notResponding' ? { notResponding: true } : state === 'notResponding' ? { notResponding: true } :
{}), {}),
})), })),
@ -86,6 +88,7 @@ const pagination = {
function getStatus(instance) { function getStatus(instance) {
if (instance.isSuspended) return 'Suspended'; if (instance.isSuspended) return 'Suspended';
if (instance.isBlocked) return 'Blocked'; if (instance.isBlocked) return 'Blocked';
if (instance.isSilenced) return 'Silenced';
if (instance.isNotResponding) return 'Error'; if (instance.isNotResponding) return 'Error';
return 'Alive'; return 'Alive';
} }

View file

@ -10,11 +10,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init"> <FormSuspense :p="init">
<MkTextarea v-model="blockedHosts"> <MkTextarea v-if="tab === 'block'" v-model="blockedHosts">
<span>{{ i18n.ts.blockedInstances }}</span> <span>{{ i18n.ts.blockedInstances }}</span>
<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template> <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
</MkTextarea> </MkTextarea>
<MkTextarea v-else-if="tab === 'silence'" v-model="silencedHosts" class="_formBlock">
<span>{{ i18n.ts.silencedInstances }}</span>
<template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
</MkTextarea>
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
</FormSuspense> </FormSuspense>
</MkSpacer> </MkSpacer>
@ -22,7 +25,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {} from 'vue';
import XHeader from './_header_.vue'; import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@ -33,15 +35,20 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
let blockedHosts: string = $ref(''); let blockedHosts: string = $ref('');
let silencedHosts: string = $ref('');
let tab = $ref('block');
async function init() { async function init() {
const meta = await os.api('admin/meta'); const meta = await os.api('admin/meta');
blockedHosts = meta.blockedHosts.join('\n'); blockedHosts = meta.blockedHosts.join('\n');
silencedHosts = meta.silencedHosts.join('\n');
} }
function save() { function save() {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
blockedHosts: blockedHosts.split('\n') || [], blockedHosts: blockedHosts.split('\n') || [],
silencedHosts: silencedHosts.split('\n') || [],
}).then(() => { }).then(() => {
fetchInstance(); fetchInstance();
}); });
@ -49,7 +56,15 @@ function save() {
const headerActions = $computed(() => []); const headerActions = $computed(() => []);
const headerTabs = $computed(() => []); const headerTabs = $computed(() => [{
key: 'block',
title: i18n.ts.block,
icon: 'ti ti-ban',
}, {
key: 'silence',
title: i18n.ts.silence,
icon: 'ti ti-eye-off',
}]);
definePageMetadata({ definePageMetadata({
title: i18n.ts.instanceBlocking, title: i18n.ts.instanceBlocking,

View file

@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s"> <div class="_gaps_s">
<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch> <MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch> <MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton> <MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
</div> </div>
</FormSection> </FormSection>
@ -147,6 +148,7 @@ let meta = $ref<Misskey.entities.AdminInstanceMetadata | null>(null);
let instance = $ref<Misskey.entities.Instance | null>(null); let instance = $ref<Misskey.entities.Instance | null>(null);
let suspended = $ref(false); let suspended = $ref(false);
let isBlocked = $ref(false); let isBlocked = $ref(false);
let isSilenced = $ref(false);
let faviconUrl = $ref<string | null>(null); let faviconUrl = $ref<string | null>(null);
const usersPagination = { const usersPagination = {
@ -169,6 +171,7 @@ async function fetch(): Promise<void> {
}); });
suspended = instance.isSuspended; suspended = instance.isSuspended;
isBlocked = instance.isBlocked; isBlocked = instance.isBlocked;
isSilenced = instance.isSilenced;
faviconUrl = getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.iconUrl, 'preview'); faviconUrl = getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.iconUrl, 'preview');
} }
@ -180,7 +183,14 @@ async function toggleBlock(): Promise<void> {
blockedHosts: isBlocked ? meta.blockedHosts.concat([host]) : meta.blockedHosts.filter(x => x !== host), blockedHosts: isBlocked ? meta.blockedHosts.concat([host]) : meta.blockedHosts.filter(x => x !== host),
}); });
} }
async function toggleSilenced(): Promise<void> {
if (!meta) throw new Error('No meta?');
if (!instance) throw new Error('No instance?');
const { host } = instance;
await os.api('admin/update-meta', {
silencedHosts: isSilenced ? meta.silencedHosts.concat([host]) : meta.silencedHosts.filter(x => x !== host),
});
}
async function toggleSuspend(): Promise<void> { async function toggleSuspend(): Promise<void> {
if (!instance) throw new Error('No instance?'); if (!instance) throw new Error('No instance?');
await os.api('admin/federation/update-instance', { await os.api('admin/federation/update-instance', {

View file

@ -29,6 +29,7 @@ type Ad = TODO_2;
// @public (undocumented) // @public (undocumented)
type AdminInstanceMetadata = DetailedInstanceMetadata & { type AdminInstanceMetadata = DetailedInstanceMetadata & {
blockedHosts: string[]; blockedHosts: string[];
silencedHosts: string[];
app192IconUrl: string | null; app192IconUrl: string | null;
app512IconUrl: string | null; app512IconUrl: string | null;
manifestJsonOverride: string; manifestJsonOverride: string;
@ -2360,6 +2361,7 @@ type Instance = {
lastCommunicatedAt: DateString; lastCommunicatedAt: DateString;
isNotResponding: boolean; isNotResponding: boolean;
isSuspended: boolean; isSuspended: boolean;
isSilenced: boolean;
isBlocked: boolean; isBlocked: boolean;
softwareName: string | null; softwareName: string | null;
softwareVersion: string | null; softwareVersion: string | null;
@ -2986,7 +2988,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
// src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts // src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
// src/entities.ts:601:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/entities.ts:603:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package) // (No @packageDocumentation comment for this package)

View file

@ -23,7 +23,7 @@
"@microsoft/api-extractor": "7.38.0", "@microsoft/api-extractor": "7.38.0",
"@swc/jest": "0.2.29", "@swc/jest": "0.2.29",
"@types/jest": "29.5.5", "@types/jest": "29.5.5",
"@types/node": "20.8.4", "@types/node": "20.8.6",
"@typescript-eslint/eslint-plugin": "6.7.5", "@typescript-eslint/eslint-plugin": "6.7.5",
"@typescript-eslint/parser": "6.7.5", "@typescript-eslint/parser": "6.7.5",
"eslint": "8.51.0", "eslint": "8.51.0",
@ -39,7 +39,7 @@
], ],
"dependencies": { "dependencies": {
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.92", "@swc/core": "1.3.93",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"reconnecting-websocket": "4.4.0" "reconnecting-websocket": "4.4.0"
} }

View file

@ -386,6 +386,7 @@ export type InstanceMetadata = LiteInstanceMetadata | DetailedInstanceMetadata;
export type AdminInstanceMetadata = DetailedInstanceMetadata & { export type AdminInstanceMetadata = DetailedInstanceMetadata & {
// TODO: There are more fields. // TODO: There are more fields.
blockedHosts: string[]; blockedHosts: string[];
silencedHosts: string[];
app192IconUrl: string | null; app192IconUrl: string | null;
app512IconUrl: string | null; app512IconUrl: string | null;
manifestJsonOverride: string; manifestJsonOverride: string;
@ -546,6 +547,7 @@ export type Instance = {
lastCommunicatedAt: DateString; lastCommunicatedAt: DateString;
isNotResponding: boolean; isNotResponding: boolean;
isSuspended: boolean; isSuspended: boolean;
isSilenced: boolean;
isBlocked: boolean; isBlocked: boolean;
softwareName: string | null; softwareName: string | null;
softwareVersion: string | null; softwareVersion: string | null;

File diff suppressed because it is too large Load diff