Compare commits

...

2 Commits

Author SHA1 Message Date
おさむのひと 9bbc2028ad
feat: URLプレビューのリダイレクトを受け入れるかどうかを設定できるようにする (#16112)
* feat: URLプレビューのリダイレクトを受け入れるかどうかを設定できるようにする

* fix CHANGELOG.md

* fix lang
2025-05-27 20:46:22 +09:00
zyoshoka 97e916c912
refactor(frontend): revoke weakening endpoint param type of API caller for type safety (#16100) 2025-05-27 20:45:05 +09:00
11 changed files with 57 additions and 18 deletions

View File

@ -16,6 +16,7 @@
- デフォルトは**テキスト、JSON、画像、動画、音声ファイル**になっています。zipなど、その他の種別のファイルは含まれていないため、必要に応じて設定を変更してください。 - デフォルトは**テキスト、JSON、画像、動画、音声ファイル**になっています。zipなど、その他の種別のファイルは含まれていないため、必要に応じて設定を変更してください。
- 場合によってはファイル種別を正しく検出できないことがあります(特にテキストフォーマット)。その場合、ファイル種別は application/octet-stream と見做されます。 - 場合によってはファイル種別を正しく検出できないことがあります(特にテキストフォーマット)。その場合、ファイル種別は application/octet-stream と見做されます。
- したがって、それらの種別不明ファイルを許可したい場合は application/octet-stream を指定に追加してください。 - したがって、それらの種別不明ファイルを許可したい場合は application/octet-stream を指定に追加してください。
- Feat: プレビュー先がリダイレクトを伴う場合、リダイレクト先のコンテンツを取得しに行くか否かを設定できるように(#16043)
- Enhance: UIのアイコンデータの読み込みを軽量化 - Enhance: UIのアイコンデータの読み込みを軽量化
### Client ### Client

8
locales/index.d.ts vendored
View File

@ -11232,6 +11232,14 @@ export interface Locale extends ILocale {
* URLプレビューを有効にする * URLプレビューを有効にする
*/ */
"enable": string; "enable": string;
/**
*
*/
"allowRedirect": string;
/**
* URLがリダイレクトされる場合に
*/
"allowRedirectDescription": string;
/** /**
* (ms) * (ms)
*/ */

View File

@ -2987,6 +2987,8 @@ _offlineScreen:
_urlPreviewSetting: _urlPreviewSetting:
title: "URLプレビューの設定" title: "URLプレビューの設定"
enable: "URLプレビューを有効にする" enable: "URLプレビューを有効にする"
allowRedirect: "プレビュー先のリダイレクトを許可"
allowRedirectDescription: "入力されたURLがリダイレクトされる場合に、そのリダイレクト先をたどってプレビューを表示するかどうかを設定します。無効にするとサーバーリソースの節約になりますが、リダイレクト先の内容は表示されなくなります。"
timeout: "プレビュー取得時のタイムアウト(ms)" timeout: "プレビュー取得時のタイムアウト(ms)"
timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。" timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。"
maximumContentLength: "Content-Lengthの最大値(byte)" maximumContentLength: "Content-Lengthの最大値(byte)"

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddUrlPreviewAllowRedirect1748310233000 {
name = 'AddUrlPreviewAllowRedirect1748310233000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "urlPreviewAllowRedirect" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "urlPreviewAllowRedirect"`);
}
}

View File

@ -619,6 +619,11 @@ export class MiMeta {
}) })
public urlPreviewEnabled: boolean; public urlPreviewEnabled: boolean;
@Column('boolean', {
default: true,
})
public urlPreviewAllowRedirect: boolean;
@Column('integer', { @Column('integer', {
default: 10000, default: 10000,
}) })

View File

@ -495,6 +495,10 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
urlPreviewAllowRedirect: {
type: 'boolean',
optional: false, nullable: false,
},
urlPreviewTimeout: { urlPreviewTimeout: {
type: 'number', type: 'number',
optional: false, nullable: false, optional: false, nullable: false,
@ -704,6 +708,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
notesPerOneAd: instance.notesPerOneAd, notesPerOneAd: instance.notesPerOneAd,
summalyProxy: instance.urlPreviewSummaryProxyUrl, summalyProxy: instance.urlPreviewSummaryProxyUrl,
urlPreviewEnabled: instance.urlPreviewEnabled, urlPreviewEnabled: instance.urlPreviewEnabled,
urlPreviewAllowRedirect: instance.urlPreviewAllowRedirect,
urlPreviewTimeout: instance.urlPreviewTimeout, urlPreviewTimeout: instance.urlPreviewTimeout,
urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength, urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,

View File

@ -170,6 +170,7 @@ export const paramDef = {
description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
}, },
urlPreviewEnabled: { type: 'boolean' }, urlPreviewEnabled: { type: 'boolean' },
urlPreviewAllowRedirect: { type: 'boolean' },
urlPreviewTimeout: { type: 'integer' }, urlPreviewTimeout: { type: 'integer' },
urlPreviewMaximumContentLength: { type: 'integer' }, urlPreviewMaximumContentLength: { type: 'integer' },
urlPreviewRequireContentLength: { type: 'boolean' }, urlPreviewRequireContentLength: { type: 'boolean' },
@ -664,6 +665,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.urlPreviewEnabled = ps.urlPreviewEnabled; set.urlPreviewEnabled = ps.urlPreviewEnabled;
} }
if (ps.urlPreviewAllowRedirect !== undefined) {
set.urlPreviewAllowRedirect = ps.urlPreviewAllowRedirect;
}
if (ps.urlPreviewTimeout !== undefined) { if (ps.urlPreviewTimeout !== undefined) {
set.urlPreviewTimeout = ps.urlPreviewTimeout; set.urlPreviewTimeout = ps.urlPreviewTimeout;
} }

