From 4bbfc98883c5d26a8958f37928909579b888143d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 8 Oct 2023 17:56:44 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E6=9B=B4=E6=96=B0=E4=B8=AD=E3=81=AB=E5=BA=83?= =?UTF-8?q?=E5=91=8A=E3=82=92=E6=8C=BF=E5=85=A5=20(#11989)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: タイムライン更新中に広告を挿入 * 翻訳を変更 * Run api extractor * fix api extractor * Update locales/ja-JP.yml Co-authored-by: syuilo * confirm -> mkinfo * MkInputにmin, maxを指定できるように * 負の値が指定されたら何もしない --------- Co-authored-by: syuilo --- CHANGELOG.md | 3 ++ locales/index.d.ts | 4 +++ locales/ja-JP.yml | 4 +++ .../migration/1696743032098-AdsOnStream.js | 16 +++++++++++ packages/backend/src/models/Meta.ts | 5 ++++ .../src/server/api/endpoints/admin/meta.ts | 5 ++++ .../server/api/endpoints/admin/update-meta.ts | 5 ++++ .../backend/src/server/api/endpoints/meta.ts | 6 ++++ packages/frontend/src/components/MkInput.vue | 4 +++ .../frontend/src/components/MkTimeline.vue | 9 ++++++ .../frontend/src/pages/admin/settings.vue | 28 ++++++++++++++++--- packages/misskey-js/etc/misskey-js.api.md | 3 +- packages/misskey-js/src/entities.ts | 1 + 13 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 packages/backend/migration/1696743032098-AdsOnStream.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ab09b9a76..e655bef8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ - Feat: ユーザーごとのハイライト - Feat: プライバシーポリシー・運営者情報(Impressum)の指定が可能になりました - プライバシーポリシーはサーバー登録時に同意確認が入ります +- Feat: タイムラインがリアルタイム更新中に広告を挿入できるようになりました + - デフォルトは無効 + - 頻度はコントロールパネルから設定できます。運営中のサーバーのTLの流速を見て、最適な値を指定してください。 - Enhance: ソフトワードミュートとハードワードミュートは統合されました - Enhance: モデレーションログ機能の強化 - Enhance: ローカリゼーションの更新 diff --git a/locales/index.d.ts b/locales/index.d.ts index d90f8fa6f..8a429e3b6 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1627,6 +1627,10 @@ export interface Locale { "reduceFrequencyOfThisAd": string; "hide": string; "timezoneinfo": string; + "adsSettings": string; + "notesPerOneAd": string; + "setZeroToDisable": string; + "adsTooClose": string; }; "_forgotPassword": { "enterEmail": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c92d83436..52e06e720 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1546,6 +1546,10 @@ _ad: reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" hide: "表示しない" timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されます。" + adsSettings: "広告配信設定" + notesPerOneAd: "リアルタイム更新中に広告を配信する間隔(ノートの個数)" + setZeroToDisable: "0でリアルタイム更新時の広告配信を無効" + adsTooClose: "広告の配信間隔が極めて短いため、ユーザー体験が著しく損われる可能性があります。" _forgotPassword: enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" diff --git a/packages/backend/migration/1696743032098-AdsOnStream.js b/packages/backend/migration/1696743032098-AdsOnStream.js new file mode 100644 index 000000000..c86ee8488 --- /dev/null +++ b/packages/backend/migration/1696743032098-AdsOnStream.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AdsOnStream1696743032098 { + name = 'AdsOnStream1696743032098' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "notesPerOneAd" integer NOT NULL DEFAULT '0'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "notesPerOneAd"`); + } +} diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 60fdaab54..d2bd0c26e 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -503,4 +503,9 @@ export class MiMeta { default: 300, }) public perUserListTimelineCacheMax: number; + + @Column('integer', { + default: 0, + }) + public notesPerOneAd: number; } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index ddca5b470..5a74456ab 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -297,6 +297,10 @@ export const meta = { type: 'number', optional: false, nullable: false, }, + notesPerOneAd: { + type: 'number', + optional: false, nullable: false, + }, }, }, } as const; @@ -408,6 +412,7 @@ export default class extends Endpoint { // eslint- perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, + notesPerOneAd: instance.notesPerOneAd, }; }); } 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 ee7c564e2..7db25e659 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -114,6 +114,7 @@ export const paramDef = { perRemoteUserUserTimelineCacheMax: { type: 'integer' }, perUserHomeTimelineCacheMax: { type: 'integer' }, perUserListTimelineCacheMax: { type: 'integer' }, + notesPerOneAd: { type: 'integer' }, }, required: [], } as const; @@ -471,6 +472,10 @@ export default class extends Endpoint { // eslint- set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax; } + if (ps.notesPerOneAd !== undefined) { + set.notesPerOneAd = ps.notesPerOneAd; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index e7c8d2682..2727e4f09 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -181,6 +181,11 @@ export const meta = { }, }, }, + notesPerOneAd: { + type: 'number', + optional: false, nullable: false, + default: 0, + }, requireSetup: { type: 'boolean', optional: false, nullable: false, @@ -331,6 +336,7 @@ export default class extends Endpoint { // eslint- imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, })), + notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index 315ce958c..9490d8042 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -23,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only :spellcheck="spellcheck" :step="step" :list="id" + :min="min" + :max="max" @focus="focused = true" @blur="focused = false" @keydown="onKeydown($event)" @@ -59,6 +61,8 @@ const props = defineProps<{ spellcheck?: boolean; step?: any; datalist?: string[]; + min?: string; + max?: string; inline?: boolean; debounce?: boolean; manualSave?: boolean; diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index c4a34667e..4e71c048b 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -13,6 +13,7 @@ import MkNotes from '@/components/MkNotes.vue'; import { useStream } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; import { $i } from '@/account.js'; +import { instance } from '@/instance.js'; import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ @@ -38,7 +39,15 @@ provide('inChannel', computed(() => props.src === 'channel')); const tlComponent: InstanceType = $ref(); +let tlNotesCount = 0; + const prepend = note => { + tlNotesCount++; + + if (instance.notesPerOneAd > 0 && tlNotesCount % instance.notesPerOneAd === 0) { + note._shouldInsertAd_ = true; + } + tlComponent.pagingComponent?.prepend(note); emit('note'); diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 2e3f1611d..2db0dd5c3 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -107,6 +107,22 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + +
+
+ + + + + + {{ i18n.ts._ad.adsTooClose }} + +
+
+
@@ -127,6 +143,7 @@ import XHeader from './_header_.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; +import MkInfo from '@/components/MkInfo.vue'; import FormSection from '@/components/form/section.vue'; import FormSplit from '@/components/form/split.vue'; import FormSuspense from '@/components/form/suspense.vue'; @@ -152,6 +169,7 @@ let perLocalUserUserTimelineCacheMax: number = $ref(0); let perRemoteUserUserTimelineCacheMax: number = $ref(0); let perUserHomeTimelineCacheMax: number = $ref(0); let perUserListTimelineCacheMax: number = $ref(0); +let notesPerOneAd: number = $ref(0); async function init(): Promise { const meta = await os.api('admin/meta'); @@ -171,10 +189,11 @@ async function init(): Promise { perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax; perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax; perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax; + notesPerOneAd = meta.notesPerOneAd; } -function save(): void { - os.apiWithDialog('admin/update-meta', { +async function save(): void { + await os.apiWithDialog('admin/update-meta', { name, shortName: shortName === '' ? null : shortName, description, @@ -191,9 +210,10 @@ function save(): void { perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax, perUserListTimelineCacheMax, - }).then(() => { - fetchInstance(); + notesPerOneAd, }); + + fetchInstance(); } const headerTabs = $computed(() => []); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index fe913886c..09662073e 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2448,6 +2448,7 @@ type LiteInstanceMetadata = { url: string; imageUrl: string; }[]; + notesPerOneAd: number; translatorAvailable: boolean; serverRules: string[]; }; @@ -2980,7 +2981,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts // src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts -// src/entities.ts:596:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts +// src/entities.ts:597:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index b60056e21..b02f3e1f9 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -362,6 +362,7 @@ export type LiteInstanceMetadata = { url: string; imageUrl: string; }[]; + notesPerOneAd: number; translatorAvailable: boolean; serverRules: string[]; };