diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-05-11 14:39:40 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-05-11 14:39:40 +0900 |
| commit | f96c60c1a0374aabeecbe82c15d7e3373efed8eb (patch) | |
| tree | 59b136dff0287f74166b385d6de811d83b1f455a | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.81.0 (diff) | |
| download | misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.tar.gz misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.tar.bz2 misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.zip | |
Merge branch 'develop'
53 files changed, 389 insertions, 108 deletions
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index b7372ac47d..6d62b5345e 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1,11 +1,13 @@ --- _lang_: "Čeština" +headlineMisskey: "Síť propojená poznámkami" introMisskey: "Vítejte! Misskey je otevřený a decentralizovaný microblogový servis.\n\"Poznámkami\" můžete sdílet co se zrovna děje se všemi ve Vašem okolí. 📡\nPomocí \"reakcí\" můžete sdílet své názory a pocity na ostatní poznámky. 👍\nPojďte objevovat nový svět! 🚀" monthAndDay: "{day}. {month}." search: "Vyhledávání" notifications: "Oznámení" username: "Uživatelské jméno" password: "Heslo" +forgotPassword: "Zapomenuté heslo" fetchingAsApObject: "Načítám data z Fediversu..." ok: "Potvrdit" gotIt: "Rozumím!" @@ -72,6 +74,7 @@ error: "Chyba" somethingHappened: "Jejda. Něco se nepovedlo." retry: "Opakovat" pageLoadError: "Nepodařilo se načíst stránku" +enterListName: "Jméno seznamu" privacy: "Soukromí" follow: "Sledovaní" unfollow: "Přestat sledovat" @@ -112,16 +115,24 @@ emojiName: "Jméno emoji" emojiUrl: "URL obrázku" addEmoji: "Přidat emoji" settingGuide: "Doporučené nastavení" +cacheRemoteFiles: "Ukládání vzdálených souborů do mezipaměti" +cacheRemoteFilesDescription: "Zakázání tohoto nastavení způsobí, že vzdálené soubory budou odkazovány přímo, místo aby byly ukládány do mezipaměti. Tím se ušetří úložiště na serveru, ale zvýší se provoz, protože se negenerují miniatury." flagAsBot: "Tento účet je bot" flagAsBotDescription: "Pokud je tento účet kontrolován programem zaškrtněte tuto možnost. To označí tento účet jako bot pro ostatní vývojáře a zabrání tak nekonečným interakcím s ostatními boty a upraví Misskey systém aby se choval k tomuhle účtu jako bot." flagAsCat: "Tenhle účet je kočka" flagAsCatDescription: "Vyberte tuto možnost aby tento účet byl označen jako kočka." autoAcceptFollowed: "Automaticky akceptovat následování od účtů které sledujete" +addAccount: "Přidat účet" loginFailed: "Přihlášení se nezdařilo." +showOnRemote: "Více na původním profilu" general: "Obecně" wallpaper: "Obrázek na pozadí" setWallpaper: "Nastavení obrázku na pozadí" removeWallpaper: "Odstranit pozadí" +youHaveNoLists: "Nemáte žádné seznamy" +proxyAccount: "Proxy účet" +proxyAccountDescription: "Proxy účet je účet, který za určitých podmínek sleduje uživatele na dálku vaším jménem. Například když uživatel zařadí vzdáleného uživatele do seznamu, pokud nikdo nesleduje uživatele na seznamu, aktivita nebude doručena instanci, takže místo toho bude uživatele sledovat účet proxy." +host: "Hostitel" selectUser: "Vyberte uživatele" recipient: "Pro" annotation: "Komentáře" @@ -139,6 +150,8 @@ operations: "Operace" software: "Software" version: "Verze" metadata: "Metadata" +withNFiles: "{n} soubor(ů)" +monitor: "Monitorovat" jobQueue: "Fronta úloh" cpuAndMemory: "CPU a paměť" network: "Síť" @@ -204,8 +217,12 @@ remoteUserCaution: "Tyto informace nemusí být aktuální jelikož uživatel je activity: "Aktivita" images: "Obrázky" birthday: "Datum narození" +yearsOld: "{age} let" registeredDate: "Datum registrace" location: "Lokace" +theme: "Vzhled" +themeForLightMode: "Vzhled pro použití ve světlém režimu" +themeForDarkMode: "Vzhled k použití v tmavém režimu" light: "Světlý" dark: "Tmavý" lightThemes: "Světlý vzhled" @@ -319,6 +336,10 @@ retype: "Zadejte znovu" noteOf: "{user} poznámky" inviteToGroup: "Pozvat do skupiny" invitations: "Pozvat" +checking: "Ověřuji" +available: "K dispozici" +unavailable: "Není k dispozici" +usernameInvalidFormat: "Písmena, čísla a _ jsou povolená." tooShort: "Příliš krátké" tooLong: "Příliš dlouhé" weakPassword: "Slabé heslo" @@ -330,7 +351,13 @@ signinWith: "Přihlásit se s {x}" signinFailed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo." or: "Nebo" language: "Jazyk" +uiLanguage: "Jazyk uživatelského rozhraní" +groupInvited: "Pozvat do skupiny" +aboutX: "O {x}" +useOsNativeEmojis: "Použití nativních emoji operačního systému" youHaveNoGroups: "Nemáte žádné skupiny" +joinOrCreateGroup: "Můžete požádat o pozvání do stávající skupiny nebo vytvořit novou." +noHistory: "Žádná historie" signinHistory: "Historie přihlášení" category: "Kategorie" tags: "Štítky" @@ -371,6 +398,7 @@ rooms: "Místnost" inboxUrl: "Inbox URL" deletedNote: "Odstraněné příspěvky" invisibleNote: "Skryté příspěvky" +smtpHost: "Hostitel" smtpUser: "Uživatelské jméno" smtpPass: "Heslo" clearCache: "Vyprázdnit mezipaměť" @@ -416,6 +444,8 @@ _timelines: _rooms: _roomType: default: "Výchozí" + _furnitures: + monitor: "Monitorovat" _pages: blocks: image: "Obrázky" @@ -438,6 +468,7 @@ _pages: types: array: "Seznamy" _notification: + youWereInvitedToGroup: "Pozvat do skupiny" _types: follow: "Sledovaní" mention: "Zmínění" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index ffd2aa93f9..356ea26573 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -754,6 +754,10 @@ high: "Hoch" middle: "Mittel" low: "Niedrig" emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt" +ratio: "Verhältnis" +_ad: + back: "Zurück" + reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen" _forgotPassword: enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit der du dein Passwort zurücksetzen kannst." ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator." diff --git a/locales/en-US.yml b/locales/en-US.yml index 22dbd35243..4cf417d21b 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -756,6 +756,10 @@ high: "High" middle: "Medium" low: "Low" emailNotConfiguredWarning: "Email address not set" +ratio: "Ratio" +_ad: + back: "Back" + reduceFrequencyOfThisAd: "Show this ad less" _forgotPassword: enterEmail: "Enter the email address you used to register. A link with which you can reset your password will then be sent to it." ifNoEmail: "If you did not use an email during registration, please contact the administrator instead." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index fa645b3ecf..ae7fba8976 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -667,6 +667,8 @@ user: "Usuarios" administration: "Administrar" expiration: "Termina el" middle: "Mediano" +_ad: + back: "Deseleccionar" _gallery: unlike: "Quitar me gusta" _email: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 8dc1b98fdf..c62b1c3b6e 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -607,7 +607,7 @@ chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouvertu behavior: "Comportement" sample: "Exemple" abuseReports: "Signalements" -reportAbuse: "Signalements" +reportAbuse: "Signaler" reportAbuseOf: "Signaler {name}" fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien." abuseReported: "Le rapport est envoyé. Merci." @@ -750,12 +750,16 @@ popularPosts: "Les plus consultées" shareWithNote: "Partager dans une note" ads: "Publicité" expiration: "Échéance" -memo: "Mémo" +memo: "Pense-bête" priority: "Priorité" high: "Haute" middle: "Moyen" low: "Basse" emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail." +ratio: "Ratio" +_ad: + back: "Retour" + reduceFrequencyOfThisAd: "Voir cette publicité moins souvent" _forgotPassword: enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte. Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette adresse." ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice de votre instance." diff --git a/locales/it-IT.yml b/locales/it-IT.yml index db236bad9e..69c8152d7f 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -7,6 +7,7 @@ search: "Cerca" notifications: "Notifiche" username: "Nome utente" password: "Password" +forgotPassword: "Hai dimenticato la tua password?" fetchingAsApObject: "Recuperando dal Fediverso..." ok: "OK" gotIt: "Capito!" @@ -589,10 +590,13 @@ regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solit fileIdOrUrl: "ID o URL del file" chatOpenBehavior: "Comportamento della finestra di chat quando viene aperta" behavior: "Comportamento" -abuseReports: "Segnala" -reportAbuse: "Segnala" +abuseReports: "Segnalazioni" +reportAbuse: "Segnalazioni" reportAbuseOf: "Segnala {name}" +fillAbuseReportDescription: "Si prega di spiegare il motivo della segnalazione. Se riguarda una nota precisa, si prega di collegare anche l'URL della nota." +abuseReported: "La segnalazione è stata inviata. Grazie." send: "Inviare" +abuseMarkAsResolved: "Contrassegna la segnalazione come risolta" openInNewTab: "Apri in una nuova scheda" openInSideView: "Apri in vista laterale" defaultNavigationBehaviour: "Navigazione preimpostata" @@ -704,6 +708,7 @@ onlineStatus: "Stato di connessione" hideOnlineStatus: "Stato invisibile" hideOnlineStatusDescription: "Abilitare l'opzione di stato invisibile può guastare la praticità di singole funzioni, come la ricerca." online: "Online" +active: "Attiv@" offline: "Offline" notRecommended: "Sconsigliato" botProtection: "Protezione contro i bot" @@ -724,8 +729,22 @@ gallery: "Galleria" recentPosts: "Le più recenti" popularPosts: "Le più visualizzate" shareWithNote: "Condividere in nota" +ads: "Pubblicità" expiration: "Scadenza" -middle: "Predefinito" +memo: "Promemoria" +priority: "Priorità" +high: "Alta" +middle: "Media" +low: "Bassa" +emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail." +ratio: "Rapporto" +_ad: + back: "Indietro" + reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso" +_forgotPassword: + enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo." + ifNoEmail: "Se nessun indirizzo e-mail è stato registrato, si prega di contattare l'amministratore·trice dell'istanza." + contactAdmin: "Poiché questa istanza non permette l'utilizzo di una mail, si prega di contattare l'amministratore·trice dell'istanza per poter ripristinare la password." _gallery: my: "Le mie pubblicazioni" liked: "Pubblicazioni che mi piacciono" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0f786a6b14..e869f5b015 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -756,6 +756,11 @@ high: "高" middle: "中" low: "低" emailNotConfiguredWarning: "メールアドレスの設定がされていません。" +ratio: "比率" + +_ad: + back: "戻る" + reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" _forgotPassword: enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 16f9eb23b0..6435b5835b 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -649,6 +649,8 @@ memo: "メモ" high: "高い" middle: "中" low: "低い" +_ad: + back: "戻る" _gallery: unlike: "良くないわ" _email: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index c8dba9b15c..49069d258f 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -7,6 +7,7 @@ search: "검색" notifications: "알림" username: "유저명" password: "비밀번호" +forgotPassword: "비밀번호 재설정" fetchingAsApObject: "연합에서 조회 중" ok: "OK" gotIt: "알겠어요" @@ -747,8 +748,22 @@ gallery: "갤러리" recentPosts: "최근 포스트" popularPosts: "인기 포스트" shareWithNote: "노트로 공유" -expiration: "투표 기한" +ads: "광고" +expiration: "기한" +memo: "메모" +priority: "우선순위" +high: "높음" middle: "보통" +low: "낮음" +emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다." +ratio: "비율" +_ad: + back: "뒤로" + reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" +_forgotPassword: + enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다." + ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오." + contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오." _gallery: my: "내 갤러리" liked: "좋아요 한 갤러리" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 4a1a2d8686..fce7a97082 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -649,6 +649,8 @@ user: "Użytkownicy" administration: "Zarządzanie" expiration: "Ankieta kończy się" middle: "Średnie" +_ad: + back: "Wróć" _gallery: unlike: "Cofnij polubienie" _email: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 6e906eb55a..2610403a71 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -749,6 +749,8 @@ popularPosts: "Популярные публикации" shareWithNote: "Поделиться заметкой" expiration: "Опрос длится" middle: "Средне" +_ad: + back: "Выход" _gallery: my: "Личная" liked: "Понравившееся" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 2dfd667330..bb010ed5f6 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -691,6 +691,8 @@ user: "Користувачі" administration: "Управління" expiration: "Опитування закінчується" middle: "Середній" +_ad: + back: "Назад" _gallery: unlike: "Не вподобати" _email: diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 4d1780c687..4754fec4a4 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -7,6 +7,7 @@ search: "搜索" notifications: "通知" username: "用户名" password: "密码" +forgotPassword: "忘记密码" fetchingAsApObject: "联合查询中" ok: "OK" gotIt: "我明白了" @@ -173,7 +174,7 @@ metadata: "元数据" withNFiles: "{n}个文件" monitor: "监视器" jobQueue: "作业队列" -cpuAndMemory: "CPU使用量" +cpuAndMemory: "CPU和内存" network: "网络" disk: "存储" instanceInfo: "实例信息" @@ -747,8 +748,22 @@ gallery: "图库" recentPosts: "最新发布" popularPosts: "热门投稿" shareWithNote: "在帖子中分享" +ads: "广告" expiration: "截止时间" +memo: "便笺" +priority: "优先级" +high: "高" middle: "中" +low: "低" +emailNotConfiguredWarning: "电子邮件地址未设置。" +ratio: "比率" +_ad: + back: "返回" + reduceFrequencyOfThisAd: "减少此广告的频率" +_forgotPassword: + enterEmail: "请输入您用来注册帐户的电子邮件地址。密码重置链接将发送到该地址。" + ifNoEmail: "如果您没有使用电子邮件地址注册,请联系管理员。" + contactAdmin: "该实例不支持电子邮件。如果您想重设密码,请联系管理员。" _gallery: my: "我的图库" liked: "喜欢的图片" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 23e47f25ac..e8c7e55531 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -733,6 +733,8 @@ noBotProtectionWarning: "尚未設定Bot防護。" configure: "設定" expiration: "期限" middle: "中" +_ad: + back: "返回" _gallery: unlike: "收回喜歡" _email: 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/package.json b/package.json index 06c2293a71..b16bb44a50 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo <syuilotan@yahoo.co.jp>", - "version": "12.80.3", + "version": "12.81.0", "codename": "indigo", "repository": { "type": "git", diff --git a/src/client/components/global/ad.vue b/src/client/components/global/ad.vue index f88a1d2026..8397b2229e 100644 --- a/src/client/components/global/ad.vue +++ b/src/client/components/global/ad.vue @@ -9,8 +9,9 @@ <div class="menu" v-else> <div class="body"> <div>Ads by {{ host }}</div> - <!--<MkButton>{{ $ts.stopThisAd }}</MkButton>--> - <button class="_textButton" @click="toggleMenu">{{ $ts.close }}</button> + <!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>--> + <MkButton v-if="ad.ratio !== 0" class="button" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton> + <button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button> </div> </div> </div> @@ -19,9 +20,11 @@ <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'; +import { defaultStore } from '@client/store'; +import * as os from '@client/os'; export default defineComponent({ components: { @@ -45,35 +48,65 @@ 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]; + } + + const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? { + ...ad, + ratio: 0 + } : ad); - if (props.specify) { - ad = props.specify; - } else { - let ads = instance.ads.filter(ad => props.prefer.includes(ad.place)); + let ads = allAds.filter(ad => props.prefer.includes(ad.place)); if (ads.length === 0) { - ads = instance.ads.filter(ad => ad.place === 'square'); + ads = allAds.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; + }; + + const chosen = ref(choseAd()); + + const reduceFrequency = () => { + if (chosen.value == null) return; + if (defaultStore.state.mutedAds.includes(chosen.value.id)) return; + defaultStore.push('mutedAds', chosen.value.id); + os.success(); + chosen.value = choseAd(); + showMenu.value = false; + }; return { - ad, + ad: chosen, showMenu, toggleMenu, host, + reduceFrequency, }; } }); @@ -157,6 +190,10 @@ export default defineComponent({ margin: 0 auto; max-width: 400px; border: solid 1px var(--divider); + + > .button { + margin: 8px auto; + } } } } diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 0573b2592d..267e4debd2 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -9,7 +9,6 @@ </div> </div> <div class="gqnyydlz" :style="{ background: color }" v-else> - <i class="fas fa-eye-slash" @click="hide = true"></i> <a :href="image.url" :title="image.name" @@ -18,6 +17,7 @@ <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name" :cover="false"/> <div class="gif" v-if="image.type === 'image/gif'">GIF</div> </a> + <i class="fas fa-eye-slash" @click="hide = true"></i> </div> </template> diff --git a/src/client/components/ui/modal.vue b/src/client/components/ui/modal.vue index 3b11213426..2a4eec4034 100644 --- a/src/client/components/ui/modal.vue +++ b/src/client/components/ui/modal.vue @@ -226,12 +226,12 @@ export default defineComponent({ .mk-modal { > .bg { - z-index: 10000; + z-index: 20000; } > .content:not(.popup) { position: fixed; - z-index: 10000; + z-index: 20000; top: 0; bottom: 0; left: 0; @@ -263,7 +263,7 @@ export default defineComponent({ > .content.popup { position: absolute; - z-index: 10000; + z-index: 20000; &.fixed { position: fixed; diff --git a/src/client/instance.ts b/src/client/instance.ts index bd6b1bd571..024ff1acbd 100644 --- a/src/client/instance.ts +++ b/src/client/instance.ts @@ -3,10 +3,17 @@ import { api } from './os'; // TODO: 他のタブと永続化されたstateを同期 -type Instance = { +export type Instance = { emojis: { category: string; }[]; + ads: { + id: string; + 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/client/pages/instance/index.vue b/src/client/pages/instance/index.vue index 974c4345bb..acd46518f5 100644 --- a/src/client/pages/instance/index.vue +++ b/src/client/pages/instance/index.vue @@ -43,6 +43,7 @@ <FormGroup> <template #label>{{ $ts.info }}</template> <FormLink :active="page === 'database'" replace to="/instance/database"><template #icon><i class="fas fa-database"></i></template>{{ $ts.database }}</FormLink> + <FormLink :active="page === 'logs'" replace to="/instance/logs"><template #icon><i class="fas fa-stream"></i></template>{{ $ts.logs }}</FormLink> </FormGroup> </FormBase> </div> @@ -105,6 +106,7 @@ export default defineComponent({ case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); case 'ads': return defineAsyncComponent(() => import('./ads.vue')); case 'database': return defineAsyncComponent(() => import('./database.vue')); + case 'logs': return defineAsyncComponent(() => import('./logs.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue')); case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue')); diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue index f52e5d866b..75a24bcb80 100644 --- a/src/client/pages/instance/instance.vue +++ b/src/client/pages/instance/instance.vue @@ -123,7 +123,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; import XModalWindow from '@client/components/ui/modal-window.vue'; import MkUsersDialog from '@client/components/users-dialog.vue'; @@ -280,7 +280,7 @@ export default defineComponent({ } Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg'); - this.chartInstance = new Chart(this.canvas, { + this.chartInstance = markRaw(new Chart(this.canvas, { type: 'line', data: { labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(), @@ -331,7 +331,7 @@ export default defineComponent({ mode: 'index', } } - }); + })); }, getDate(ago: number) { diff --git a/src/client/pages/instance/logs.vue b/src/client/pages/instance/logs.vue index 7b634259d3..112b0f66d0 100644 --- a/src/client/pages/instance/logs.vue +++ b/src/client/pages/instance/logs.vue @@ -5,13 +5,13 @@ <span>{{ $ts.domain }}</span> </MkInput> <MkSelect v-model:value="logLevel"> - <template #label>{{ $ts.level }}</template> - <option value="all">{{ $ts.levels.all }}</option> - <option value="info">{{ $ts.levels.info }}</option> - <option value="success">{{ $ts.levels.success }}</option> - <option value="warning">{{ $ts.levels.warning }}</option> - <option value="error">{{ $ts.levels.error }}</option> - <option value="debug">{{ $ts.levels.debug }}</option> + <template #label>Level</template> + <option value="all">All</option> + <option value="info">Info</option> + <option value="success">Success</option> + <option value="warning">Warning</option> + <option value="error">Error</option> + <option value="debug">Debug</option> </MkSelect> </div> @@ -45,6 +45,8 @@ export default defineComponent({ MkTextarea, }, + emits: ['info'], + data() { return { [symbols.PAGE_INFO]: { @@ -72,6 +74,10 @@ export default defineComponent({ this.fetchLogs(); }, + mounted() { + this.$emit('info', this[symbols.PAGE_INFO]); + }, + methods: { fetchLogs() { os.api('admin/logs', { diff --git a/src/client/pages/instance/queue.chart.vue b/src/client/pages/instance/queue.chart.vue index 446c979209..0cd983127f 100644 --- a/src/client/pages/instance/queue.chart.vue +++ b/src/client/pages/instance/queue.chart.vue @@ -27,7 +27,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; import number from '../../filters/number'; @@ -69,7 +69,7 @@ export default defineComponent({ Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg'); - this.chart = new Chart(this.$refs.chart, { + this.chart = markRaw(new Chart(this.$refs.chart, { type: 'line', data: { labels: [], @@ -152,7 +152,7 @@ export default defineComponent({ mode: 'index', } } - }); + })); this.connection.on('stats', this.onStats); this.connection.on('statsLog', this.onStatsLog); diff --git a/src/client/store.ts b/src/client/store.ts index 376135a99d..1a3286554d 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -55,6 +55,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'account', default: [] }, + mutedAds: { + where: 'account', + default: [] as string[] + }, menu: { where: 'deviceAccount', diff --git a/src/client/ui/deck/widgets-column.vue b/src/client/ui/deck/widgets-column.vue index 47d7e7e314..69aaaffa88 100644 --- a/src/client/ui/deck/widgets-column.vue +++ b/src/client/ui/deck/widgets-column.vue @@ -64,11 +64,8 @@ export default defineComponent({ <style lang="scss" scoped> .wtdtxvec { --margin: 8px; + --panelShadow: none; padding: 0 var(--margin); - - ::v-deep(._panel) { - box-shadow: none; - } } </style> diff --git a/src/client/widgets/federation.vue b/src/client/widgets/federation.vue index 3f2e1e691d..8ab7f594a2 100644 --- a/src/client/widgets/federation.vue +++ b/src/client/widgets/federation.vue @@ -114,6 +114,7 @@ export default defineComponent({ overflow: hidden; font-size: 0.9em; color: var(--fg); + padding-right: 8px; > .a { display: block; @@ -129,6 +130,9 @@ export default defineComponent({ font-size: 75%; opacity: 0.7; line-height: $bodyInfoHieght; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } 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/queue/index.ts b/src/queue/index.ts index 201b663799..01dd020d93 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -1,7 +1,6 @@ import * as httpSignature from 'http-signature'; import config from '@/config'; -import { User } from '../models/entities/user'; import { program } from '../argv'; import processDeliver from './processors/deliver'; @@ -11,14 +10,9 @@ import procesObjectStorage from './processors/object-storage'; import { queueLogger } from './logger'; import { DriveFile } from '../models/entities/drive-file'; import { getJobInfo } from './get-job-info'; -import { IActivity } from '../remote/activitypub/type'; import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues'; - -export type InboxJobData = { - activity: IActivity, - /** HTTP-Signature */ - signature: httpSignature.IParsedSignature -}; +import { ThinUser } from './types'; +import { IActivity } from '@/remote/activitypub/type'; function renderError(e: Error): any { return { @@ -65,8 +59,9 @@ objectStorageQueue .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); -export function deliver(user: { id: User['id']; host: null; }, content: any, to: any) { +export function deliver(user: ThinUser, content: unknown, to: string | null) { if (content == null) return null; + if (to == null) return null; const data = { user, @@ -85,7 +80,7 @@ export function deliver(user: { id: User['id']; host: null; }, content: any, to: }); } -export function inbox(activity: any, signature: httpSignature.IParsedSignature) { +export function inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { const data = { activity: activity, signature @@ -102,7 +97,7 @@ export function inbox(activity: any, signature: httpSignature.IParsedSignature) }); } -export function createDeleteDriveFilesJob(user: { id: User['id'] }) { +export function createDeleteDriveFilesJob(user: ThinUser) { return dbQueue.add('deleteDriveFiles', { user: user }, { @@ -111,7 +106,7 @@ export function createDeleteDriveFilesJob(user: { id: User['id'] }) { }); } -export function createExportNotesJob(user: { id: User['id'] }) { +export function createExportNotesJob(user: ThinUser) { return dbQueue.add('exportNotes', { user: user }, { @@ -120,7 +115,7 @@ export function createExportNotesJob(user: { id: User['id'] }) { }); } -export function createExportFollowingJob(user: { id: User['id'] }) { +export function createExportFollowingJob(user: ThinUser) { return dbQueue.add('exportFollowing', { user: user }, { @@ -129,7 +124,7 @@ export function createExportFollowingJob(user: { id: User['id'] }) { }); } -export function createExportMuteJob(user: { id: User['id'] }) { +export function createExportMuteJob(user: ThinUser) { return dbQueue.add('exportMute', { user: user }, { @@ -138,7 +133,7 @@ export function createExportMuteJob(user: { id: User['id'] }) { }); } -export function createExportBlockingJob(user: { id: User['id'] }) { +export function createExportBlockingJob(user: ThinUser) { return dbQueue.add('exportBlocking', { user: user }, { @@ -147,7 +142,7 @@ export function createExportBlockingJob(user: { id: User['id'] }) { }); } -export function createExportUserListsJob(user: { id: User['id'] }) { +export function createExportUserListsJob(user: ThinUser) { return dbQueue.add('exportUserLists', { user: user }, { @@ -156,7 +151,7 @@ export function createExportUserListsJob(user: { id: User['id'] }) { }); } -export function createImportFollowingJob(user: { id: User['id'] }, fileId: DriveFile['id']) { +export function createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { return dbQueue.add('importFollowing', { user: user, fileId: fileId @@ -166,7 +161,7 @@ export function createImportFollowingJob(user: { id: User['id'] }, fileId: Drive }); } -export function createImportUserListsJob(user: { id: User['id'] }, fileId: DriveFile['id']) { +export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { return dbQueue.add('importUserLists', { user: user, fileId: fileId diff --git a/src/queue/initialize.ts b/src/queue/initialize.ts index 941fe4bc33..4c0e5f9d87 100644 --- a/src/queue/initialize.ts +++ b/src/queue/initialize.ts @@ -1,8 +1,8 @@ -import * as Queue from 'bull'; +import * as Bull from 'bull'; import config from '@/config'; -export function initialize(name: string, limitPerSec = -1) { - return new Queue(name, { +export function initialize<T>(name: string, limitPerSec = -1) { + return new Bull<T>(name, { redis: { port: config.redis.port, host: config.redis.host, diff --git a/src/queue/processors/db/delete-drive-files.ts b/src/queue/processors/db/delete-drive-files.ts index a2fd9050a9..874623204b 100644 --- a/src/queue/processors/db/delete-drive-files.ts +++ b/src/queue/processors/db/delete-drive-files.ts @@ -4,10 +4,11 @@ import { queueLogger } from '../../logger'; import { deleteFileSync } from '../../../services/drive/delete-file'; import { Users, DriveFiles } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('delete-drive-files'); -export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> { +export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Deleting drive files of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); diff --git a/src/queue/processors/db/export-blocking.ts b/src/queue/processors/db/export-blocking.ts index 9bbc9b2f12..001b50a22c 100644 --- a/src/queue/processors/db/export-blocking.ts +++ b/src/queue/processors/db/export-blocking.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, Blockings } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-blocking'); -export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { +export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting blocking of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -61,7 +62,7 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { } const content = getFullApAccount(u.username, u.host); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-following.ts b/src/queue/processors/db/export-following.ts index 79df2298d8..c1ccb7af4c 100644 --- a/src/queue/processors/db/export-following.ts +++ b/src/queue/processors/db/export-following.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, Followings } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-following'); -export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { +export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting following of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -61,7 +62,7 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { } const content = getFullApAccount(u.username, u.host); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-mute.ts b/src/queue/processors/db/export-mute.ts index c10556f882..55d45cc29c 100644 --- a/src/queue/processors/db/export-mute.ts +++ b/src/queue/processors/db/export-mute.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, Mutings } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-mute'); -export async function exportMute(job: Bull.Job, done: any): Promise<void> { +export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting mute of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -61,7 +62,7 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> { } const content = getFullApAccount(u.username, u.host); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-notes.ts b/src/queue/processors/db/export-notes.ts index f76a47aacd..2d09c0d201 100644 --- a/src/queue/processors/db/export-notes.ts +++ b/src/queue/processors/db/export-notes.ts @@ -9,10 +9,11 @@ import { Users, Notes, Polls } from '../../../models'; import { MoreThan } from 'typeorm'; import { Note } from '../../../models/entities/note'; import { Poll } from '../../../models/entities/poll'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-notes'); -export async function exportNotes(job: Bull.Job, done: any): Promise<void> { +export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting notes of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -33,7 +34,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { const stream = fs.createWriteStream(path, { flags: 'a' }); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write('[', err => { if (err) { logger.error(err); @@ -72,7 +73,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { poll = await Polls.findOneOrFail({ noteId: note.id }); } const content = JSON.stringify(serialize(note, poll)); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(exportedNotesCount === 0 ? content : ',\n' + content, err => { if (err) { logger.error(err); @@ -92,7 +93,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { job.progress(exportedNotesCount / total); } - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(']', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-user-lists.ts b/src/queue/processors/db/export-user-lists.ts index b6c527fc78..3f793e064f 100644 --- a/src/queue/processors/db/export-user-lists.ts +++ b/src/queue/processors/db/export-user-lists.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, UserLists, UserListJoinings } from '../../../models'; import { In } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-user-lists'); -export async function exportUserLists(job: Bull.Job, done: any): Promise<void> { +export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting user lists of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -45,7 +46,7 @@ export async function exportUserLists(job: Bull.Job, done: any): Promise<void> { for (const u of users) { const acct = getFullApAccount(u.username, u.host); const content = `${list.name},${acct}`; - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/import-following.ts b/src/queue/processors/db/import-following.ts index 554337849e..55c0aaa9f9 100644 --- a/src/queue/processors/db/import-following.ts +++ b/src/queue/processors/db/import-following.ts @@ -7,10 +7,11 @@ import { resolveUser } from '../../../remote/resolve-user'; import { downloadTextFile } from '@/misc/download-text-file'; import { isSelfHost, toPuny } from '@/misc/convert-host'; import { Users, DriveFiles } from '../../../models'; +import { DbUserImportJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('import-following'); -export async function importFollowing(job: Bull.Job, done: any): Promise<void> { +export async function importFollowing(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { logger.info(`Importing following of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); diff --git a/src/queue/processors/db/import-user-lists.ts b/src/queue/processors/db/import-user-lists.ts index 2fe023da7d..d316b95ff0 100644 --- a/src/queue/processors/db/import-user-lists.ts +++ b/src/queue/processors/db/import-user-lists.ts @@ -8,10 +8,11 @@ import { downloadTextFile } from '@/misc/download-text-file'; import { isSelfHost, toPuny } from '@/misc/convert-host'; import { DriveFiles, Users, UserLists, UserListJoinings } from '../../../models'; import { genId } from '@/misc/gen-id'; +import { DbUserImportJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('import-user-lists'); -export async function importUserLists(job: Bull.Job, done: any): Promise<void> { +export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { logger.info(`Importing user lists of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); diff --git a/src/queue/processors/db/index.ts b/src/queue/processors/db/index.ts index 921cdf7ab1..b56b7bfa2c 100644 --- a/src/queue/processors/db/index.ts +++ b/src/queue/processors/db/index.ts @@ -1,4 +1,5 @@ import * as Bull from 'bull'; +import { DbJobData } from '@/queue/types'; import { deleteDriveFiles } from './delete-drive-files'; import { exportNotes } from './export-notes'; import { exportFollowing } from './export-following'; @@ -17,10 +18,10 @@ const jobs = { exportUserLists, importFollowing, importUserLists -} as any; +} as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; -export default function(dbQueue: Bull.Queue) { +export default function(dbQueue: Bull.Queue<DbJobData>) { for (const [k, v] of Object.entries(jobs)) { - dbQueue.process(k, v as any); + dbQueue.process(k, v); } } diff --git a/src/queue/processors/deliver.ts b/src/queue/processors/deliver.ts index b167154fcd..f9c53fc8f1 100644 --- a/src/queue/processors/deliver.ts +++ b/src/queue/processors/deliver.ts @@ -10,6 +10,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { toPuny } from '@/misc/convert-host'; import { Cache } from '@/misc/cache'; import { Instance } from '../../models/entities/instance'; +import { DeliverJobData } from '../types'; const logger = new Logger('deliver'); @@ -17,7 +18,7 @@ let latest: string | null = null; const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60); -export default async (job: Bull.Job) => { +export default async (job: Bull.Job<DeliverJobData>) => { const { host } = new URL(job.data.to); // ブロックしてたら中断 diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 7c746eb25d..2ef19777f1 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -10,7 +10,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { toPuny, extractDbHost } from '@/misc/convert-host'; import { getApId } from '../../remote/activitypub/type'; import { fetchInstanceMetadata } from '../../services/fetch-instance-metadata'; -import { InboxJobData } from '..'; +import { InboxJobData } from '../types'; import DbResolver from '../../remote/activitypub/db-resolver'; import { resolvePerson } from '../../remote/activitypub/models/person'; import { LdSignature } from '../../remote/activitypub/misc/ld-signature'; @@ -23,7 +23,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => { const activity = job.data.activity; //#region Log - const info = Object.assign({}, activity); + const info = Object.assign({}, activity) as any; delete info['@context']; logger.debug(JSON.stringify(info, null, 2)); //#endregion diff --git a/src/queue/processors/object-storage/clean-remote-files.ts b/src/queue/processors/object-storage/clean-remote-files.ts index 7b34892e1f..a922755f4d 100644 --- a/src/queue/processors/object-storage/clean-remote-files.ts +++ b/src/queue/processors/object-storage/clean-remote-files.ts @@ -7,7 +7,7 @@ import { MoreThan, Not, IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('clean-remote-files'); -export default async function cleanRemoteFiles(job: Bull.Job, done: any): Promise<void> { +export default async function cleanRemoteFiles(job: Bull.Job<{}>, done: any): Promise<void> { logger.info(`Deleting cached remote files...`); let deletedCount = 0; diff --git a/src/queue/processors/object-storage/delete-file.ts b/src/queue/processors/object-storage/delete-file.ts index f899df7d2e..31050998af 100644 --- a/src/queue/processors/object-storage/delete-file.ts +++ b/src/queue/processors/object-storage/delete-file.ts @@ -1,7 +1,8 @@ +import { ObjectStorageFileJobData } from '@/queue/types'; import * as Bull from 'bull'; import { deleteObjectStorageFile } from '../../../services/drive/delete-file'; -export default async (job: Bull.Job) => { +export default async (job: Bull.Job<ObjectStorageFileJobData>) => { const key: string = job.data.key; await deleteObjectStorageFile(key); diff --git a/src/queue/processors/object-storage/index.ts b/src/queue/processors/object-storage/index.ts index 33ef665b38..0d9570e179 100644 --- a/src/queue/processors/object-storage/index.ts +++ b/src/queue/processors/object-storage/index.ts @@ -1,14 +1,15 @@ import * as Bull from 'bull'; +import { ObjectStorageJobData } from '@/queue/types'; import deleteFile from './delete-file'; import cleanRemoteFiles from './clean-remote-files'; const jobs = { deleteFile, cleanRemoteFiles, -} as any; +} as Record<string, Bull.ProcessCallbackFunction<ObjectStorageJobData> | Bull.ProcessPromiseFunction<ObjectStorageJobData>>; export default function(q: Bull.Queue) { for (const [k, v] of Object.entries(jobs)) { - q.process(k, 16, v as any); + q.process(k, 16, v); } } diff --git a/src/queue/queues.ts b/src/queue/queues.ts index 819bcf31d8..5e2754b83f 100644 --- a/src/queue/queues.ts +++ b/src/queue/queues.ts @@ -1,7 +1,8 @@ import config from '@/config'; import { initialize as initializeQueue } from './initialize'; +import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types'; -export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128); -export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16); -export const dbQueue = initializeQueue('db'); -export const objectStorageQueue = initializeQueue('objectStorage'); +export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.deliverJobPerSec || 128); +export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16); +export const dbQueue = initializeQueue<DbJobData>('db'); +export const objectStorageQueue = initializeQueue<ObjectStorageJobData>('objectStorage'); diff --git a/src/queue/types.ts b/src/queue/types.ts new file mode 100644 index 0000000000..a782fc6b97 --- /dev/null +++ b/src/queue/types.ts @@ -0,0 +1,39 @@ +import { DriveFile } from '@/models/entities/drive-file'; +import { User } from '@/models/entities/user'; +import { IActivity } from '@/remote/activitypub/type'; +import * as httpSignature from 'http-signature'; + +export type DeliverJobData = { + /** Actor */ + user: ThinUser; + /** Activity */ + content: unknown; + /** inbox URL to deliver */ + to: string; +}; + +export type InboxJobData = { + activity: IActivity; + signature: httpSignature.IParsedSignature; +}; + +export type DbJobData = DbUserJobData | DbUserImportJobData; + +export type DbUserJobData = { + user: ThinUser; +}; + +export type DbUserImportJobData = { + user: ThinUser; + fileId: DriveFile['id']; +}; + +export type ObjectStorageJobData = ObjectStorageFileJobData | {}; + +export type ObjectStorageFileJobData = { + key: string; +}; + +export type ThinUser = { + id: User['id']; +}; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index a6f02a1f8f..6ba03e4a48 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -11,6 +11,11 @@ export default async (actor: IRemoteUser, activity: ILike) => { await extractEmojis(activity.tag || [], actor.host).catch(() => null); - await create(actor, note, activity._misskey_reaction || activity.content || activity.name); - return `ok`; + return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => { + if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { + return 'skip: already reacted'; + } else { + throw e; + } + }).then(() => 'ok'); }; 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/admin/get-index-stats.ts b/src/server/api/endpoints/admin/get-index-stats.ts new file mode 100644 index 0000000000..f2b06d0ef2 --- /dev/null +++ b/src/server/api/endpoints/admin/get-index-stats.ts @@ -0,0 +1,26 @@ +import define from '../../define'; +import { getConnection } from 'typeorm'; + +export const meta = { + requireCredential: true as const, + requireModerator: true, + + tags: ['admin'], + + params: { + }, +}; + +export default define(meta, async () => { + const stats = await + getConnection().query(`SELECT * FROM pg_indexes;`) + .then(recs => { + const res = [] as { tablename: string; indexname: string; }[]; + for (const rec of recs) { + res.push(rec); + } + return res; + }); + + return stats; +}); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 5b7292ef16..26d9110ea9 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -509,9 +509,10 @@ export default define(meta, async (ps, me) => { maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH), emojis: await Emojis.packMany(emojis), ads: ads.map(ad => ({ + id: ad.id, url: ad.url, place: ad.place, - priority: ad.priority, + ratio: ad.ratio, imageUrl: ad.imageUrl, })), enableEmail: instance.enableEmail, diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index e2e7fc54ef..ce6ae08b30 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -13,12 +13,13 @@ import { createNotification } from '../../create-notification'; import deleteReaction from './delete'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; import { NoteReaction } from '../../../models/entities/note-reaction'; +import { IdentifiableError } from '@/misc/identifiable-error'; export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { // TODO: cache reaction = await toDbReaction(reaction, user.host); - let record: NoteReaction = { + const record: NoteReaction = { id: genId(), createdAt: new Date(), noteId: note.id, @@ -31,17 +32,18 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, await NoteReactions.insert(record); } catch (e) { if (isDuplicateKeyValueError(e)) { - record = await NoteReactions.findOneOrFail({ + const exists = await NoteReactions.findOneOrFail({ noteId: note.id, userId: user.id, }); - if (record.reaction !== reaction) { + if (exists.reaction !== reaction) { // 別のリアクションがすでにされていたら置き換える await deleteReaction(user, note); + await NoteReactions.insert(record); } else { - // 同じリアクションがすでにされていたら何もしない - return; + // 同じリアクションがすでにされていたらエラー + throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); } } else { throw e; |