View File

@ -122,7 +122,7 @@ export class UrlPreviewService {
: undefined; : undefined;
return summaly(url, { return summaly(url, {
followRedirects: false, followRedirects: this.meta.urlPreviewAllowRedirect,
lang: lang ?? 'ja-JP', lang: lang ?? 'ja-JP',
agent: agent, agent: agent,
userAgent: meta.urlPreviewUserAgent ?? undefined, userAgent: meta.urlPreviewUserAgent ?? undefined,
@ -137,6 +137,7 @@ export class UrlPreviewService {
const queryStr = query({ const queryStr = query({
url: url, url: url,
lang: lang ?? 'ja-JP', lang: lang ?? 'ja-JP',
followRedirects: this.meta.urlPreviewAllowRedirect,
userAgent: meta.urlPreviewUserAgent ?? undefined, userAgent: meta.urlPreviewUserAgent ?? undefined,
operationTimeout: meta.urlPreviewTimeout, operationTimeout: meta.urlPreviewTimeout,
contentLengthLimit: meta.urlPreviewMaximumContentLength, contentLengthLimit: meta.urlPreviewMaximumContentLength,

View File

@ -146,6 +146,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
<template v-if="urlPreviewForm.state.urlPreviewEnabled"> <template v-if="urlPreviewForm.state.urlPreviewEnabled">
<MkSwitch v-model="urlPreviewForm.state.urlPreviewAllowRedirect">
<template #label>{{ i18n.ts._urlPreviewSetting.allowRedirect }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewAllowRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
<template #caption>{{ i18n.ts._urlPreviewSetting.allowRedirectDescription }}</template>
</MkSwitch>
<MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength"> <MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
<template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template> <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
<template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template> <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
@ -288,7 +293,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, reactive } from 'vue'; import { computed } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@ -301,7 +306,6 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { useForm } from '@/composables/use-form.js'; import { useForm } from '@/composables/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue'; import MkFormFooter from '@/components/MkFormFooter.vue';
import MkRadios from '@/components/MkRadios.vue'; import MkRadios from '@/components/MkRadios.vue';
@ -370,6 +374,7 @@ const adForm = useForm({
const urlPreviewForm = useForm({ const urlPreviewForm = useForm({
urlPreviewEnabled: meta.urlPreviewEnabled, urlPreviewEnabled: meta.urlPreviewEnabled,
urlPreviewAllowRedirect: meta.urlPreviewAllowRedirect,
urlPreviewTimeout: meta.urlPreviewTimeout, urlPreviewTimeout: meta.urlPreviewTimeout,
urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength, urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength, urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength,
@ -378,6 +383,7 @@ const urlPreviewForm = useForm({
}, async (state) => { }, async (state) => {
await os.apiWithDialog('admin/update-meta', { await os.apiWithDialog('admin/update-meta', {
urlPreviewEnabled: state.urlPreviewEnabled, urlPreviewEnabled: state.urlPreviewEnabled,
urlPreviewAllowRedirect: state.urlPreviewAllowRedirect,
urlPreviewTimeout: state.urlPreviewTimeout, urlPreviewTimeout: state.urlPreviewTimeout,
urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength, urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: state.urlPreviewRequireContentLength, urlPreviewRequireContentLength: state.urlPreviewRequireContentLength,

View File

@ -9,24 +9,12 @@ import { apiUrl } from '@@/js/config.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
export const pendingApiRequestsCount = ref(0); export const pendingApiRequestsCount = ref(0);
export type Endpoint = keyof Misskey.Endpoints;
export type Request<E extends Endpoint> = Misskey.Endpoints[E]['req'];
export type AnyRequest<E extends Endpoint | (string & unknown)> =
(E extends Endpoint ? Request<E> : never) | object;
export type Response<E extends Endpoint | (string & unknown), P extends AnyRequest<E>> =
E extends Endpoint
? P extends Request<E> ? Misskey.api.SwitchCaseResponseType<E, P> : never
: object;
// Implements Misskey.api.ApiClient.request // Implements Misskey.api.ApiClient.request
export function misskeyApi< export function misskeyApi<
ResT = void, ResT = void,
E extends Endpoint | NonNullable<string> = Endpoint, E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
P extends AnyRequest<E> = E extends Endpoint ? Request<E> : never, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
_ResT = ResT extends void ? Response<E, P> : ResT, _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
>( >(
endpoint: E, endpoint: E,
data: P & { i?: string | null; } = {} as any, data: P & { i?: string | null; } = {} as any,

View File

@ -8809,6 +8809,7 @@ export type operations = {
uri: string; uri: string;
version: string; version: string;
urlPreviewEnabled: boolean; urlPreviewEnabled: boolean;
urlPreviewAllowRedirect: boolean;
urlPreviewTimeout: number; urlPreviewTimeout: number;
urlPreviewMaximumContentLength: number; urlPreviewMaximumContentLength: number;
urlPreviewRequireContentLength: boolean; urlPreviewRequireContentLength: boolean;
@ -11536,6 +11537,7 @@ export type operations = {
/** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */ /** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */
summalyProxy?: string | null; summalyProxy?: string | null;
urlPreviewEnabled?: boolean; urlPreviewEnabled?: boolean;
urlPreviewAllowRedirect?: boolean;
urlPreviewTimeout?: number; urlPreviewTimeout?: number;
urlPreviewMaximumContentLength?: number; urlPreviewMaximumContentLength?: number;
urlPreviewRequireContentLength?: boolean; urlPreviewRequireContentLength?: boolean;