diff --git a/locales/en-US.yml b/locales/en-US.yml index 3f8157b1d1aa..d8ebeb093543 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1525,8 +1525,7 @@ admin/views/instance.vue: smtp-auth: "Perform SMTP authentication" smtp-user: "SMTP User" smtp-pass: "SMTP Password" - test-email-address: "Test mail address" - test-mail: "Send Test mail" + test-mail: "Test mail" serviceworker-config: "ServiceWorker" enable-serviceworker: "Enable ServiceWorker" serviceworker-info: "Must be enabled for push notifications." @@ -1786,6 +1785,8 @@ admin/views/instanceblocks.vue: silencedInstances-info: "Instances to recieve with maximum visibility home. One host per line. You can use /RegExp/." selfSilencedInstances: "Self-silenced instances" selfSilencedInstances-info: "Instances to deliver with maximum visibility home. One host per line. You can use /RegExp/." + mutedFiles: "Attachments to mute" + mutedFiles-info: "Mute the specified file if it is attached. MD5 lower case." exposeHome: "If fetched from remote, max visibility to home." exposeHome-info: "If posts are retrieved from instances with no followers, etc., set the maximum visibility to Home." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a09aee44abd9..95f421cf4d54 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1675,8 +1675,7 @@ admin/views/instance.vue: smtp-auth: "SMTP認証を行う" smtp-user: "SMTPユーザー" smtp-pass: "SMTPパスワード" - test-email-address: "テストメールの送信先" - test-mail: "テストメールを送信" + test-mail: "テスト送信" serviceworker-config: "ServiceWorker" enable-serviceworker: "ServiceWorkerを有効にする" serviceworker-info: "プッシュ通知を行うには有効する必要があります。" @@ -1949,6 +1948,8 @@ admin/views/instanceblocks.vue: silencedInstances-info: "最大公開範囲ホームで受信するインスタンス。1行に1ホスト記述します。/正規表現/ が使えます。" selfSilencedInstances: "セルフサイレンスするインスタンス" selfSilencedInstances-info: "最大公開範囲ホームで配信するインスタンス。1行に1ホスト記述します。/正規表現/ が使えます。" + mutedFiles: "ミュートする添付ファイル" + mutedFiles-info: "指定のファイルが投稿に添付されている場合にミュートします。MD5小文字。" exposeHome: 取得されるときの最大公開範囲はホームにする exposeHome-info: "フォロワーのいないインスタンスなどから投稿を取得されるときの最大公開範囲をホームにします。" diff --git a/package.json b/package.json index 96dc29ef5ac8..3282d77ada76 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "mei23 ", - "version": "10.102.689-m544", + "version": "10.102.690-m544", "codename": "m544", "repository": { "type": "git", diff --git a/src/client/app/admin/views/cards/email.vue b/src/client/app/admin/views/cards/email.vue index 227aec5b5e14..880ea796ea63 100644 --- a/src/client/app/admin/views/cards/email.vue +++ b/src/client/app/admin/views/cards/email.vue @@ -17,7 +17,6 @@ {{ $t('smtp-pass') }} {{ $t('smtp-secure') }} - {{ $t('test-email-address') }} {{ $t('test-mail') }} @@ -57,9 +56,6 @@ export default defineComponent({ smtpUser: null, smtpPass: null, smtpAuth: false, - testEmailAddress: null, - - maintainerEmail: null, // icons アイコンを追加したらここをいじる 2/2 farEnvelope, @@ -84,9 +80,6 @@ export default defineComponent({ this.smtpUser = meta.smtpUser; this.smtpPass = meta.smtpPass; this.smtpAuth = meta.smtpUser != null && meta.smtpUser !== ''; - this.testEmailAddress = meta.testEmailAddress; - - this.maintainerEmail = meta.maintainer.email; }); }, @@ -108,7 +101,6 @@ export default defineComponent({ smtpPort: parseInt(this.smtpPort, 10), smtpUser: this.smtpAuth ? this.smtpUser : '', smtpPass: this.smtpAuth ? this.smtpPass : '', - testEmailAddress: this.testEmailAddress, }).then(() => { this.fetchMeta(); this.$root.dialog({ @@ -124,10 +116,18 @@ export default defineComponent({ }, async testEmail() { + const { canceled, result } = await this.$root.dialog({ + title: 'To', + input: { + type: 'text' + } + }); + if (canceled) return; + this.$root.api('admin/send-email', { - to: this.testEmailAddress, - subject: 'Test email from Misskey', - text: 'Test email from your Misskey instance.' + to: result, + subject: 'Test email', + text: 'Na' }).then((e: Error) => { this.$root.dialog({ type: 'success', diff --git a/src/client/app/admin/views/cards/general.vue b/src/client/app/admin/views/cards/general.vue index 1a0f9b3d5f2a..ab65f967bcb8 100644 --- a/src/client/app/admin/views/cards/general.vue +++ b/src/client/app/admin/views/cards/general.vue @@ -13,10 +13,10 @@
-
{{ $t('maintainer-config') }}
+
{{ $t('maintainer-config') }}
{{ $t('maintainer-name') }} - {{ $t('maintainer-email') }} + {{ $t('maintainer-email') }}
@@ -42,7 +42,7 @@ import { defineComponent, getCurrentInstance } from 'vue'; import i18n from '../../../i18n'; // アイコンを追加したらここをいじる 1/2 -import { faHeadset } from '@fortawesome/free-solid-svg-icons'; +import { faCrown } from '@fortawesome/free-solid-svg-icons'; import { faEnvelope as farEnvelope } from '@fortawesome/free-regular-svg-icons'; export default defineComponent({ @@ -68,7 +68,7 @@ export default defineComponent({ disableInvitation: false, // icons アイコンを追加したらここをいじる 2/2 - faHeadset, farEnvelope, + faCrown, farEnvelope, }; }, diff --git a/src/client/app/admin/views/instanceblocks.vue b/src/client/app/admin/views/instanceblocks.vue index b12468a5a355..90ba8d3d075c 100644 --- a/src/client/app/admin/views/instanceblocks.vue +++ b/src/client/app/admin/views/instanceblocks.vue @@ -21,6 +21,12 @@ {{ $t('selfSilencedInstances-info') }} +
+
{{ $t('mutedFiles') }}
+ + {{ $t('mutedFiles-info') }} +
+
{{ $t('exposeHome') }} {{ $t('exposeHome-info') }} @@ -45,6 +51,7 @@ export default defineComponent({ blockedInstances: '', silencedInstances: '', selfSilencedInstances: '', + mutedFiles: '', exposeHome: false, }; }, @@ -57,6 +64,7 @@ export default defineComponent({ this.blockedInstances = meta.blockedInstances.join('\n'); this.silencedInstances = meta.silencedInstances.join('\n'); this.selfSilencedInstances = meta.selfSilencedInstances.join('\n'); + this.mutedFiles = meta.mutedFiles.join('\n'); this.exposeHome = meta.exposeHome; }); }, @@ -65,6 +73,7 @@ export default defineComponent({ blockedInstances: this.blockedInstances ? this.blockedInstances.split('\n') : [], silencedInstances: this.silencedInstances ? this.silencedInstances.split('\n') : [], selfSilencedInstances: this.selfSilencedInstances ? this.selfSilencedInstances.split('\n') : [], + mutedFiles: this.mutedFiles ? this.mutedFiles.split('\n') : [], exposeHome: !!this.exposeHome, }).then(() => { this.$root.dialog({ diff --git a/src/client/app/common/views/deck/deck.user-column.vue b/src/client/app/common/views/deck/deck.user-column.vue index e3dfd478e239..56999283ca10 100644 --- a/src/client/app/common/views/deck/deck.user-column.vue +++ b/src/client/app/common/views/deck/deck.user-column.vue @@ -145,7 +145,10 @@ export default Vue.extend({ this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => { this.user = user; this.fetching = false; - }); + }).catch((e: any) => { + this.$root.dialog({ type: 'error', text: e.message || 'Error' }); + this.fetching = false; + });; }, onAvatarClick() { diff --git a/src/client/app/desktop/views/home/user/index.vue b/src/client/app/desktop/views/home/user/index.vue index 88fe99024e47..b10f45b555bf 100644 --- a/src/client/app/desktop/views/home/user/index.vue +++ b/src/client/app/desktop/views/home/user/index.vue @@ -50,7 +50,8 @@ export default Vue.extend({ if (this.$store.state.i || !user.host) this.user = user; this.fetching = false; Progress.done(); - }).catch(() => { + }).catch((e: any) => { + this.$root.dialog({ type: 'error', text: e.message || 'Error' }); this.fetching = false; Progress.done(); }); diff --git a/src/client/app/mobile/views/pages/user/index.vue b/src/client/app/mobile/views/pages/user/index.vue index 446bf51af84c..c6351ce3e302 100644 --- a/src/client/app/mobile/views/pages/user/index.vue +++ b/src/client/app/mobile/views/pages/user/index.vue @@ -155,6 +155,10 @@ export default Vue.extend({ Progress.done(); document.title = `${Vue.filter('userName')(this.user)} | ${this.$root.instanceName}`; + }).catch((e: any) => { + this.$root.dialog({ type: 'error', text: e.message || 'Error' }); + this.fetching = false; + Progress.done(); }); }, diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts index fdfb3bb33539..30587287417b 100644 --- a/src/misc/fetch-meta.ts +++ b/src/misc/fetch-meta.ts @@ -11,6 +11,7 @@ const defaultMeta: any = { blockedInstances: [], silencedInstances: [], selfSilencedInstances: [], + mutedFiles: [], exposeHome: false, stats: { // Object.assignじゃマージされない diff --git a/src/models/meta.ts b/src/models/meta.ts index 5ba49365bbc1..9fe27d313a2a 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -195,6 +195,7 @@ export type IMeta = { blockedInstances?: string[]; silencedInstances?: string[]; selfSilencedInstances?: string[]; + mutedFiles?: string[]; exposeHome?: boolean; mascotImageUrl?: string; bannerUrl?: string; diff --git a/src/server/api/endpoints/admin/meta.ts b/src/server/api/endpoints/admin/meta.ts index d3cfdffca4e9..d700ae0c2cb2 100644 --- a/src/server/api/endpoints/admin/meta.ts +++ b/src/server/api/endpoints/admin/meta.ts @@ -160,6 +160,7 @@ export default define(meta, async (ps, me) => { response.blockedInstances = instance.blockedInstances; response.silencedInstances = instance.silencedInstances; response.selfSilencedInstances = instance.selfSilencedInstances; + response.mutedFiles = instance.mutedFiles; response.exposeHome = instance.exposeHome; response.recaptchaSecretKey = instance.recaptchaSecretKey; response.proxyAccount = instance.proxyAccount; diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 8fd2ded88445..c4d5dff3c60f 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -113,6 +113,13 @@ export const meta = { } }, + mutedFiles: { + validator: $.optional.nullable.arr($.str), + desc: { + 'ja-JP': 'ミュートする添付ファイル' + } + }, + exposeHome: { validator: $.optional.boolean, desc: { @@ -223,7 +230,7 @@ export const meta = { maintainerEmail: { validator: $.optional.nullable.str, desc: { - 'ja-JP': 'インスタンス管理者の連絡先メールアドレス' + 'ja-JP': 'インスタンス管理者の連絡先' } }, @@ -442,6 +449,10 @@ export default define(meta, async (ps) => { set.selfSilencedInstances = ps.selfSilencedInstances.map(x => x.trim()).filter(x => x !== '').map(x => toApHost(x)); } + if (Array.isArray(ps.mutedFiles)) { + set.mutedFiles = ps.mutedFiles.map(x => x.trim()).filter(x => x !== ''); + } + if (typeof ps.exposeHome === 'boolean') { set.exposeHome = ps.exposeHome; } @@ -598,7 +609,7 @@ export default define(meta, async (ps) => { $set: set }, { upsert: true }); - if (set.blockedInstances || set.silencedInstances || set.selfSilencedInstances) { + if (set.blockedInstances || set.silencedInstances || set.selfSilencedInstances || set.mutedFiles) { publishInstanceModUpdated(); } diff --git a/src/services/instance-moderation.ts b/src/services/instance-moderation.ts index a525ac3d42b7..b1d28a53a31b 100644 --- a/src/services/instance-moderation.ts +++ b/src/services/instance-moderation.ts @@ -10,6 +10,7 @@ let silencedHostsRegExp: Set; let selfSilencedHosts: Set; let selfSilencedHostsRegExp: Set; let closedHosts: Set; +let mutedFiles: Set; export async function isBlockedHost(host: string | null) { if (host == null) return false; @@ -44,6 +45,12 @@ export async function isSelfSilencedHost(host: string | null) { return false; } +export async function isMutedFile(md5: string | null | undefined) { + if (md5 == null) return false; + if (!mutedFiles) await Update(); + return mutedFiles?.has(md5); +} + export async function isClosedHost(host: string | null) { if (host == null) return false; if (!closedHosts) await Update(); @@ -111,6 +118,17 @@ async function Update() { selfSilencedHostsRegExp = regExps; } + // mutedFiles from meta + { + const literals = new Set(); + + for (const b of (meta.mutedFiles || [])) { + literals.add(b); + } + + mutedFiles = literals; + } + // closed from instance const closed = await Instance.find({ isMarkedAsClosed: true diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 02d20f1736ef..11e083c5aeb9 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -35,6 +35,9 @@ import { IActivity } from '../../remote/activitypub/type'; import { normalizeTag } from '../../misc/normalize-tag'; import * as ms from 'ms'; import { isSilencedHost } from '../../services/instance-moderation' +import { isMutedFile } from '../instance-moderation'; +import Logger from '../logger'; +export const noteLogger = new Logger('note'); type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'highlight'; @@ -85,6 +88,15 @@ class NotificationManager { return; } + if (this.queue.length === 0) return; + + for (const atc of this.note._files || []) { + if (await isMutedFile(atc.md5)) { + noteLogger.info(`muted file: note=${this.note._id} md5=${atc.md5}`); + return; + } + } + for (const x of this.queue) { // ミュートされてたらスキップ const mute = await getMute(x.target, this.notifier._id); diff --git a/src/tools/_delete-deleted-user-by-hosts.ts b/src/tools/_delete-deleted-user-by-hosts.ts index 6cf366efaf1b..3dd25977a7a5 100644 --- a/src/tools/_delete-deleted-user-by-hosts.ts +++ b/src/tools/_delete-deleted-user-by-hosts.ts @@ -1,4 +1,5 @@ -// 消したリモートユーザーを物理削除する、削除フラグリセット、clean-deleted-user-objsをやった後にやる、使うな +// 消したリモートユーザーを物理削除する by host +import Note from '../models/note'; import User, { IUser } from '../models/user'; async function main(host: string) { @@ -23,6 +24,10 @@ async function main(host: string) { console.log(`user(${prs}/${users.length}): ${user.username}@${user.host}`); + await Note.remove({ + userId: user._id + }); + await User.remove({ _id: u._id }); diff --git a/src/tools/_delete-deleted-user-by-id.ts b/src/tools/_delete-deleted-user-by-id.ts index 4369eafdf484..051ce262cf65 100644 --- a/src/tools/_delete-deleted-user-by-id.ts +++ b/src/tools/_delete-deleted-user-by-id.ts @@ -1,6 +1,7 @@ -// 消したリモートユーザーを物理削除する、削除フラグリセット、clean-deleted-user-objsをやった後にやる、使うな +// 消したリモートユーザーを物理削除する by UserId import * as mongo from 'mongodb'; import User from '../models/user'; +import Note from '../models/note'; async function main(userId: string) { if (!userId) throw 'userId required'; @@ -16,6 +17,10 @@ async function main(userId: string) { console.log('user', user); + await Note.remove({ + userId: user._id + }); + await User.remove({ _id: user._id });