feat : サーバーサイレンスを追加
This commit is contained in:
parent
096fa16c4c
commit
a92ef26cbb
|
|
@ -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 "flash" ALTER COLUMN "visibility" DROP NOT NULL`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
|
import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import { IsNull } from 'typeorm';
|
import {DataSource, 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';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
|
@ -29,6 +29,8 @@ 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 Logger from '../logger.js';
|
import Logger from '../logger.js';
|
||||||
|
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||||
|
|
||||||
|
|
||||||
const logger = new Logger('following/create');
|
const logger = new Logger('following/create');
|
||||||
|
|
||||||
|
|
@ -52,6 +54,9 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
|
||||||
|
@Inject(DI.db)
|
||||||
|
private db: DataSource,
|
||||||
|
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
|
|
@ -121,12 +126,14 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
|
|
||||||
// フォロー対象が鍵アカウントである 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) && await shouldSilenceInstance(follower.host,this.db))
|
||||||
) {
|
) {
|
||||||
let autoAccept = false;
|
let autoAccept = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,23 @@
|
||||||
* 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 { } 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';
|
||||||
import { UtilityService } from '../UtilityService.js';
|
import { UtilityService } from '../UtilityService.js';
|
||||||
|
import {shouldSilenceInstance} from "@/misc/should-block-instance.js";
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import {DI} from "@/di-symbols.js";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InstanceEntityService {
|
export class InstanceEntityService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.db)
|
||||||
|
private db: DataSource,
|
||||||
|
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
|
|
@ -43,6 +49,7 @@ export class InstanceEntityService {
|
||||||
description: instance.description,
|
description: instance.description,
|
||||||
maintainerName: instance.maintainerName,
|
maintainerName: instance.maintainerName,
|
||||||
maintainerEmail: instance.maintainerEmail,
|
maintainerEmail: instance.maintainerEmail,
|
||||||
|
isSilenced: await shouldSilenceInstance(instance.host,this.db),
|
||||||
iconUrl: instance.iconUrl,
|
iconUrl: instance.iconUrl,
|
||||||
faviconUrl: instance.faviconUrl,
|
faviconUrl: instance.faviconUrl,
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { MiMeta } from "@/models/Meta.js";
|
||||||
|
|
||||||
|
let cache: MiMeta;
|
||||||
|
|
||||||
|
export async function fetchMeta(noCache = false , db: DataSource): Promise<MiMeta> {
|
||||||
|
if (!noCache && cache) return cache;
|
||||||
|
|
||||||
|
return await db.transaction(async (transactionalEntityManager) => {
|
||||||
|
// New IDs are prioritized because multiple records may have been created due to past bugs.
|
||||||
|
const metas = await transactionalEntityManager.find(MiMeta, {
|
||||||
|
order: {
|
||||||
|
id: "DESC",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const meta = metas[0];
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
cache = meta;
|
||||||
|
return meta;
|
||||||
|
} else {
|
||||||
|
// If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert.
|
||||||
|
const saved = await transactionalEntityManager
|
||||||
|
.upsert(
|
||||||
|
MiMeta,
|
||||||
|
{
|
||||||
|
id: "x",
|
||||||
|
},
|
||||||
|
["id"],
|
||||||
|
)
|
||||||
|
.then((x) =>
|
||||||
|
transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0]),
|
||||||
|
);
|
||||||
|
|
||||||
|
cache = saved;
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||||
|
import type { MiInstance } from "@/models/Instance.js";
|
||||||
|
import type { MiMeta } from "@/models/Meta.js";
|
||||||
|
import { DataSource } from "typeorm";
|
||||||
|
|
||||||
|
export async function shouldSilenceInstance(
|
||||||
|
host: MiInstance["host"],
|
||||||
|
db : DataSource,
|
||||||
|
meta?: MiMeta,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const { silencedHosts } = meta ?? (await fetchMeta(true,db));
|
||||||
|
return silencedHosts.some(
|
||||||
|
(limitedHost: string) => host === limitedHost || host.endsWith(`.${limitedHost}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import {Injectable} from '@nestjs/common';
|
||||||
import type { MiMeta } from '@/models/Meta.js';
|
import type {MiMeta} from '@/models/Meta.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import {ModerationLogService} from '@/core/ModerationLogService.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import {Endpoint} from '@/server/api/endpoint-base.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import {MetaService} from '@/core/MetaService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
@ -19,102 +19,119 @@ export const meta = {
|
||||||
export const paramDef = {
|
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}$' },
|
},
|
||||||
mascotImageUrl: { type: 'string', nullable: true },
|
themeColor: {type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$'},
|
||||||
bannerUrl: { type: 'string', nullable: true },
|
mascotImageUrl: {type: 'string', nullable: true},
|
||||||
serverErrorImageUrl: { type: 'string', nullable: true },
|
bannerUrl: {type: 'string', nullable: true},
|
||||||
infoImageUrl: { type: 'string', nullable: true },
|
serverErrorImageUrl: {type: 'string', nullable: true},
|
||||||
notFoundImageUrl: { type: 'string', nullable: true },
|
infoImageUrl: {type: 'string', nullable: true},
|
||||||
iconUrl: { type: 'string', nullable: true },
|
notFoundImageUrl: {type: 'string', nullable: true},
|
||||||
app192IconUrl: { type: 'string', nullable: true },
|
iconUrl: {type: 'string', nullable: true},
|
||||||
app512IconUrl: { type: 'string', nullable: true },
|
app192IconUrl: {type: 'string', nullable: true},
|
||||||
backgroundImageUrl: { type: 'string', nullable: true },
|
app512IconUrl: {type: 'string', nullable: true},
|
||||||
logoImageUrl: { type: 'string', nullable: true },
|
backgroundImageUrl: {type: 'string', nullable: true},
|
||||||
name: { type: 'string', nullable: true },
|
logoImageUrl: {type: 'string', nullable: true},
|
||||||
shortName: { type: 'string', nullable: true },
|
name: {type: 'string', nullable: true},
|
||||||
description: { type: 'string', nullable: true },
|
shortName: {type: 'string', nullable: true},
|
||||||
defaultLightTheme: { type: 'string', nullable: true },
|
description: {type: 'string', nullable: true},
|
||||||
defaultDarkTheme: { type: 'string', nullable: true },
|
defaultLightTheme: {type: 'string', nullable: true},
|
||||||
cacheRemoteFiles: { type: 'boolean' },
|
defaultDarkTheme: {type: 'string', nullable: true},
|
||||||
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
cacheRemoteFiles: {type: 'boolean'},
|
||||||
emailRequiredForSignup: { type: 'boolean' },
|
cacheRemoteSensitiveFiles: {type: 'boolean'},
|
||||||
enableHcaptcha: { type: 'boolean' },
|
emailRequiredForSignup: {type: 'boolean'},
|
||||||
hcaptchaSiteKey: { type: 'string', nullable: true },
|
enableHcaptcha: {type: 'boolean'},
|
||||||
hcaptchaSecretKey: { type: 'string', nullable: true },
|
hcaptchaSiteKey: {type: 'string', nullable: true},
|
||||||
enableRecaptcha: { type: 'boolean' },
|
hcaptchaSecretKey: {type: 'string', nullable: true},
|
||||||
recaptchaSiteKey: { type: 'string', nullable: true },
|
enableRecaptcha: {type: 'boolean'},
|
||||||
recaptchaSecretKey: { type: 'string', nullable: true },
|
recaptchaSiteKey: {type: 'string', nullable: true},
|
||||||
enableTurnstile: { type: 'boolean' },
|
recaptchaSecretKey: {type: 'string', nullable: true},
|
||||||
turnstileSiteKey: { type: 'string', nullable: true },
|
enableTurnstile: {type: 'boolean'},
|
||||||
turnstileSecretKey: { type: 'string', nullable: true },
|
turnstileSiteKey: {type: 'string', nullable: true},
|
||||||
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
|
turnstileSecretKey: {type: 'string', nullable: true},
|
||||||
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
sensitiveMediaDetection: {type: 'string', enum: ['none', 'all', 'local', 'remote']},
|
||||||
setSensitiveFlagAutomatically: { type: 'boolean' },
|
sensitiveMediaDetectionSensitivity: {type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh']},
|
||||||
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
|
setSensitiveFlagAutomatically: {type: 'boolean'},
|
||||||
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
|
enableSensitiveMediaDetectionForVideos: {type: 'boolean'},
|
||||||
maintainerName: { type: 'string', nullable: true },
|
proxyAccountId: {type: 'string', format: 'misskey:id', nullable: true},
|
||||||
maintainerEmail: { type: 'string', nullable: true },
|
maintainerName: {type: 'string', nullable: true},
|
||||||
langs: { type: 'array', items: {
|
maintainerEmail: {type: 'string', nullable: true},
|
||||||
|
langs: {
|
||||||
|
type: 'array', items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
}
|
||||||
summalyProxy: { type: 'string', nullable: true },
|
},
|
||||||
deeplAuthKey: { type: 'string', nullable: true },
|
summalyProxy: {type: 'string', nullable: true},
|
||||||
deeplIsPro: { type: 'boolean' },
|
deeplAuthKey: {type: 'string', nullable: true},
|
||||||
enableEmail: { type: 'boolean' },
|
deeplIsPro: {type: 'boolean'},
|
||||||
email: { type: 'string', nullable: true },
|
enableEmail: {type: 'boolean'},
|
||||||
smtpSecure: { type: 'boolean' },
|
email: {type: 'string', nullable: true},
|
||||||
smtpHost: { type: 'string', nullable: true },
|
smtpSecure: {type: 'boolean'},
|
||||||
smtpPort: { type: 'integer', nullable: true },
|
smtpHost: {type: 'string', nullable: true},
|
||||||
smtpUser: { type: 'string', nullable: true },
|
smtpPort: {type: 'integer', nullable: true},
|
||||||
smtpPass: { type: 'string', nullable: true },
|
smtpUser: {type: 'string', nullable: true},
|
||||||
enableServiceWorker: { type: 'boolean' },
|
smtpPass: {type: 'string', nullable: true},
|
||||||
swPublicKey: { type: 'string', nullable: true },
|
enableServiceWorker: {type: 'boolean'},
|
||||||
swPrivateKey: { type: 'string', nullable: true },
|
swPublicKey: {type: 'string', nullable: true},
|
||||||
tosUrl: { type: 'string', nullable: true },
|
swPrivateKey: {type: 'string', nullable: true},
|
||||||
repositoryUrl: { type: 'string' },
|
tosUrl: {type: 'string', nullable: true},
|
||||||
feedbackUrl: { type: 'string' },
|
repositoryUrl: {type: 'string'},
|
||||||
impressumUrl: { type: 'string' },
|
feedbackUrl: {type: 'string'},
|
||||||
privacyPolicyUrl: { type: 'string' },
|
impressumUrl: {type: 'string'},
|
||||||
useObjectStorage: { type: 'boolean' },
|
privacyPolicyUrl: {type: 'string'},
|
||||||
objectStorageBaseUrl: { type: 'string', nullable: true },
|
useObjectStorage: {type: 'boolean'},
|
||||||
objectStorageBucket: { type: 'string', nullable: true },
|
objectStorageBaseUrl: {type: 'string', nullable: true},
|
||||||
objectStoragePrefix: { type: 'string', nullable: true },
|
objectStorageBucket: {type: 'string', nullable: true},
|
||||||
objectStorageEndpoint: { type: 'string', nullable: true },
|
objectStoragePrefix: {type: 'string', nullable: true},
|
||||||
objectStorageRegion: { type: 'string', nullable: true },
|
objectStorageEndpoint: {type: 'string', nullable: true},
|
||||||
objectStoragePort: { type: 'integer', nullable: true },
|
objectStorageRegion: {type: 'string', nullable: true},
|
||||||
objectStorageAccessKey: { type: 'string', nullable: true },
|
objectStoragePort: {type: 'integer', nullable: true},
|
||||||
objectStorageSecretKey: { type: 'string', nullable: true },
|
objectStorageAccessKey: {type: 'string', nullable: true},
|
||||||
objectStorageUseSSL: { type: 'boolean' },
|
objectStorageSecretKey: {type: 'string', nullable: true},
|
||||||
objectStorageUseProxy: { type: 'boolean' },
|
objectStorageUseSSL: {type: 'boolean'},
|
||||||
objectStorageSetPublicRead: { type: 'boolean' },
|
objectStorageUseProxy: {type: 'boolean'},
|
||||||
objectStorageS3ForcePathStyle: { type: 'boolean' },
|
objectStorageSetPublicRead: {type: 'boolean'},
|
||||||
enableIpLogging: { type: 'boolean' },
|
objectStorageS3ForcePathStyle: {type: 'boolean'},
|
||||||
enableActiveEmailValidation: { type: 'boolean' },
|
enableIpLogging: {type: 'boolean'},
|
||||||
enableChartsForRemoteUser: { type: 'boolean' },
|
enableActiveEmailValidation: {type: 'boolean'},
|
||||||
enableChartsForFederatedInstances: { type: 'boolean' },
|
enableChartsForRemoteUser: {type: 'boolean'},
|
||||||
enableServerMachineStats: { type: 'boolean' },
|
enableChartsForFederatedInstances: {type: 'boolean'},
|
||||||
enableIdenticonGeneration: { type: 'boolean' },
|
enableServerMachineStats: {type: 'boolean'},
|
||||||
serverRules: { type: 'array', items: { type: 'string' } },
|
enableIdenticonGeneration: {type: 'boolean'},
|
||||||
preservedUsernames: { type: 'array', items: { type: 'string' } },
|
serverRules: {type: 'array', items: {type: 'string'}},
|
||||||
manifestJsonOverride: { type: 'string' },
|
preservedUsernames: {type: 'array', items: {type: 'string'}},
|
||||||
perLocalUserUserTimelineCacheMax: { type: 'integer' },
|
manifestJsonOverride: {type: 'string'},
|
||||||
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
|
perLocalUserUserTimelineCacheMax: {type: 'integer'},
|
||||||
perUserHomeTimelineCacheMax: { type: 'integer' },
|
perRemoteUserUserTimelineCacheMax: {type: 'integer'},
|
||||||
perUserListTimelineCacheMax: { type: 'integer' },
|
perUserHomeTimelineCacheMax: {type: 'integer'},
|
||||||
notesPerOneAd: { type: 'integer' },
|
perUserListTimelineCacheMax: {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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))');
|
||||||
|
|
|
||||||
|
|
@ -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%);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -75,6 +76,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 } :
|
||||||
{}),
|
{}),
|
||||||
})),
|
})),
|
||||||
|
|
@ -83,6 +85,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';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><XHeader :actions="headerActions" v-model:tab="tab" :tabs="headerTabs"/></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>
|
||||||
|
|
@ -31,15 +36,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();
|
||||||
});
|
});
|
||||||
|
|
@ -47,7 +57,18 @@ function save() {
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => [
|
||||||
|
{
|
||||||
|
key: "block",
|
||||||
|
title: i18n.ts.block,
|
||||||
|
icon: "ph-prohibit ph-bold ph-lg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "silence",
|
||||||
|
title: i18n.ts.silence,
|
||||||
|
icon: "ph-eye-slash ph-bold ph-lg",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
definePageMetadata({
|
definePageMetadata({
|
||||||
title: i18n.ts.instanceBlocking,
|
title: i18n.ts.instanceBlocking,
|
||||||
|
|
|
||||||
|
|
@ -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.silence }}</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', {
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,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;
|
||||||
|
|
@ -544,6 +545,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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue