From 082e48056cd46883e73736a9a573eb666c0544d5 Mon Sep 17 00:00:00 2001 From: mattyatea Date: Fri, 27 Oct 2023 10:32:22 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=E3=82=A2=E3=83=90=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=83=87=E3=82=B3=E3=83=AC=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E5=8F=97=E4=BF=A1=E3=82=92=E8=A8=B1=E5=8F=AF=E5=88=B6?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: mattyatea --- locales/index.d.ts | 2 + locales/ja-JP.yml | 2 + ...698363835246-avatardecorationaccepthost.js | 11 ++++ packages/backend/src/core/UtilityService.ts | 6 +++ .../activitypub/models/ApPersonService.ts | 51 ++++++++++--------- packages/backend/src/models/Meta.ts | 5 ++ .../queue/processors/CleanProcessorService.ts | 14 +++++ .../admin/avatar-decorations/list.ts | 3 +- .../src/server/api/endpoints/admin/meta.ts | 11 ++++ .../server/api/endpoints/admin/update-meta.ts | 31 ++++++++--- .../api/endpoints/get-avatar-decorations.ts | 2 +- .../src/server/api/endpoints/i/update.ts | 1 + .../src/pages/admin/avatar-decorations.vue | 33 ++++++++++-- packages/misskey-js/src/entities.ts | 1 + 14 files changed, 135 insertions(+), 38 deletions(-) create mode 100644 packages/backend/migration/1698363835246-avatardecorationaccepthost.js diff --git a/locales/index.d.ts b/locales/index.d.ts index bfe25c94a7..284beec8af 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -220,6 +220,8 @@ export interface Locale { "blockedInstancesDescription": string; "silencedInstances": string; "silencedInstancesDescription": string; + "avatarDecorationsAcceptInstance": string; + "avatarDecorationsAcceptInstancesDescription": string; "muteAndBlock": string; "mutedUsers": string; "blockedUsers": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2b475e2134..2494c955b9 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -217,6 +217,8 @@ blockedInstances: "ブロックしたサーバー" blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。" silencedInstances: "サイレンスしたサーバー" silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。" +avatarDecorationsAcceptInstance: "アイコンデコレーションを表示できるサーバー" +avatarDecorationsAcceptInstancesDescription: "アイコンデコレーションを受け入れたいサーバーのホストを改行で区切って設定します。" muteAndBlock: "ミュートとブロック" mutedUsers: "ミュートしたユーザー" blockedUsers: "ブロックしたユーザー" diff --git a/packages/backend/migration/1698363835246-avatardecorationaccepthost.js b/packages/backend/migration/1698363835246-avatardecorationaccepthost.js new file mode 100644 index 0000000000..2a22a05630 --- /dev/null +++ b/packages/backend/migration/1698363835246-avatardecorationaccepthost.js @@ -0,0 +1,11 @@ +export class Avatardecorationaccepthost1698363835246 { + name = 'Avatardecorationaccepthost1698363835246' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "avatarDecorationAcceptHosts" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "avatarDecorationAcceptHosts"`); + } +} diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index b95e41167b..83a4777975 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -41,6 +41,12 @@ export class UtilityService { return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } + @bindThis + public avatarDecorationAcceptHost(AcceptHost: string[] | undefined, host: string | null): boolean { + if (!AcceptHost || host == null) return false; + return AcceptHost.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); + } + @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index ec54ec0f9c..3fa611cefa 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -294,21 +294,24 @@ export class ApPersonService implements OnModuleInit { //#endregion //#region アバターデコレーション取得 - const _avatarDecorations = await this.apNoteService.extractAvatarDecorations(person.tag ?? [], host) - .catch(err => { - this.logger.error('error occurred while fetching user avatar decorations', { stack: err }); - return []; - }); const avatarDecorations: {id: string, angle: number, flipH: boolean}[] = []; + if (this.utilityService.avatarDecorationAcceptHost((await this.metaService.fetch()).avatarDecorationAcceptHosts, host)) { + const _avatarDecorations = await this.apNoteService.extractAvatarDecorations(person.tag ?? [], host) + .catch(err => { + this.logger.error('error occurred while fetching user avatar decorations', { stack: err }); + return []; + }); - _avatarDecorations.forEach((value, index) => { - avatarDecorations.push({ - id: value.id, - angle: person.AvatarDecorations ? person.AvatarDecorations[index].angle : 0, - flipH: person.AvatarDecorations ? person.AvatarDecorations[index].flipH : false, + _avatarDecorations.forEach((value, index) => { + avatarDecorations.push({ + id: value.id, + angle: person.AvatarDecorations ? person.AvatarDecorations[index].angle : 0, + flipH: person.AvatarDecorations ? person.AvatarDecorations[index].flipH : false, + }); }); - }); + } + //#endregion try { @@ -439,22 +442,22 @@ export class ApPersonService implements OnModuleInit { const person = this.validateActor(object, uri); this.logger.info(`Updating the Person: ${person.id}`); - - const _avatarDecorations = await this.apNoteService.extractAvatarDecorations(person.tag ?? [], exist.host) - .catch(err => { - this.logger.error('error occurred while fetching user avatar decorations', { stack: err }); - return []; - }); - const avatarDecorations: {id: string, angle: number, flipH: boolean}[] = []; + if (this.utilityService.avatarDecorationAcceptHost((await this.metaService.fetch()).avatarDecorationAcceptHosts, exist.host)) { + const _avatarDecorations = await this.apNoteService.extractAvatarDecorations(person.tag ?? [], exist.host) + .catch(err => { + this.logger.error('error occurred while fetching user avatar decorations', { stack: err }); + return []; + }); - _avatarDecorations.forEach((value, index) => { - avatarDecorations.push({ - id: value.id, - angle: person.AvatarDecorations ? person.AvatarDecorations[index].angle : 0, - flipH: person.AvatarDecorations ? person.AvatarDecorations[index].flipH : false, + _avatarDecorations.forEach((value, index) => { + avatarDecorations.push({ + id: value.id, + angle: person.AvatarDecorations ? person.AvatarDecorations[index].angle : 0, + flipH: person.AvatarDecorations ? person.AvatarDecorations[index].flipH : false, + }); }); - }); + } // カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 360239f509..2e9a8a940c 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -81,6 +81,11 @@ export class MiMeta { }) public silencedHosts: string[]; + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public avatarDecorationAcceptHosts: string[]; + @Column('varchar', { length: 1024, nullable: true, diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index e252c5d8a1..8d096d720f 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -11,6 +11,9 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; +import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -33,6 +36,9 @@ export class CleanProcessorService { private queueLoggerService: QueueLoggerService, private idService: IdService, + private metaService: MetaService, + private utilityService: UtilityService, + private avatarDecorationService: AvatarDecorationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('clean'); } @@ -54,6 +60,14 @@ export class CleanProcessorService { }); } + const { avatarDecorationAcceptHosts } = await this.metaService.fetch(); + const allAvatarDecorations = await this.avatarDecorationService.getAll(); + for (const avatarDecoration of allAvatarDecorations) { + if (avatarDecoration.host !== null && !this.utilityService.avatarDecorationAcceptHost(avatarDecorationAcceptHosts, avatarDecoration.host)) { + await this.avatarDecorationService.delete(avatarDecoration.id); + } + } + const expiredRoleAssignments = await this.roleAssignmentsRepository.createQueryBuilder('assign') .where('assign.expiresAt IS NOT NULL') .andWhere('assign.expiresAt < :now', { now: new Date() }) diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts index 9a32a59081..03381cec8d 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts @@ -86,8 +86,7 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const avatarDecorations = await this.avatarDecorationService.getAll(true); - - return avatarDecorations.map(avatarDecoration => ({ + return avatarDecorations.filter(x => x.host === null).map(avatarDecoration => ({ id: avatarDecoration.id, createdAt: this.idService.parse(avatarDecoration.id).date.toISOString(), updatedAt: avatarDecoration.updatedAt?.toISOString() ?? null, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 73c84a8674..abedeafcac 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -115,6 +115,16 @@ export const meta = { nullable: false, }, }, + avatarDecorationAcceptHosts: { + type: 'array', + optional: true, + nullable: false, + items: { + type: 'string', + optional: false, + nullable: false, + }, + }, pinnedUsers: { type: 'array', optional: false, nullable: false, @@ -382,6 +392,7 @@ export default class extends Endpoint { // eslint- hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, silencedHosts: instance.silencedHosts, + avatarDecorationAcceptHosts: instance.avatarDecorationAcceptHosts, sensitiveWords: instance.sensitiveWords, preservedUsernames: instance.preservedUsernames, hcaptchaSecretKey: instance.hcaptchaSecretKey, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index c58569a31c..2a51fac756 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -35,6 +35,20 @@ export const paramDef = { type: 'string', }, }, + silencedHosts: { + type: 'array', + nullable: true, + items: { + type: 'string', + }, + }, + avatarDecorationAcceptHosts: { + type: 'array', + nullable: true, + items: { + type: 'string', + }, + }, sensitiveWords: { type: 'array', nullable: true, items: { type: 'string', @@ -126,13 +140,6 @@ export const paramDef = { perUserHomeTimelineCacheMax: { type: 'integer' }, perUserListTimelineCacheMax: { type: 'integer' }, notesPerOneAd: { type: 'integer' }, - silencedHosts: { - type: 'array', - nullable: true, - items: { - type: 'string', - }, - }, }, required: [], } as const; @@ -173,6 +180,16 @@ export default class extends Endpoint { // eslint- return h !== '' && h !== lv && !set.blockedHosts?.includes(h); }); } + + if (Array.isArray(ps.avatarDecorationAcceptHosts)) { + let lastValue = ''; + set.avatarDecorationAcceptHosts = ps.avatarDecorationAcceptHosts.sort().filter((h) => { + const lv = lastValue; + lastValue = h; + return h !== '' && h !== lv && !set.avatarDecorationAcceptHosts?.includes(h); + }); + } + if (ps.themeColor !== undefined) { set.themeColor = ps.themeColor; } diff --git a/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts b/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts index dbe1626149..00fa9d3d93 100644 --- a/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts +++ b/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts @@ -70,7 +70,7 @@ export default class extends Endpoint { // eslint- const decorations = await this.avatarDecorationService.getAll(true); const allRoles = await this.roleService.getRoles(); - return decorations.map(decoration => ({ + return decorations.filter(x => x.host === null).map(decoration => ({ id: decoration.id, name: decoration.name, description: decoration.description, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index b03381a3f3..e0067e33bf 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -313,6 +313,7 @@ export default class extends Endpoint { // eslint- const allRoles = await this.roleService.getRoles(); const decorationIds = decorations .filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id))) + .filter(d => d.host === null) .map(d => d.id); updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({ diff --git a/packages/frontend/src/pages/admin/avatar-decorations.vue b/packages/frontend/src/pages/admin/avatar-decorations.vue index b4007e6d20..78dde77291 100644 --- a/packages/frontend/src/pages/admin/avatar-decorations.vue +++ b/packages/frontend/src/pages/admin/avatar-decorations.vue @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -48,7 +55,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkFolder from '@/components/MkFolder.vue'; let avatarDecorations: any[] = $ref([]); - +let tab = $ref('avatarDecorations'); +let acceptHosts: string = $ref(''); function add() { avatarDecorations.unshift({ _id: Math.random().toString(36), @@ -79,10 +87,19 @@ async function save(avatarDecoration) { } } +async function acceptSave() { + await os.apiWithDialog('admin/update-meta', { + avatarDecorationAcceptHosts: acceptHosts.split('\n') || [], + }); +} + function load() { os.api('admin/avatar-decorations/list').then(_avatarDecorations => { avatarDecorations = _avatarDecorations; }); + os.api('admin/meta').then(_meta => { + acceptHosts = _meta.avatarDecorationAcceptHosts.join('\n'); + }); } load(); @@ -94,7 +111,15 @@ const headerActions = $computed(() => [{ handler: add, }]); -const headerTabs = $computed(() => []); +const headerTabs = $computed(() => [{ + key: 'avatarDecorations', + title: i18n.ts.avatarDecorations, + icon: 'ti ti-sparkles', +}, { + key: 'avatarDecorationsAcceptHosts', + title: i18n.ts.avatarDecorationsAcceptInstance, + icon: 'ti ti-thumb-up-filled', +}]); definePageMetadata({ title: i18n.ts.avatarDecorations, diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 38bac3b7c3..8bd87c7206 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -393,6 +393,7 @@ export type InstanceMetadata = LiteInstanceMetadata | DetailedInstanceMetadata; export type AdminInstanceMetadata = DetailedInstanceMetadata & { // TODO: There are more fields. blockedHosts: string[]; + avatarDecorationAcceptHosts: string[]; silencedHosts: string[]; app192IconUrl: string | null; app512IconUrl: string | null;