diff --git a/locales/en-US.yml b/locales/en-US.yml index 9e111fa680..a763d96728 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -61,6 +61,9 @@ copyNoteId: "Copy note ID" copyFileId: "Copy file ID" copyFolderId: "Copy folder ID" copyProfileUrl: "Copy profile URL" +trustedLinkUrlPatterns: "Link to external site warning exclusion list" +trustedLinkUrlPatternsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition. Using surrounding keywords with slashes will turn them into a regular expression. If you write only the domain name, it will be a backward match." +open: "Open" searchUser: "Search for a user" searchThisUsersNotes: "Search this user’s notes" reply: "Reply" @@ -143,12 +146,15 @@ markAsSensitive: "Mark as sensitive" unmarkAsSensitive: "Unmark as sensitive" enterFileName: "Enter filename" mute: "Mute" +muted: "Muted" unmute: "Unmute" renoteMute: "Mute Boosts" +renoteMuted: "Boosts muted" renoteUnmute: "Unmute Boosts" block: "Block" unblock: "Unblock" markAsNSFW: "Mark all media from user as NSFW" +markInstanceAsNSFW: "Mark as NSFW" suspend: "Suspend" unsuspend: "Unsuspend" blockConfirm: "Are you sure that you want to block this account?" @@ -223,6 +229,7 @@ stopActivityDelivery: "Stop sending activities" blockThisInstance: "Block this instance" silenceThisInstance: "Silence this instance" mediaSilenceThisInstance: "Silence media from this instance" +rejectReports: "Reject reports from this instance" operations: "Operations" software: "Software" version: "Version" @@ -263,6 +270,9 @@ noCustomEmojis: "There are no emoji" noJobs: "There are no jobs" federating: "Federating" blocked: "Blocked" +blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)." +silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)." +mediaSilencedByBase: "This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s)." suspended: "Suspended" all: "All" subscribing: "Subscribing" @@ -291,7 +301,6 @@ removeAreYouSure: "Are you sure that you want to remove \"{x}\"?" deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?" resetAreYouSure: "Really reset?" areYouSure: "Are you sure?" -confirmRemoteUrl: "Are you sure that you want to go to \"{x}\"?" saved: "Saved" messaging: "Chat" upload: "Upload" @@ -2577,6 +2586,10 @@ _moderationLogTypes: resetPassword: "Password reset" suspendRemoteInstance: "Remote instance suspended" unsuspendRemoteInstance: "Remote instance unsuspended" + setRemoteInstanceNSFW: "Set remote instance as NSFW" + unsetRemoteInstanceNSFW: "Set remote instance as NSFW" + rejectRemoteInstanceReports: "Rejected reports from remote instance" + acceptRemoteInstanceReports: "Accepted reports from remote instance" updateRemoteInstanceNote: "Moderation note updated for remote instance." markSensitiveDriveFile: "File marked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive" @@ -2832,3 +2845,7 @@ _contextMenu: app: "Application" appWithShift: "Application with shift key" native: "Native" +_externalNavigationWarning: + title: "Navigate to an external site" + description: "Leave {host} and go to an external site" + trustThisDomain: "Trust this domain on this device in the future" diff --git a/locales/index.d.ts b/locales/index.d.ts index b3d56d31b3..f77457108e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -260,6 +260,18 @@ export interface Locale extends ILocale { * プロフィールURLをコピー */ "copyProfileUrl": string; + /** + * 外部サイトへのリンク警告 除外リスト + */ + "trustedLinkUrlPatterns": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。ドメイン名だけ書くと後方一致になります。 + */ + "trustedLinkUrlPatternsDescription": string; + /** + * 開く + */ + "open": string; /** * ユーザーを検索 */ @@ -588,6 +600,10 @@ export interface Locale extends ILocale { * ミュート */ "mute": string; + /** + * Muted + */ + "muted": string; /** * ミュート解除 */ @@ -596,6 +612,10 @@ export interface Locale extends ILocale { * ブーストをミュート */ "renoteMute": string; + /** + * Boosts muted + */ + "renoteMuted": string; /** * ブーストのミュートを解除 */ @@ -612,6 +632,10 @@ export interface Locale extends ILocale { * ユーザーのすべてのメディアをNSFWとしてマークする */ "markAsNSFW": string; + /** + * Mark as NSFW + */ + "markInstanceAsNSFW": string; /** * 凍結 */ @@ -908,6 +932,10 @@ export interface Locale extends ILocale { * サーバーをメディアサイレンス */ "mediaSilenceThisInstance": string; + /** + * Reject reports from this instance + */ + "rejectReports": string; /** * 操作 */ @@ -1068,6 +1096,18 @@ export interface Locale extends ILocale { * ブロック中 */ "blocked": string; + /** + * This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s). + */ + "blockedByBase": string; + /** + * This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s). + */ + "silencedByBase": string; + /** + * This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s). + */ + "mediaSilencedByBase": string; /** * 配信停止 */ @@ -1180,10 +1220,6 @@ export interface Locale extends ILocale { * よろしいですか? */ "areYouSure": string; - /** - * 「{x}」を開きますか? - */ - "confirmRemoteUrl": ParameterizedString<"x">; /** * 保存しました */ @@ -9988,6 +10024,22 @@ export interface Locale extends ILocale { * リモートサーバーを再開 */ "unsuspendRemoteInstance": string; + /** + * Set remote instance as NSFW + */ + "setRemoteInstanceNSFW": string; + /** + * Set remote instance as NSFW + */ + "unsetRemoteInstanceNSFW": string; + /** + * Rejected reports from remote instance + */ + "rejectRemoteInstanceReports": string; + /** + * Accepted reports from remote instance + */ + "acceptRemoteInstanceReports": string; /** * リモートサーバーのモデレーションノート更新 */ @@ -10929,6 +10981,20 @@ export interface Locale extends ILocale { */ "native": string; }; + "_externalNavigationWarning": { + /** + * 外部サイトに移動します + */ + "title": string; + /** + * {host}を離れて外部サイトに移動します + */ + "description": ParameterizedString<"host">; + /** + * このデバイスで今後このドメインを信頼する + */ + "trustThisDomain": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b37641cc4f..d688160968 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -61,6 +61,9 @@ copyNoteId: "ノートIDをコピー" copyFileId: "ファイルIDをコピー" copyFolderId: "フォルダーIDをコピー" copyProfileUrl: "プロフィールURLをコピー" +trustedLinkUrlPatterns: "外部サイトへのリンク警告 除外リスト" +trustedLinkUrlPatternsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。ドメイン名だけ書くと後方一致になります。" +open: "開く" searchUser: "ユーザーを検索" searchThisUsersNotes: "ユーザーのノートを検索" reply: "返信" @@ -143,12 +146,15 @@ markAsSensitive: "センシティブとして設定" unmarkAsSensitive: "センシティブを解除する" enterFileName: "ファイル名を入力" mute: "ミュート" +muted: "Muted" unmute: "ミュート解除" renoteMute: "ブーストをミュート" +renoteMuted: "Boosts muted" renoteUnmute: "ブーストのミュートを解除" block: "ブロック" unblock: "ブロック解除" markAsNSFW: "ユーザーのすべてのメディアをNSFWとしてマークする" +markInstanceAsNSFW: "Mark as NSFW" suspend: "凍結" unsuspend: "解凍" blockConfirm: "ブロックしますか?" @@ -223,6 +229,7 @@ stopActivityDelivery: "アクティビティの配送を停止" blockThisInstance: "このサーバーをブロック" silenceThisInstance: "サーバーをサイレンス" mediaSilenceThisInstance: "サーバーをメディアサイレンス" +rejectReports: "Reject reports from this instance" operations: "操作" software: "ソフトウェア" version: "バージョン" @@ -263,6 +270,9 @@ noCustomEmojis: "絵文字はありません" noJobs: "ジョブはありません" federating: "連合中" blocked: "ブロック中" +blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)." +silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)." +mediaSilencedByBase: "This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s)." suspended: "配信停止" all: "全て" subscribing: "購読中" @@ -291,7 +301,6 @@ removeAreYouSure: "「{x}」を削除しますか?" deleteAreYouSure: "「{x}」を削除しますか?" resetAreYouSure: "リセットしますか?" areYouSure: "よろしいですか?" -confirmRemoteUrl: "「{x}」を開きますか?" saved: "保存しました" messaging: "チャット" upload: "アップロード" @@ -2644,6 +2653,10 @@ _moderationLogTypes: resetPassword: "パスワードをリセット" suspendRemoteInstance: "リモートサーバーを停止" unsuspendRemoteInstance: "リモートサーバーを再開" + setRemoteInstanceNSFW: "Set remote instance as NSFW" + unsetRemoteInstanceNSFW: "Set remote instance as NSFW" + rejectRemoteInstanceReports: "Rejected reports from remote instance" + acceptRemoteInstanceReports: "Accepted reports from remote instance" updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新" markSensitiveDriveFile: "ファイルをセンシティブ付与" unmarkSensitiveDriveFile: "ファイルをセンシティブ解除" @@ -2906,3 +2919,8 @@ _contextMenu: app: "アプリケーション" appWithShift: "Shiftキーでアプリケーション" native: "ブラウザのUI" + +_externalNavigationWarning: + title: "外部サイトに移動します" + description: "{host}を離れて外部サイトに移動します" + trustThisDomain: "このデバイスで今後このドメインを信頼する" diff --git a/packages/backend/migration/1711008460816-external-website-warn.js b/packages/backend/migration/1711008460816-external-website-warn.js new file mode 100644 index 0000000000..d36639459b --- /dev/null +++ b/packages/backend/migration/1711008460816-external-website-warn.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ExternalWebsiteWarn1711008460816 { + name = 'ExternalWebsiteWarn1711008460816' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "trustedLinkUrlPatterns" character varying(3072) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "trustedLinkUrlPatterns"`); + } +} \ No newline at end of file diff --git a/packages/backend/migration/1728177700920-add-reject-reports.js b/packages/backend/migration/1728177700920-add-reject-reports.js new file mode 100644 index 0000000000..ed5f6bc559 --- /dev/null +++ b/packages/backend/migration/1728177700920-add-reject-reports.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddRejectReports1728177700920 { + name = 'AddRejectReports1728177700920' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" ADD "rejectReports" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "rejectReports"`); + } +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 6a28cbad15..bce67a458f 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; +import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; @@ -29,6 +30,7 @@ import { bindThis } from '@/decorators.js'; import type { MiRemoteUser } from '@/models/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AbuseReportService } from '@/core/AbuseReportService.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; @@ -83,6 +85,7 @@ export class ApInboxService { private apQuestionService: ApQuestionService, private queueService: QueueService, private globalEventService: GlobalEventService, + private federatedInstanceService: FederatedInstanceService, ) { this.logger = this.apLoggerService.logger; } @@ -530,6 +533,12 @@ export class ApInboxService { @bindThis private async flag(actor: MiRemoteUser, activity: IFlag): Promise { + // Make sure the source instance is allowed to send reports. + const instance = await this.federatedInstanceService.fetch(actor.host); + if (instance.rejectReports) { + throw new Bull.UnrecoverableError(`Rejecting report from instance: ${actor.host}`); + } + // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 55d1054de9..499a163d6c 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -526,6 +526,7 @@ export class ApRendererService { publicKey: this.renderKey(user, keypair, '#main-key'), isCat: user.isCat, noindex: user.noindex, + indexable: !user.noindex, speakAsCat: user.speakAsCat, attachment: attachment.length ? attachment : undefined, }; diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts index 815b20b910..86a665732a 100644 --- a/packages/backend/src/core/activitypub/misc/contexts.ts +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -545,6 +545,7 @@ const extension_context_definition = { Emoji: 'toot:Emoji', featured: 'toot:featured', discoverable: 'toot:discoverable', + indexable: 'toot:indexable', // schema schema: 'http://schema.org#', PropertyValue: 'schema:PropertyValue', diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 7b7a7921fb..382cda301f 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -587,7 +587,7 @@ export class ApNoteService { // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 const createFrom = options.sentFrom?.origin === new URL(uri).origin ? value : uri; - return await this.createNote(createFrom, options.resolver, true); + return await this.createNote(createFrom, options.resolver, false); } finally { unlock(); } diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index afeefc9033..fa4ddc0bd6 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -120,6 +120,7 @@ export class MetaEntityService { imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, })), + trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns, notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts index dd625f95d3..ba93190c57 100644 --- a/packages/backend/src/models/Instance.ts +++ b/packages/backend/src/models/Instance.ts @@ -158,7 +158,12 @@ export class MiInstance { default: false, }) public isNSFW: boolean; - + + @Column('boolean', { + default: false, + }) + public rejectReports: boolean; + @Column('varchar', { length: 16384, default: '', }) diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 29e1dd032a..0e244931d9 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -674,4 +674,12 @@ export class MiMeta { nullable: true, }) public urlPreviewUserAgent: string | null; + + @Column('varchar', { + length: 3072, + array: true, + default: '{}', + comment: 'An array of URL strings or regex that can be used to omit warnings about redirects to external sites. Separate them with spaces to specify AND, and enclose them with slashes to specify regular expressions. Each item is regarded as an OR.', + }) + public trustedLinkUrlPatterns: string[]; } diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 74b6cfe883..8915436b9e 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -273,6 +273,14 @@ export const packedMetaLiteSchema = { optional: false, nullable: false, default: 'local', }, + trustedLinkUrlPatterns: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 8b142910a6..daf19c4435 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -25,6 +25,7 @@ export const paramDef = { host: { type: 'string' }, isSuspended: { type: 'boolean' }, isNSFW: { type: 'boolean' }, + rejectReports: { type: 'boolean' }, moderationNote: { type: 'string' }, }, required: ['host'], @@ -57,6 +58,7 @@ export default class extends Endpoint { // eslint- await this.federatedInstanceService.update(instance.id, { suspensionState, isNSFW: ps.isNSFW, + rejectReports: ps.rejectReports, moderationNote: ps.moderationNote, }); @@ -74,6 +76,22 @@ export default class extends Endpoint { // eslint- } } + if (ps.isNSFW != null && instance.isNSFW !== ps.isNSFW) { + const message = ps.rejectReports ? 'setRemoteInstanceNSFW' : 'unsetRemoteInstanceNSFW'; + this.moderationLogService.log(me, message, { + id: instance.id, + host: instance.host, + }); + } + + if (ps.rejectReports != null && instance.rejectReports !== ps.rejectReports) { + const message = ps.rejectReports ? 'rejectRemoteInstanceReports' : 'acceptRemoteInstanceReports'; + this.moderationLogService.log(me, message, { + id: instance.id, + host: instance.host, + }); + } + if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) { this.moderationLogService.log(me, 'updateRemoteInstanceNote', { id: instance.id, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5a69fbf679..29c165f87a 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -128,7 +128,7 @@ export const meta = { }, silencedHosts: { type: 'array', - optional: true, + optional: false, nullable: false, items: { type: 'string', @@ -526,6 +526,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + trustedLinkUrlPatterns: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, }, }, } as const; @@ -669,6 +677,7 @@ export default class extends Endpoint { // eslint- urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, urlPreviewUserAgent: instance.urlPreviewUserAgent, urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, + trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns, }; }); } 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 c56dd053d3..cbde554428 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -176,6 +176,11 @@ export const paramDef = { urlPreviewRequireContentLength: { type: 'boolean' }, urlPreviewUserAgent: { type: 'string', nullable: true }, urlPreviewSummaryProxyUrl: { type: 'string', nullable: true }, + trustedLinkUrlPatterns: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, }, required: [], } as const; @@ -665,6 +670,10 @@ export default class extends Endpoint { // eslint- set.urlPreviewSummaryProxyUrl = value === '' ? null : value; } + if (Array.isArray(ps.trustedLinkUrlPatterns)) { + set.trustedLinkUrlPatterns = ps.trustedLinkUrlPatterns.filter(Boolean); + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 0af90a844b..e20707977f 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -193,9 +193,9 @@ export class ClientServerService { icon: meta.iconUrl, appleTouchIcon: meta.app512IconUrl, themeColor: meta.themeColor, - serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://launcher.moe/error.png', - infoImageUrl: meta.infoImageUrl ?? 'https://launcher.moe/nothinghere.png', - notFoundImageUrl: meta.notFoundImageUrl ?? 'https://launcher.moe/missingpage.webp', + serverErrorImageUrl: meta.serverErrorImageUrl ?? '/status/error.png', + infoImageUrl: meta.infoImageUrl ?? '/status/nothinghere.png', + notFoundImageUrl: meta.notFoundImageUrl ?? '/status/missingpage.webp', instanceUrl: this.config.url, randomMOTD: this.config.customMOTD ? this.config.customMOTD[Math.floor(Math.random() * this.config.customMOTD.length)] : undefined, metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)), diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index d83d414096..d64d72c07f 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -77,8 +77,12 @@ export const moderationLogTypes = [ 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'resetPassword', + 'setRemoteInstanceNSFW', + 'unsetRemoteInstanceNSFW', 'suspendRemoteInstance', 'unsuspendRemoteInstance', + 'rejectRemoteInstanceReports', + 'acceptRemoteInstanceReports', 'updateRemoteInstanceNote', 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', @@ -227,6 +231,14 @@ export type ModerationLogPayloads = { userUsername: string; userHost: string | null; }; + setRemoteInstanceNSFW: { + id: string; + host: string; + }; + unsetRemoteInstanceNSFW: { + id: string; + host: string; + }; suspendRemoteInstance: { id: string; host: string; @@ -235,6 +247,14 @@ export type ModerationLogPayloads = { id: string; host: string; }; + rejectRemoteInstanceReports: { + id: string; + host: string; + }; + acceptRemoteInstanceReports: { + id: string; + host: string; + }; updateRemoteInstanceNote: { id: string; host: string; diff --git a/packages/frontend/assets/status/error.png b/packages/frontend/assets/status/error.png new file mode 100644 index 0000000000..9f21236c39 Binary files /dev/null and b/packages/frontend/assets/status/error.png differ diff --git a/packages/frontend/assets/status/missingpage.webp b/packages/frontend/assets/status/missingpage.webp new file mode 100644 index 0000000000..3ac83b3110 Binary files /dev/null and b/packages/frontend/assets/status/missingpage.webp differ diff --git a/packages/frontend/assets/status/nothinghere.png b/packages/frontend/assets/status/nothinghere.png new file mode 100644 index 0000000000..5ebe210acd Binary files /dev/null and b/packages/frontend/assets/status/nothinghere.png differ diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index c10930a038..5ff998fac4 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -216,19 +216,25 @@ export async function mainBoot() { claimAchievement('collectAchievements30'); } - window.setInterval(() => { - if (Math.floor(Math.random() * 20000) === 0) { - claimAchievement('justPlainLucky'); - } - }, 1000 * 10); + if (!claimedAchievements.includes('justPlainLucky')) { + window.setInterval(() => { + if (Math.floor(Math.random() * 20000) === 0) { + claimAchievement('justPlainLucky'); + } + }, 1000 * 10); + } - window.setTimeout(() => { - claimAchievement('client30min'); - }, 1000 * 60 * 30); + if (!claimedAchievements.includes('client30min')) { + window.setTimeout(() => { + claimAchievement('client30min'); + }, 1000 * 60 * 30); + } - window.setTimeout(() => { - claimAchievement('client60min'); - }, 1000 * 60 * 60); + if (!claimedAchievements.includes('client60min')) { + window.setTimeout(() => { + claimAchievement('client60min'); + }, 1000 * 60 * 60); + } // 邪魔 //const lastUsed = miLocalStorage.getItem('lastUsed'); diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index d2819f9f4c..b04edd1150 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only :is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target" :behavior="props.navigationBehavior" :title="url" - @click.prevent="self ? true : promptConfirm()" + @click.prevent="self ? true : warningExternalWebsite(url)" @click.stop > @@ -23,7 +23,7 @@ import { useTooltip } from '@/scripts/use-tooltip.js'; import * as os from '@/os.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { MkABehavior } from '@/components/global/MkA.vue'; -import { i18n } from '@/i18n.js'; +import { warningExternalWebsite } from '@/scripts/warning-external-website.js'; const props = withDefaults(defineProps<{ url: string; @@ -49,16 +49,6 @@ if (isEnabledUrlPreview.value) { }); }); } - -async function promptConfirm() { - const { canceled } = await os.confirm({ - type: 'question', - text: i18n.tsx.confirmRemoteUrl({ x: props.url }), - plain: true, - }); - if (canceled) return; - window.open(props.url, '_blank', 'nofollow noopener popup=false'); -} diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index b154f7a5b3..ff2e27aaf8 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -142,6 +142,7 @@ function showMenu(ev: MouseEvent) { height: 32px; border-radius: var(--radius-sm); font-size: 18px; + z-index: 50; } .mainFg { diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index 15595ba515..1dec8ad28c 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only :is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target" :behavior="props.navigationBehavior" @contextmenu.stop="() => {}" + @click.prevent="self ? true : warningExternalWebsite(props.url)" @click.stop >