fix: URLプレビューの動作改善+動作設定を可能にする (#13579)
* wip * support new version * URLプレビュー無効化時、フロント側も非表示にしてリクエストをしないようにする * fix lint * fix lint * tweak preview request error handles * fix: CHANGELOG.md * fix * fix --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									f4838e50b4
								
							
						
					
					
						commit
						831c74a25b
					
				
					 22 changed files with 420 additions and 66 deletions
				
			
		| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
## Unreleased
 | 
			
		||||
 | 
			
		||||
### Note
 | 
			
		||||
- コントロールパネル内にあるサマリープロキシの設定個所がセキュリティから全般へ変更となります。
 | 
			
		||||
 | 
			
		||||
### General
 | 
			
		||||
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
 | 
			
		||||
- Enhance: アンテナでBotによるノートを除外できるように  
 | 
			
		||||
  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
 | 
			
		||||
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +29,7 @@
 | 
			
		|||
 | 
			
		||||
### Server
 | 
			
		||||
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
 | 
			
		||||
- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化)
 | 
			
		||||
- Fix: フォローリクエストを作成する際に既存のものは削除するように  
 | 
			
		||||
  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										58
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -4916,6 +4916,10 @@ export interface Locale extends ILocale {
 | 
			
		|||
     * リトライ
 | 
			
		||||
     */
 | 
			
		||||
    "gameRetry": string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 使用しない場合は空欄にしてください
 | 
			
		||||
     */
 | 
			
		||||
    "notUsePleaseLeaveBlank": string;
 | 
			
		||||
    "_bubbleGame": {
 | 
			
		||||
        /**
 | 
			
		||||
         * 遊び方
 | 
			
		||||
| 
						 | 
				
			
			@ -9768,6 +9772,60 @@ export interface Locale extends ILocale {
 | 
			
		|||
         */
 | 
			
		||||
        "header": string;
 | 
			
		||||
    };
 | 
			
		||||
    "_urlPreviewSetting": {
 | 
			
		||||
        /**
 | 
			
		||||
         * URLプレビューの設定
 | 
			
		||||
         */
 | 
			
		||||
        "title": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * URLプレビューを有効にする
 | 
			
		||||
         */
 | 
			
		||||
        "enable": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * プレビュー取得時のタイムアウト(ms)
 | 
			
		||||
         */
 | 
			
		||||
        "timeout": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。
 | 
			
		||||
         */
 | 
			
		||||
        "timeoutDescription": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * Content-Lengthの最大値(byte)
 | 
			
		||||
         */
 | 
			
		||||
        "maximumContentLength": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * Content-Lengthがこの値を超えた場合、プレビューは生成されません。
 | 
			
		||||
         */
 | 
			
		||||
        "maximumContentLengthDescription": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * Content-Lengthが取得できた場合のみプレビューを生成
 | 
			
		||||
         */
 | 
			
		||||
        "requireContentLength": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * 相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。
 | 
			
		||||
         */
 | 
			
		||||
        "requireContentLengthDescription": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * User-Agent
 | 
			
		||||
         */
 | 
			
		||||
        "userAgent": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。
 | 
			
		||||
         */
 | 
			
		||||
        "userAgentDescription": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * プレビューを生成するプロキシのエンドポイント
 | 
			
		||||
         */
 | 
			
		||||
        "summaryProxy": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。
 | 
			
		||||
         */
 | 
			
		||||
        "summaryProxyDescription": string;
 | 
			
		||||
        /**
 | 
			
		||||
         * プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。
 | 
			
		||||
         */
 | 
			
		||||
        "summaryProxyDescription2": string;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
declare const locales: {
 | 
			
		||||
    [lang: string]: Locale;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1225,6 +1225,7 @@ enableHorizontalSwipe: "スワイプしてタブを切り替える"
 | 
			
		|||
loading: "読み込み中"
 | 
			
		||||
surrender: "やめる"
 | 
			
		||||
gameRetry: "リトライ"
 | 
			
		||||
notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください"
 | 
			
		||||
 | 
			
		||||
_bubbleGame:
 | 
			
		||||
  howToPlay: "遊び方"
 | 
			
		||||
| 
						 | 
				
			
			@ -2602,3 +2603,17 @@ _offlineScreen:
 | 
			
		|||
  title: "オフライン - サーバーに接続できません"
 | 
			
		||||
  header: "サーバーに接続できません"
 | 
			
		||||
 | 
			
		||||
_urlPreviewSetting:
 | 
			
		||||
  title: "URLプレビューの設定"
 | 
			
		||||
  enable: "URLプレビューを有効にする"
 | 
			
		||||
  timeout: "プレビュー取得時のタイムアウト(ms)"
 | 
			
		||||
  timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。"
 | 
			
		||||
  maximumContentLength: "Content-Lengthの最大値(byte)"
 | 
			
		||||
  maximumContentLengthDescription: "Content-Lengthがこの値を超えた場合、プレビューは生成されません。"
 | 
			
		||||
  requireContentLength: "Content-Lengthが取得できた場合のみプレビューを生成"
 | 
			
		||||
  requireContentLengthDescription: "相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。"
 | 
			
		||||
  userAgent: "User-Agent"
 | 
			
		||||
  userAgentDescription: "プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。"
 | 
			
		||||
  summaryProxy: "プレビューを生成するプロキシのエンドポイント"
 | 
			
		||||
  summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
 | 
			
		||||
  summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								packages/backend/migration/1710512074000-url-preview-meta.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/backend/migration/1710512074000-url-preview-meta.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
/*
 | 
			
		||||
 * SPDX-FileCopyrightText: syuilo and misskey-project
 | 
			
		||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export class UrlPreviewMeta1710512074000 {
 | 
			
		||||
    name = 'UrlPreviewMeta1710512074000'
 | 
			
		||||
 | 
			
		||||
    async up(queryRunner) {
 | 
			
		||||
        await queryRunner.query(`
 | 
			
		||||
					alter table meta
 | 
			
		||||
						rename column "summalyProxy" to "urlPreviewSummaryProxyUrl";
 | 
			
		||||
					alter table meta
 | 
			
		||||
						add "urlPreviewEnabled" boolean default true not null;
 | 
			
		||||
					alter table meta
 | 
			
		||||
						add "urlPreviewTimeout" integer default 10000 not null;
 | 
			
		||||
					alter table meta
 | 
			
		||||
						add "urlPreviewMaximumContentLength" bigint default 10485760 not null;
 | 
			
		||||
					alter table meta
 | 
			
		||||
						add "urlPreviewRequireContentLength" boolean default false not null;
 | 
			
		||||
					alter table meta
 | 
			
		||||
						add "urlPreviewUserAgent" varchar(1024) default null;
 | 
			
		||||
				`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async down(queryRunner) {
 | 
			
		||||
        await queryRunner.query(`
 | 
			
		||||
					alter table meta
 | 
			
		||||
						rename column "urlPreviewSummaryProxyUrl" to "summalyProxy";
 | 
			
		||||
					alter table meta
 | 
			
		||||
						drop column "urlPreviewEnabled";
 | 
			
		||||
					alter table meta
 | 
			
		||||
						drop column "urlPreviewTimeout";
 | 
			
		||||
					alter table meta
 | 
			
		||||
						drop column "urlPreviewMaximumContentLength";
 | 
			
		||||
					alter table meta
 | 
			
		||||
						drop column "urlPreviewRequireContentLength";
 | 
			
		||||
					alter table meta
 | 
			
		||||
						drop column "urlPreviewUserAgent";
 | 
			
		||||
				`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +80,7 @@
 | 
			
		|||
		"@fastify/static": "6.12.0",
 | 
			
		||||
		"@fastify/view": "8.2.0",
 | 
			
		||||
		"@misskey-dev/sharp-read-bmp": "1.2.0",
 | 
			
		||||
		"@misskey-dev/summaly": "5.0.3",
 | 
			
		||||
		"@misskey-dev/summaly": "5.1.0",
 | 
			
		||||
		"@nestjs/common": "10.3.3",
 | 
			
		||||
		"@nestjs/core": "10.3.3",
 | 
			
		||||
		"@nestjs/testing": "10.3.3",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,6 +111,7 @@ export class MetaEntityService {
 | 
			
		|||
			policies: { ...DEFAULT_POLICIES, ...instance.policies },
 | 
			
		||||
 | 
			
		||||
			mediaProxy: this.config.mediaProxy,
 | 
			
		||||
			enableUrlPreview: instance.urlPreviewEnabled,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		return packed;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -277,12 +277,6 @@ export class MiMeta {
 | 
			
		|||
	})
 | 
			
		||||
	public enableSensitiveMediaDetectionForVideos: boolean;
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024,
 | 
			
		||||
		nullable: true,
 | 
			
		||||
	})
 | 
			
		||||
	public summalyProxy: string | null;
 | 
			
		||||
 | 
			
		||||
	@Column('boolean', {
 | 
			
		||||
		default: false,
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			@ -588,4 +582,36 @@ export class MiMeta {
 | 
			
		|||
		default: 0,
 | 
			
		||||
	})
 | 
			
		||||
	public notesPerOneAd: number;
 | 
			
		||||
 | 
			
		||||
	@Column('boolean', {
 | 
			
		||||
		default: true,
 | 
			
		||||
	})
 | 
			
		||||
	public urlPreviewEnabled: boolean;
 | 
			
		||||
 | 
			
		||||
	@Column('integer', {
 | 
			
		||||
		default: 10000,
 | 
			
		||||
	})
 | 
			
		||||
	public urlPreviewTimeout: number;
 | 
			
		||||
 | 
			
		||||
	@Column('bigint', {
 | 
			
		||||
		default: 1024 * 1024 * 10,
 | 
			
		||||
	})
 | 
			
		||||
	public urlPreviewMaximumContentLength: number;
 | 
			
		||||
 | 
			
		||||
	@Column('boolean', {
 | 
			
		||||
		default: true,
 | 
			
		||||
	})
 | 
			
		||||
	public urlPreviewRequireContentLength: boolean;
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024,
 | 
			
		||||
		nullable: true,
 | 
			
		||||
	})
 | 
			
		||||
	public urlPreviewSummaryProxyUrl: string | null;
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024,
 | 
			
		||||
		nullable: true,
 | 
			
		||||
	})
 | 
			
		||||
	public urlPreviewUserAgent: string | null;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -207,6 +207,10 @@ export const packedMetaLiteSchema = {
 | 
			
		|||
			type: 'string',
 | 
			
		||||
			optional: false, nullable: false,
 | 
			
		||||
		},
 | 
			
		||||
		enableUrlPreview: {
 | 
			
		||||
			type: 'boolean',
 | 
			
		||||
			optional: false, nullable: false,
 | 
			
		||||
		},
 | 
			
		||||
		backgroundImageUrl: {
 | 
			
		||||
			type: 'string',
 | 
			
		||||
			optional: false, nullable: true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -434,6 +434,8 @@ export const meta = {
 | 
			
		|||
			summalyProxy: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				optional: false, nullable: true,
 | 
			
		||||
				deprecated: true,
 | 
			
		||||
				description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
 | 
			
		||||
			},
 | 
			
		||||
			themeColor: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
| 
						 | 
				
			
			@ -451,6 +453,30 @@ export const meta = {
 | 
			
		|||
				type: 'string',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
			},
 | 
			
		||||
			urlPreviewEnabled: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
			},
 | 
			
		||||
			urlPreviewTimeout: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
			},
 | 
			
		||||
			urlPreviewMaximumContentLength: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
			},
 | 
			
		||||
			urlPreviewRequireContentLength: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
			},
 | 
			
		||||
			urlPreviewUserAgent: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				optional: false, nullable: true,
 | 
			
		||||
			},
 | 
			
		||||
			urlPreviewSummaryProxyUrl: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				optional: false, nullable: true,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
} as const;
 | 
			
		||||
| 
						 | 
				
			
			@ -533,7 +559,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
 | 
			
		||||
				enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
 | 
			
		||||
				proxyAccountId: instance.proxyAccountId,
 | 
			
		||||
				summalyProxy: instance.summalyProxy,
 | 
			
		||||
				email: instance.email,
 | 
			
		||||
				smtpSecure: instance.smtpSecure,
 | 
			
		||||
				smtpHost: instance.smtpHost,
 | 
			
		||||
| 
						 | 
				
			
			@ -577,6 +602,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
 | 
			
		||||
				perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
 | 
			
		||||
				notesPerOneAd: instance.notesPerOneAd,
 | 
			
		||||
				summalyProxy: instance.urlPreviewSummaryProxyUrl,
 | 
			
		||||
				urlPreviewEnabled: instance.urlPreviewEnabled,
 | 
			
		||||
				urlPreviewTimeout: instance.urlPreviewTimeout,
 | 
			
		||||
				urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength,
 | 
			
		||||
				urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
 | 
			
		||||
				urlPreviewUserAgent: instance.urlPreviewUserAgent,
 | 
			
		||||
				urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
 | 
			
		||||
			};
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,7 +90,6 @@ export const paramDef = {
 | 
			
		|||
				type: 'string',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		summalyProxy: { type: 'string', nullable: true },
 | 
			
		||||
		deeplAuthKey: { type: 'string', nullable: true },
 | 
			
		||||
		deeplIsPro: { type: 'boolean' },
 | 
			
		||||
		enableEmail: { type: 'boolean' },
 | 
			
		||||
| 
						 | 
				
			
			@ -150,6 +149,16 @@ export const paramDef = {
 | 
			
		|||
				type: 'string',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		summalyProxy: {
 | 
			
		||||
			type: 'string', nullable: true,
 | 
			
		||||
			description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
 | 
			
		||||
		},
 | 
			
		||||
		urlPreviewEnabled: { type: 'boolean' },
 | 
			
		||||
		urlPreviewTimeout: { type: 'integer' },
 | 
			
		||||
		urlPreviewMaximumContentLength: { type: 'integer' },
 | 
			
		||||
		urlPreviewRequireContentLength: { type: 'boolean' },
 | 
			
		||||
		urlPreviewUserAgent: { type: 'string', nullable: true },
 | 
			
		||||
		urlPreviewSummaryProxyUrl: { type: 'string', nullable: true },
 | 
			
		||||
	},
 | 
			
		||||
	required: [],
 | 
			
		||||
} as const;
 | 
			
		||||
| 
						 | 
				
			
			@ -353,10 +362,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				set.langs = ps.langs.filter(Boolean);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.summalyProxy !== undefined) {
 | 
			
		||||
				set.summalyProxy = ps.summalyProxy;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.enableEmail !== undefined) {
 | 
			
		||||
				set.enableEmail = ps.enableEmail;
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -581,6 +586,32 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				set.bannedEmailDomains = ps.bannedEmailDomains;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.urlPreviewEnabled !== undefined) {
 | 
			
		||||
				set.urlPreviewEnabled = ps.urlPreviewEnabled;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.urlPreviewTimeout !== undefined) {
 | 
			
		||||
				set.urlPreviewTimeout = ps.urlPreviewTimeout;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.urlPreviewMaximumContentLength !== undefined) {
 | 
			
		||||
				set.urlPreviewMaximumContentLength = ps.urlPreviewMaximumContentLength;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.urlPreviewRequireContentLength !== undefined) {
 | 
			
		||||
				set.urlPreviewRequireContentLength = ps.urlPreviewRequireContentLength;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.urlPreviewUserAgent !== undefined) {
 | 
			
		||||
				const value = (ps.urlPreviewUserAgent ?? '').trim();
 | 
			
		||||
				set.urlPreviewUserAgent = value === '' ? null : ps.urlPreviewUserAgent;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.summalyProxy !== undefined || ps.urlPreviewSummaryProxyUrl !== undefined) {
 | 
			
		||||
				const value = ((ps.urlPreviewSummaryProxyUrl ?? ps.summalyProxy) ?? '').trim();
 | 
			
		||||
				set.urlPreviewSummaryProxyUrl = value === '' ? null : value;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const before = await this.metaService.fetch(true);
 | 
			
		||||
 | 
			
		||||
			await this.metaService.update(set);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
 | 
			
		||||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import { summaly } from '@misskey-dev/summaly';
 | 
			
		||||
import { SummalyResult } from '@misskey-dev/summaly/built/summary.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import type { Config } from '@/config.js';
 | 
			
		||||
import { MetaService } from '@/core/MetaService.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +15,7 @@ import { query } from '@/misc/prelude/url.js';
 | 
			
		|||
import { LoggerService } from '@/core/LoggerService.js';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import { ApiError } from '@/server/api/error.js';
 | 
			
		||||
import { MiMeta } from '@/models/Meta.js';
 | 
			
		||||
import type { FastifyRequest, FastifyReply } from 'fastify';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
| 
						 | 
				
			
			@ -62,24 +64,25 @@ export class UrlPreviewService {
 | 
			
		|||
 | 
			
		||||
		const meta = await this.metaService.fetch();
 | 
			
		||||
 | 
			
		||||
		this.logger.info(meta.summalyProxy
 | 
			
		||||
		if (!meta.urlPreviewEnabled) {
 | 
			
		||||
			reply.code(403);
 | 
			
		||||
			return {
 | 
			
		||||
				error: new ApiError({
 | 
			
		||||
					message: 'URL preview is disabled',
 | 
			
		||||
					code: 'URL_PREVIEW_DISABLED',
 | 
			
		||||
					id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8',
 | 
			
		||||
				}),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.logger.info(meta.urlPreviewSummaryProxyUrl
 | 
			
		||||
			? `(Proxy) Getting preview of ${url}@${lang} ...`
 | 
			
		||||
			: `Getting preview of ${url}@${lang} ...`);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			const summary = meta.summalyProxy ?
 | 
			
		||||
				await this.httpRequestService.getJson<ReturnType<typeof summaly>>(`${meta.summalyProxy}?${query({
 | 
			
		||||
					url: url,
 | 
			
		||||
					lang: lang ?? 'ja-JP',
 | 
			
		||||
				})}`)
 | 
			
		||||
				:
 | 
			
		||||
				await summaly(url, {
 | 
			
		||||
					followRedirects: false,
 | 
			
		||||
					lang: lang ?? 'ja-JP',
 | 
			
		||||
					agent: this.config.proxy ? {
 | 
			
		||||
						http: this.httpRequestService.httpAgent,
 | 
			
		||||
						https: this.httpRequestService.httpsAgent,
 | 
			
		||||
					} : undefined,
 | 
			
		||||
				});
 | 
			
		||||
			const summary = meta.urlPreviewSummaryProxyUrl
 | 
			
		||||
				? await this.fetchSummaryFromProxy(url, meta, lang)
 | 
			
		||||
				: await this.fetchSummary(url, meta, lang);
 | 
			
		||||
 | 
			
		||||
			this.logger.succ(`Got preview of ${url}: ${summary.title}`);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +103,7 @@ export class UrlPreviewService {
 | 
			
		|||
			return summary;
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			this.logger.warn(`Failed to get preview of ${url}: ${err}`);
 | 
			
		||||
 | 
			
		||||
			reply.code(422);
 | 
			
		||||
			reply.header('Cache-Control', 'max-age=86400, immutable');
 | 
			
		||||
			return {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,4 +115,37 @@ export class UrlPreviewService {
 | 
			
		|||
			};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private fetchSummary(url: string, meta: MiMeta, lang?: string): Promise<SummalyResult> {
 | 
			
		||||
		const agent = this.config.proxy
 | 
			
		||||
			? {
 | 
			
		||||
				http: this.httpRequestService.httpAgent,
 | 
			
		||||
				https: this.httpRequestService.httpsAgent,
 | 
			
		||||
			}
 | 
			
		||||
			: undefined;
 | 
			
		||||
 | 
			
		||||
		return summaly(url, {
 | 
			
		||||
			followRedirects: false,
 | 
			
		||||
			lang: lang ?? 'ja-JP',
 | 
			
		||||
			agent: agent,
 | 
			
		||||
			userAgent: meta.urlPreviewUserAgent ?? undefined,
 | 
			
		||||
			operationTimeout: meta.urlPreviewTimeout,
 | 
			
		||||
			contentLengthLimit: meta.urlPreviewMaximumContentLength,
 | 
			
		||||
			contentLengthRequired: meta.urlPreviewRequireContentLength,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private fetchSummaryFromProxy(url: string, meta: MiMeta, lang?: string): Promise<SummalyResult> {
 | 
			
		||||
		const proxy = meta.urlPreviewSummaryProxyUrl!;
 | 
			
		||||
		const queryStr = query({
 | 
			
		||||
			url: url,
 | 
			
		||||
			lang: lang ?? 'ja-JP',
 | 
			
		||||
			userAgent: meta.urlPreviewUserAgent ?? undefined,
 | 
			
		||||
			operationTimeout: meta.urlPreviewTimeout,
 | 
			
		||||
			contentLengthLimit: meta.urlPreviewMaximumContentLength,
 | 
			
		||||
			contentLengthRequired: meta.urlPreviewRequireContentLength,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return this.httpRequestService.getJson<SummalyResult>(`${proxy}?${queryStr}`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ import { defineAsyncComponent, ref } from 'vue';
 | 
			
		|||
import { url as local } from '@/config.js';
 | 
			
		||||
import { useTooltip } from '@/scripts/use-tooltip.js';
 | 
			
		||||
import * as os from '@/os.js';
 | 
			
		||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	url: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -31,13 +32,15 @@ const target = self ? null : '_blank';
 | 
			
		|||
 | 
			
		||||
const el = ref<HTMLElement | { $el: HTMLElement }>();
 | 
			
		||||
 | 
			
		||||
useTooltip(el, (showing) => {
 | 
			
		||||
	os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
 | 
			
		||||
		showing,
 | 
			
		||||
		url: props.url,
 | 
			
		||||
		source: el.value instanceof HTMLElement ? el.value : el.value?.$el,
 | 
			
		||||
	}, {}, 'closed');
 | 
			
		||||
});
 | 
			
		||||
if (isEnabledUrlPreview.value) {
 | 
			
		||||
	useTooltip(el, (showing) => {
 | 
			
		||||
		os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
 | 
			
		||||
			showing,
 | 
			
		||||
			url: props.url,
 | 
			
		||||
			source: el.value instanceof HTMLElement ? el.value : el.value?.$el,
 | 
			
		||||
		}, {}, 'closed');
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,7 +82,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
						<MkMediaList :mediaList="appearNote.files"/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
 | 
			
		||||
					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
 | 
			
		||||
					<div v-if="isEnabledUrlPreview">
 | 
			
		||||
						<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
 | 
			
		||||
					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
 | 
			
		||||
						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +196,7 @@ import { MenuItem } from '@/types/menu.js';
 | 
			
		|||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
 | 
			
		||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 | 
			
		||||
import { shouldCollapsed } from '@/scripts/collapsed.js';
 | 
			
		||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	note: Misskey.entities.Note;
 | 
			
		||||
| 
						 | 
				
			
			@ -268,7 +271,7 @@ const renoteCollapsed = ref(
 | 
			
		|||
	defaultStore.state.collapseRenotes && isRenote && (
 | 
			
		||||
		($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
 | 
			
		||||
		(appearNote.value.myReaction != null)
 | 
			
		||||
	)
 | 
			
		||||
	),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/* Overload FunctionにLintが対応していないのでコメントアウト
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
					<MkMediaList :mediaList="appearNote.files"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
 | 
			
		||||
				<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
 | 
			
		||||
				<div v-if="isEnabledUrlPreview">
 | 
			
		||||
					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 | 
			
		||||
| 
						 | 
				
			
			@ -229,6 +231,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
 | 
			
		|||
import MkPagination, { type Paging } from '@/components/MkPagination.vue';
 | 
			
		||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	note: Misskey.entities.Note;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,7 +110,6 @@ const MOBILE_THRESHOLD = 500;
 | 
			
		|||
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
 | 
			
		||||
 | 
			
		||||
const self = props.url.startsWith(local);
 | 
			
		||||
const attr = self ? 'to' : 'href';
 | 
			
		||||
const target = self ? null : '_blank';
 | 
			
		||||
const fetching = ref(true);
 | 
			
		||||
const title = ref<string | null>(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -152,15 +151,16 @@ requestUrl.hash = '';
 | 
			
		|||
window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`)
 | 
			
		||||
	.then(res => {
 | 
			
		||||
		if (!res.ok) {
 | 
			
		||||
			fetching.value = false;
 | 
			
		||||
			unknownUrl.value = true;
 | 
			
		||||
			return;
 | 
			
		||||
			if (_DEV_) {
 | 
			
		||||
				console.warn(`[HTTP${res.status}] Failed to fetch url preview`);
 | 
			
		||||
			}
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res.json();
 | 
			
		||||
	})
 | 
			
		||||
	.then((info: SummalyResult) => {
 | 
			
		||||
		if (info.url == null) {
 | 
			
		||||
	.then((info: SummalyResult | null) => {
 | 
			
		||||
		if (!info || info.url == null) {
 | 
			
		||||
			fetching.value = false;
 | 
			
		||||
			unknownUrl.value = true;
 | 
			
		||||
			return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@ import { url as local } from '@/config.js';
 | 
			
		|||
import * as os from '@/os.js';
 | 
			
		||||
import { useTooltip } from '@/scripts/use-tooltip.js';
 | 
			
		||||
import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
 | 
			
		||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	url: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +45,7 @@ const url = new URL(props.url);
 | 
			
		|||
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
 | 
			
		||||
const el = ref();
 | 
			
		||||
 | 
			
		||||
if (props.showUrlPreview) {
 | 
			
		||||
if (props.showUrlPreview && isEnabledUrlPreview.value) {
 | 
			
		||||
	useTooltip(el, (showing) => {
 | 
			
		||||
		os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
 | 
			
		||||
			showing,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
<template>
 | 
			
		||||
<div class="_gaps" :class="$style.textRoot">
 | 
			
		||||
	<Mfm :text="block.text ?? ''" :isNote="false"/>
 | 
			
		||||
	<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 | 
			
		||||
	<div v-if="isEnabledUrlPreview">
 | 
			
		||||
		<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +17,7 @@ import { defineAsyncComponent } from 'vue';
 | 
			
		|||
import * as mfm from 'mfm-js';
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
 | 
			
		||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
			
		||||
 | 
			
		||||
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,8 @@ export const infoImageUrl = computed(() => instance.infoImageUrl ?? DEFAULT_INFO
 | 
			
		|||
 | 
			
		||||
export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
 | 
			
		||||
 | 
			
		||||
export const isEnabledUrlPreview = computed(() => instance.enableUrlPreview ?? true);
 | 
			
		||||
 | 
			
		||||
export async function fetchInstance(force = false): Promise<void> {
 | 
			
		||||
	if (!force) {
 | 
			
		||||
		const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -118,19 +118,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
						</MkSwitch>
 | 
			
		||||
					</div>
 | 
			
		||||
				</MkFolder>
 | 
			
		||||
 | 
			
		||||
				<MkFolder>
 | 
			
		||||
					<template #label>Summaly Proxy</template>
 | 
			
		||||
 | 
			
		||||
					<div class="_gaps_m">
 | 
			
		||||
						<MkInput v-model="summalyProxy">
 | 
			
		||||
							<template #prefix><i class="ti ti-link"></i></template>
 | 
			
		||||
							<template #label>Summaly Proxy URL</template>
 | 
			
		||||
						</MkInput>
 | 
			
		||||
 | 
			
		||||
						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 | 
			
		||||
					</div>
 | 
			
		||||
				</MkFolder>
 | 
			
		||||
			</div>
 | 
			
		||||
		</FormSuspense>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +142,6 @@ import { fetchInstance } from '@/instance.js';
 | 
			
		|||
import { i18n } from '@/i18n.js';
 | 
			
		||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
 | 
			
		||||
 | 
			
		||||
const summalyProxy = ref<string>('');
 | 
			
		||||
const enableHcaptcha = ref<boolean>(false);
 | 
			
		||||
const enableMcaptcha = ref<boolean>(false);
 | 
			
		||||
const enableRecaptcha = ref<boolean>(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +161,6 @@ const bannedEmailDomains = ref<string>('');
 | 
			
		|||
 | 
			
		||||
async function init() {
 | 
			
		||||
	const meta = await misskeyApi('admin/meta');
 | 
			
		||||
	summalyProxy.value = meta.summalyProxy;
 | 
			
		||||
	enableHcaptcha.value = meta.enableHcaptcha;
 | 
			
		||||
	enableMcaptcha.value = meta.enableMcaptcha;
 | 
			
		||||
	enableRecaptcha.value = meta.enableRecaptcha;
 | 
			
		||||
| 
						 | 
				
			
			@ -201,7 +186,6 @@ async function init() {
 | 
			
		|||
 | 
			
		||||
function save() {
 | 
			
		||||
	os.apiWithDialog('admin/update-meta', {
 | 
			
		||||
		summalyProxy: summalyProxy.value,
 | 
			
		||||
		sensitiveMediaDetection: sensitiveMediaDetection.value,
 | 
			
		||||
		sensitiveMediaDetectionSensitivity:
 | 
			
		||||
			sensitiveMediaDetectionSensitivity.value === 0 ? 'veryLow' :
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,6 +143,53 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</FormSection>
 | 
			
		||||
 | 
			
		||||
					<FormSection>
 | 
			
		||||
						<template #label>{{ i18n.ts._urlPreviewSetting.title }}</template>
 | 
			
		||||
 | 
			
		||||
						<div class="_gaps_m">
 | 
			
		||||
							<MkSwitch v-model="urlPreviewEnabled">
 | 
			
		||||
								<template #label>{{ i18n.ts._urlPreviewSetting.enable }}</template>
 | 
			
		||||
							</MkSwitch>
 | 
			
		||||
 | 
			
		||||
							<MkSwitch v-model="urlPreviewRequireContentLength">
 | 
			
		||||
								<template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</template>
 | 
			
		||||
								<template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
 | 
			
		||||
							</MkSwitch>
 | 
			
		||||
 | 
			
		||||
							<MkInput v-model="urlPreviewMaximumContentLength" type="number">
 | 
			
		||||
								<template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</template>
 | 
			
		||||
								<template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template>
 | 
			
		||||
							</MkInput>
 | 
			
		||||
 | 
			
		||||
							<MkInput v-model="urlPreviewTimeout" type="number">
 | 
			
		||||
								<template #label>{{ i18n.ts._urlPreviewSetting.timeout }}</template>
 | 
			
		||||
								<template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template>
 | 
			
		||||
							</MkInput>
 | 
			
		||||
 | 
			
		||||
							<MkInput v-model="urlPreviewUserAgent" type="text">
 | 
			
		||||
								<template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}</template>
 | 
			
		||||
								<template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template>
 | 
			
		||||
							</MkInput>
 | 
			
		||||
 | 
			
		||||
							<div>
 | 
			
		||||
								<MkInput v-model="urlPreviewSummaryProxyUrl" type="text">
 | 
			
		||||
									<template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</template>
 | 
			
		||||
									<template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template>
 | 
			
		||||
								</MkInput>
 | 
			
		||||
 | 
			
		||||
								<div :class="$style.subCaption">
 | 
			
		||||
									{{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }}
 | 
			
		||||
									<ul style="padding-left: 20px; margin: 4px 0">
 | 
			
		||||
										<li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li>
 | 
			
		||||
										<li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li>
 | 
			
		||||
										<li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li>
 | 
			
		||||
										<li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li>
 | 
			
		||||
									</ul>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</FormSection>
 | 
			
		||||
				</div>
 | 
			
		||||
			</FormSuspense>
 | 
			
		||||
		</MkSpacer>
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +220,8 @@ import { fetchInstance, instance } from '@/instance.js';
 | 
			
		|||
import { i18n } from '@/i18n.js';
 | 
			
		||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import MkFolder from '@/components/MkFolder.vue';
 | 
			
		||||
import MkSelect from '@/components/MkSelect.vue';
 | 
			
		||||
 | 
			
		||||
const name = ref<string | null>(null);
 | 
			
		||||
const shortName = ref<string | null>(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +243,12 @@ const perRemoteUserUserTimelineCacheMax = ref<number>(0);
 | 
			
		|||
const perUserHomeTimelineCacheMax = ref<number>(0);
 | 
			
		||||
const perUserListTimelineCacheMax = ref<number>(0);
 | 
			
		||||
const notesPerOneAd = ref<number>(0);
 | 
			
		||||
const urlPreviewEnabled = ref<boolean>(true);
 | 
			
		||||
const urlPreviewTimeout = ref<number>(10000);
 | 
			
		||||
const urlPreviewMaximumContentLength = ref<number>(1024 * 1024 * 10);
 | 
			
		||||
const urlPreviewRequireContentLength = ref<boolean>(true);
 | 
			
		||||
const urlPreviewUserAgent = ref<string | null>(null);
 | 
			
		||||
const urlPreviewSummaryProxyUrl = ref<string | null>(null);
 | 
			
		||||
 | 
			
		||||
async function init(): Promise<void> {
 | 
			
		||||
	const meta = await misskeyApi('admin/meta');
 | 
			
		||||
| 
						 | 
				
			
			@ -217,9 +272,15 @@ async function init(): Promise<void> {
 | 
			
		|||
	perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax;
 | 
			
		||||
	perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax;
 | 
			
		||||
	notesPerOneAd.value = meta.notesPerOneAd;
 | 
			
		||||
	urlPreviewEnabled.value = meta.urlPreviewEnabled;
 | 
			
		||||
	urlPreviewTimeout.value = meta.urlPreviewTimeout;
 | 
			
		||||
	urlPreviewMaximumContentLength.value = meta.urlPreviewMaximumContentLength;
 | 
			
		||||
	urlPreviewRequireContentLength.value = meta.urlPreviewRequireContentLength;
 | 
			
		||||
	urlPreviewUserAgent.value = meta.urlPreviewUserAgent;
 | 
			
		||||
	urlPreviewSummaryProxyUrl.value = meta.urlPreviewSummaryProxyUrl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function save(): void {
 | 
			
		||||
async function save() {
 | 
			
		||||
	await os.apiWithDialog('admin/update-meta', {
 | 
			
		||||
		name: name.value,
 | 
			
		||||
		shortName: shortName.value === '' ? null : shortName.value,
 | 
			
		||||
| 
						 | 
				
			
			@ -241,6 +302,12 @@ async function save(): void {
 | 
			
		|||
		perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value,
 | 
			
		||||
		perUserListTimelineCacheMax: perUserListTimelineCacheMax.value,
 | 
			
		||||
		notesPerOneAd: notesPerOneAd.value,
 | 
			
		||||
		urlPreviewEnabled: urlPreviewEnabled.value,
 | 
			
		||||
		urlPreviewTimeout: urlPreviewTimeout.value,
 | 
			
		||||
		urlPreviewMaximumContentLength: urlPreviewMaximumContentLength.value,
 | 
			
		||||
		urlPreviewRequireContentLength: urlPreviewRequireContentLength.value,
 | 
			
		||||
		urlPreviewUserAgent: urlPreviewUserAgent.value,
 | 
			
		||||
		urlPreviewSummaryProxyUrl: urlPreviewSummaryProxyUrl.value,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	fetchInstance(true);
 | 
			
		||||
| 
						 | 
				
			
			@ -259,4 +326,9 @@ definePageMetadata(() => ({
 | 
			
		|||
	-webkit-backdrop-filter: var(--blur, blur(15px));
 | 
			
		||||
	backdrop-filter: var(--blur, blur(15px));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.subCaption {
 | 
			
		||||
	font-size: 0.85em;
 | 
			
		||||
	color: var(--fgTransparentWeak);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4810,6 +4810,7 @@ export type components = {
 | 
			
		|||
      enableServiceWorker: boolean;
 | 
			
		||||
      translatorAvailable: boolean;
 | 
			
		||||
      mediaProxy: string;
 | 
			
		||||
      enableUrlPreview: boolean;
 | 
			
		||||
      backgroundImageUrl: string | null;
 | 
			
		||||
      impressumUrl: string | null;
 | 
			
		||||
      logoImageUrl: string | null;
 | 
			
		||||
| 
						 | 
				
			
			@ -4962,11 +4963,21 @@ export type operations = {
 | 
			
		|||
            objectStorageS3ForcePathStyle: boolean;
 | 
			
		||||
            privacyPolicyUrl: string | null;
 | 
			
		||||
            repositoryUrl: string | null;
 | 
			
		||||
            /**
 | 
			
		||||
             * @deprecated
 | 
			
		||||
             * @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead.
 | 
			
		||||
             */
 | 
			
		||||
            summalyProxy: string | null;
 | 
			
		||||
            themeColor: string | null;
 | 
			
		||||
            tosUrl: string | null;
 | 
			
		||||
            uri: string;
 | 
			
		||||
            version: string;
 | 
			
		||||
            urlPreviewEnabled: boolean;
 | 
			
		||||
            urlPreviewTimeout: number;
 | 
			
		||||
            urlPreviewMaximumContentLength: number;
 | 
			
		||||
            urlPreviewRequireContentLength: boolean;
 | 
			
		||||
            urlPreviewUserAgent: string | null;
 | 
			
		||||
            urlPreviewSummaryProxyUrl: string | null;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
| 
						 | 
				
			
			@ -8862,7 +8873,6 @@ export type operations = {
 | 
			
		|||
          maintainerName?: string | null;
 | 
			
		||||
          maintainerEmail?: string | null;
 | 
			
		||||
          langs?: string[];
 | 
			
		||||
          summalyProxy?: string | null;
 | 
			
		||||
          deeplAuthKey?: string | null;
 | 
			
		||||
          deeplIsPro?: boolean;
 | 
			
		||||
          enableEmail?: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -8916,6 +8926,14 @@ export type operations = {
 | 
			
		|||
          perUserListTimelineCacheMax?: number;
 | 
			
		||||
          notesPerOneAd?: number;
 | 
			
		||||
          silencedHosts?: string[] | null;
 | 
			
		||||
          /** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */
 | 
			
		||||
          summalyProxy?: string | null;
 | 
			
		||||
          urlPreviewEnabled?: boolean;
 | 
			
		||||
          urlPreviewTimeout?: number;
 | 
			
		||||
          urlPreviewMaximumContentLength?: number;
 | 
			
		||||
          urlPreviewRequireContentLength?: boolean;
 | 
			
		||||
          urlPreviewUserAgent?: string | null;
 | 
			
		||||
          urlPreviewSummaryProxyUrl?: string | null;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -117,8 +117,8 @@ importers:
 | 
			
		|||
        specifier: 1.2.0
 | 
			
		||||
        version: 1.2.0
 | 
			
		||||
      '@misskey-dev/summaly':
 | 
			
		||||
        specifier: 5.0.3
 | 
			
		||||
        version: 5.0.3
 | 
			
		||||
        specifier: 5.1.0
 | 
			
		||||
        version: 5.1.0
 | 
			
		||||
      '@nestjs/common':
 | 
			
		||||
        specifier: 10.3.3
 | 
			
		||||
        version: 10.3.3(reflect-metadata@0.2.1)(rxjs@7.8.1)
 | 
			
		||||
| 
						 | 
				
			
			@ -4772,6 +4772,20 @@ packages:
 | 
			
		|||
      jschardet: 3.0.0
 | 
			
		||||
      private-ip: 2.3.3
 | 
			
		||||
      trace-redirect: 1.0.6
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /@misskey-dev/summaly@5.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-WAUrgX3/z4h4aI8Y/WVwmJcJ6Fa1Zf2LJCSS651t9MHoWVGABLsQ2KCXRGmlpk4i+cMDNIwweObUroosE7j8rg==}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      cheerio: 1.0.0-rc.12
 | 
			
		||||
      escape-regexp: 0.0.1
 | 
			
		||||
      got: 12.6.1
 | 
			
		||||
      html-entities: 2.3.2
 | 
			
		||||
      iconv-lite: 0.6.3
 | 
			
		||||
      jschardet: 3.0.0
 | 
			
		||||
      private-ip: 2.3.3
 | 
			
		||||
      trace-redirect: 1.0.6
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@mole-inc/bin-wrapper@8.0.1:
 | 
			
		||||
    resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue