diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0f786a6b14..31b02821bf 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -756,6 +756,7 @@ high: "高" middle: "中" low: "低" emailNotConfiguredWarning: "メールアドレスの設定がされていません。" +ratio: "比率" _forgotPassword: enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" diff --git a/migration/1620364649428-ad2.ts b/migration/1620364649428-ad2.ts new file mode 100644 index 0000000000..a2d7f563c2 --- /dev/null +++ b/migration/1620364649428-ad2.ts @@ -0,0 +1,14 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class ad21620364649428 implements MigrationInterface { + name = 'ad21620364649428' + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "ad" ADD "ratio" integer NOT NULL DEFAULT '1'`); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "ratio"`); + } + +} diff --git a/src/client/components/global/ad.vue b/src/client/components/global/ad.vue index f88a1d2026..e340f846ee 100644 --- a/src/client/components/global/ad.vue +++ b/src/client/components/global/ad.vue @@ -19,7 +19,7 @@ <script lang="ts"> import { defineComponent, ref } from 'vue'; -import { instance } from '@client/instance'; +import { Instance, instance } from '@client/instance'; import { host } from '@client/config'; import MkButton from '@client/components/ui/button.vue'; @@ -45,32 +45,45 @@ export default defineComponent({ showMenu.value = !showMenu.value; }; - let ad = null; + const choseAd = (): Instance['ads'][number] | null => { + if (props.specify) { + return props.specify as Instance['ads'][number]; + } - if (props.specify) { - ad = props.specify; - } else { let ads = instance.ads.filter(ad => props.prefer.includes(ad.place)); if (ads.length === 0) { ads = instance.ads.filter(ad => ad.place === 'square'); } - const high = ads.filter(ad => ad.priority === 'high'); - const middle = ads.filter(ad => ad.priority === 'middle'); - const low = ads.filter(ad => ad.priority === 'low'); + const lowPriorityAds = ads.filter(ad => ad.ratio === 0); + ads = ads.filter(ad => ad.ratio !== 0); - if (high.length > 0) { - ad = high[Math.floor(Math.random() * high.length)]; - } else if (middle.length > 0) { - ad = middle[Math.floor(Math.random() * middle.length)]; - } else if (low.length > 0) { - ad = low[Math.floor(Math.random() * low.length)]; + if (ads.length === 0) { + if (lowPriorityAds.length !== 0) { + return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)]; + } else { + return null; + } } - } + + const totalFactor = ads.reduce((a, b) => a + b.ratio, 0); + const r = Math.random() * totalFactor; + + let stackedFactor = 0; + for (const ad of ads) { + if (r >= stackedFactor && r <= stackedFactor + ad.ratio) { + return ad; + } else { + stackedFactor += ad.ratio; + } + } + + return null; + }; return { - ad, + ad: choseAd(), showMenu, toggleMenu, host, diff --git a/src/client/instance.ts b/src/client/instance.ts index bd6b1bd571..ad9e1a95fd 100644 --- a/src/client/instance.ts +++ b/src/client/instance.ts @@ -3,10 +3,16 @@ import { api } from './os'; // TODO: 他のタブと永続化されたstateを同期 -type Instance = { +export type Instance = { emojis: { category: string; }[]; + ads: { + ratio: number; + place: string; + url: string; + imageUrl: string; + }[]; }; const data = localStorage.getItem('instance'); diff --git a/src/client/pages/instance/ads.vue b/src/client/pages/instance/ads.vue index 20747d6f9c..6b536793b7 100644 --- a/src/client/pages/instance/ads.vue +++ b/src/client/pages/instance/ads.vue @@ -15,12 +15,17 @@ <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio> <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio> </div> + <!-- <div style="margin: 32px 0;"> {{ $ts.priority }} <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> </div> + --> + <MkInput v-model:value="ad.ratio" type="number"> + <span>{{ $ts.ratio }}</span> + </MkInput> <MkInput v-model:value="ad.expiresAt" type="date"> <span>{{ $ts.expiration }}</span> </MkInput> @@ -82,6 +87,7 @@ export default defineComponent({ memo: '', place: 'square', priority: 'middle', + ratio: 1, url: '', imageUrl: null, expiresAt: null, diff --git a/src/models/entities/ad.ts b/src/models/entities/ad.ts index 3279de29ea..b2fc04c4f0 100644 --- a/src/models/entities/ad.ts +++ b/src/models/entities/ad.ts @@ -23,11 +23,17 @@ export class Ad { }) public place: string; + // 今は使われていないが将来的に活用される可能性はある @Column('varchar', { length: 32, nullable: false }) public priority: string; + @Column('integer', { + default: 1, nullable: false + }) + public ratio: number; + @Column('varchar', { length: 1024, nullable: false }) diff --git a/src/server/api/endpoints/admin/ad/create.ts b/src/server/api/endpoints/admin/ad/create.ts index 7777e95e6e..337114a3fa 100644 --- a/src/server/api/endpoints/admin/ad/create.ts +++ b/src/server/api/endpoints/admin/ad/create.ts @@ -22,6 +22,9 @@ export const meta = { priority: { validator: $.str }, + ratio: { + validator: $.num.int().min(0) + }, expiresAt: { validator: $.num.int() }, @@ -39,6 +42,7 @@ export default define(meta, async (ps) => { url: ps.url, imageUrl: ps.imageUrl, priority: ps.priority, + ratio: ps.ratio, place: ps.place, memo: ps.memo, }); diff --git a/src/server/api/endpoints/admin/ad/update.ts b/src/server/api/endpoints/admin/ad/update.ts index 694af98394..71e6054a88 100644 --- a/src/server/api/endpoints/admin/ad/update.ts +++ b/src/server/api/endpoints/admin/ad/update.ts @@ -29,6 +29,9 @@ export const meta = { priority: { validator: $.str }, + ratio: { + validator: $.num.int().min(0) + }, expiresAt: { validator: $.num.int() }, @@ -52,6 +55,7 @@ export default define(meta, async (ps, me) => { url: ps.url, place: ps.place, priority: ps.priority, + ratio: ps.ratio, memo: ps.memo, imageUrl: ps.imageUrl, expiresAt: new Date(ps.expiresAt), diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 5b7292ef16..d170317f1c 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -511,7 +511,7 @@ export default define(meta, async (ps, me) => { ads: ads.map(ad => ({ url: ad.url, place: ad.place, - priority: ad.priority, + ratio: ad.ratio, imageUrl: ad.imageUrl, })), enableEmail: instance.enableEmail,