Compare commits

..

No commits in common. "ebcac1a8de659e8d818dc75fd3b9b727a8c51a3b" and "bd0dde6522326d1d6d0bd020be8982a376b77d7e" have entirely different histories.

53 changed files with 108 additions and 389 deletions

View File

@ -1,13 +1,11 @@
--- ---
_lang_: "Čeština" _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! 🚀" 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}." monthAndDay: "{day}. {month}."
search: "Vyhledávání" search: "Vyhledávání"
notifications: "Oznámení" notifications: "Oznámení"
username: "Uživatelské jméno" username: "Uživatelské jméno"
password: "Heslo" password: "Heslo"
forgotPassword: "Zapomenuté heslo"
fetchingAsApObject: "Načítám data z Fediversu..." fetchingAsApObject: "Načítám data z Fediversu..."
ok: "Potvrdit" ok: "Potvrdit"
gotIt: "Rozumím!" gotIt: "Rozumím!"
@ -74,7 +72,6 @@ error: "Chyba"
somethingHappened: "Jejda. Něco se nepovedlo." somethingHappened: "Jejda. Něco se nepovedlo."
retry: "Opakovat" retry: "Opakovat"
pageLoadError: "Nepodařilo se načíst stránku" pageLoadError: "Nepodařilo se načíst stránku"
enterListName: "Jméno seznamu"
privacy: "Soukromí" privacy: "Soukromí"
follow: "Sledovaní" follow: "Sledovaní"
unfollow: "Přestat sledovat" unfollow: "Přestat sledovat"
@ -115,24 +112,16 @@ emojiName: "Jméno emoji"
emojiUrl: "URL obrázku" emojiUrl: "URL obrázku"
addEmoji: "Přidat emoji" addEmoji: "Přidat emoji"
settingGuide: "Doporučené nastavení" 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" 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." 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" flagAsCat: "Tenhle účet je kočka"
flagAsCatDescription: "Vyberte tuto možnost aby tento účet byl označen jako 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" autoAcceptFollowed: "Automaticky akceptovat následování od účtů které sledujete"
addAccount: "Přidat účet"
loginFailed: "Přihlášení se nezdařilo." loginFailed: "Přihlášení se nezdařilo."
showOnRemote: "Více na původním profilu"
general: "Obecně" general: "Obecně"
wallpaper: "Obrázek na pozadí" wallpaper: "Obrázek na pozadí"
setWallpaper: "Nastavení obrázku na pozadí" setWallpaper: "Nastavení obrázku na pozadí"
removeWallpaper: "Odstranit 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" selectUser: "Vyberte uživatele"
recipient: "Pro" recipient: "Pro"
annotation: "Komentáře" annotation: "Komentáře"
@ -150,8 +139,6 @@ operations: "Operace"
software: "Software" software: "Software"
version: "Verze" version: "Verze"
metadata: "Metadata" metadata: "Metadata"
withNFiles: "{n} soubor(ů)"
monitor: "Monitorovat"
jobQueue: "Fronta úloh" jobQueue: "Fronta úloh"
cpuAndMemory: "CPU a paměť" cpuAndMemory: "CPU a paměť"
network: "Síť" network: "Síť"
@ -217,12 +204,8 @@ remoteUserCaution: "Tyto informace nemusí být aktuální jelikož uživatel je
activity: "Aktivita" activity: "Aktivita"
images: "Obrázky" images: "Obrázky"
birthday: "Datum narození" birthday: "Datum narození"
yearsOld: "{age} let"
registeredDate: "Datum registrace" registeredDate: "Datum registrace"
location: "Lokace" 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ý" light: "Světlý"
dark: "Tmavý" dark: "Tmavý"
lightThemes: "Světlý vzhled" lightThemes: "Světlý vzhled"
@ -336,10 +319,6 @@ retype: "Zadejte znovu"
noteOf: "{user} poznámky" noteOf: "{user} poznámky"
inviteToGroup: "Pozvat do skupiny" inviteToGroup: "Pozvat do skupiny"
invitations: "Pozvat" invitations: "Pozvat"
checking: "Ověřuji"
available: "K dispozici"
unavailable: "Není k dispozici"
usernameInvalidFormat: "Písmena, čísla a _ jsou povolená."
tooShort: "Příliš krátké" tooShort: "Příliš krátké"
tooLong: "Příliš dlouhé" tooLong: "Příliš dlouhé"
weakPassword: "Slabé heslo" weakPassword: "Slabé heslo"
@ -351,13 +330,7 @@ signinWith: "Přihlásit se s {x}"
signinFailed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo." signinFailed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo."
or: "Nebo" or: "Nebo"
language: "Jazyk" 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" 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í" signinHistory: "Historie přihlášení"
category: "Kategorie" category: "Kategorie"
tags: "Štítky" tags: "Štítky"
@ -398,7 +371,6 @@ rooms: "Místnost"
inboxUrl: "Inbox URL" inboxUrl: "Inbox URL"
deletedNote: "Odstraněné příspěvky" deletedNote: "Odstraněné příspěvky"
invisibleNote: "Skryté příspěvky" invisibleNote: "Skryté příspěvky"
smtpHost: "Hostitel"
smtpUser: "Uživatelské jméno" smtpUser: "Uživatelské jméno"
smtpPass: "Heslo" smtpPass: "Heslo"
clearCache: "Vyprázdnit mezipaměť" clearCache: "Vyprázdnit mezipaměť"
@ -444,8 +416,6 @@ _timelines:
_rooms: _rooms:
_roomType: _roomType:
default: "Výchozí" default: "Výchozí"
_furnitures:
monitor: "Monitorovat"
_pages: _pages:
blocks: blocks:
image: "Obrázky" image: "Obrázky"
@ -468,7 +438,6 @@ _pages:
types: types:
array: "Seznamy" array: "Seznamy"
_notification: _notification:
youWereInvitedToGroup: "Pozvat do skupiny"
_types: _types:
follow: "Sledovaní" follow: "Sledovaní"
mention: "Zmínění" mention: "Zmínění"

View File

@ -754,10 +754,6 @@ high: "Hoch"
middle: "Mittel" middle: "Mittel"
low: "Niedrig" low: "Niedrig"
emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt" emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt"
ratio: "Verhältnis"
_ad:
back: "Zurück"
reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen"
_forgotPassword: _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." 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." ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator."

View File

@ -756,10 +756,6 @@ high: "High"
middle: "Medium" middle: "Medium"
low: "Low" low: "Low"
emailNotConfiguredWarning: "Email address not set" emailNotConfiguredWarning: "Email address not set"
ratio: "Ratio"
_ad:
back: "Back"
reduceFrequencyOfThisAd: "Show this ad less"
_forgotPassword: _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." 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." ifNoEmail: "If you did not use an email during registration, please contact the administrator instead."

View File

@ -667,8 +667,6 @@ user: "Usuarios"
administration: "Administrar" administration: "Administrar"
expiration: "Termina el" expiration: "Termina el"
middle: "Mediano" middle: "Mediano"
_ad:
back: "Deseleccionar"
_gallery: _gallery:
unlike: "Quitar me gusta" unlike: "Quitar me gusta"
_email: _email:

View File

@ -607,7 +607,7 @@ chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouvertu
behavior: "Comportement" behavior: "Comportement"
sample: "Exemple" sample: "Exemple"
abuseReports: "Signalements" abuseReports: "Signalements"
reportAbuse: "Signaler" reportAbuse: "Signalements"
reportAbuseOf: "Signaler {name}" reportAbuseOf: "Signaler {name}"
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien." 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." abuseReported: "Le rapport est envoyé. Merci."
@ -750,16 +750,12 @@ popularPosts: "Les plus consultées"
shareWithNote: "Partager dans une note" shareWithNote: "Partager dans une note"
ads: "Publicité" ads: "Publicité"
expiration: "Échéance" expiration: "Échéance"
memo: "Pense-bête" memo: "Mémo"
priority: "Priorité" priority: "Priorité"
high: "Haute" high: "Haute"
middle: "Moyen" middle: "Moyen"
low: "Basse" low: "Basse"
emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail." emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail."
ratio: "Ratio"
_ad:
back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
_forgotPassword: _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." 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." ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice de votre instance."

View File

@ -7,7 +7,6 @@ search: "Cerca"
notifications: "Notifiche" notifications: "Notifiche"
username: "Nome utente" username: "Nome utente"
password: "Password" password: "Password"
forgotPassword: "Hai dimenticato la tua password?"
fetchingAsApObject: "Recuperando dal Fediverso..." fetchingAsApObject: "Recuperando dal Fediverso..."
ok: "OK" ok: "OK"
gotIt: "Capito!" gotIt: "Capito!"
@ -590,13 +589,10 @@ regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solit
fileIdOrUrl: "ID o URL del file" fileIdOrUrl: "ID o URL del file"
chatOpenBehavior: "Comportamento della finestra di chat quando viene aperta" chatOpenBehavior: "Comportamento della finestra di chat quando viene aperta"
behavior: "Comportamento" behavior: "Comportamento"
abuseReports: "Segnalazioni" abuseReports: "Segnala"
reportAbuse: "Segnalazioni" reportAbuse: "Segnala"
reportAbuseOf: "Segnala {name}" 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" send: "Inviare"
abuseMarkAsResolved: "Contrassegna la segnalazione come risolta"
openInNewTab: "Apri in una nuova scheda" openInNewTab: "Apri in una nuova scheda"
openInSideView: "Apri in vista laterale" openInSideView: "Apri in vista laterale"
defaultNavigationBehaviour: "Navigazione preimpostata" defaultNavigationBehaviour: "Navigazione preimpostata"
@ -708,7 +704,6 @@ onlineStatus: "Stato di connessione"
hideOnlineStatus: "Stato invisibile" hideOnlineStatus: "Stato invisibile"
hideOnlineStatusDescription: "Abilitare l'opzione di stato invisibile può guastare la praticità di singole funzioni, come la ricerca." hideOnlineStatusDescription: "Abilitare l'opzione di stato invisibile può guastare la praticità di singole funzioni, come la ricerca."
online: "Online" online: "Online"
active: "Attiv@"
offline: "Offline" offline: "Offline"
notRecommended: "Sconsigliato" notRecommended: "Sconsigliato"
botProtection: "Protezione contro i bot" botProtection: "Protezione contro i bot"
@ -729,22 +724,8 @@ gallery: "Galleria"
recentPosts: "Le più recenti" recentPosts: "Le più recenti"
popularPosts: "Le più visualizzate" popularPosts: "Le più visualizzate"
shareWithNote: "Condividere in nota" shareWithNote: "Condividere in nota"
ads: "Pubblicità"
expiration: "Scadenza" expiration: "Scadenza"
memo: "Promemoria" middle: "Predefinito"
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: _gallery:
my: "Le mie pubblicazioni" my: "Le mie pubblicazioni"
liked: "Pubblicazioni che mi piacciono" liked: "Pubblicazioni che mi piacciono"

View File

@ -756,11 +756,6 @@ high: "高"
middle: "中" middle: "中"
low: "低" low: "低"
emailNotConfiguredWarning: "メールアドレスの設定がされていません。" emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
ratio: "比率"
_ad:
back: "戻る"
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
_forgotPassword: _forgotPassword:
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"

View File

@ -649,8 +649,6 @@ memo: "メモ"
high: "高い" high: "高い"
middle: "中" middle: "中"
low: "低い" low: "低い"
_ad:
back: "戻る"
_gallery: _gallery:
unlike: "良くないわ" unlike: "良くないわ"
_email: _email:

View File

@ -7,7 +7,6 @@ search: "검색"
notifications: "알림" notifications: "알림"
username: "유저명" username: "유저명"
password: "비밀번호" password: "비밀번호"
forgotPassword: "비밀번호 재설정"
fetchingAsApObject: "연합에서 조회 중" fetchingAsApObject: "연합에서 조회 중"
ok: "OK" ok: "OK"
gotIt: "알겠어요" gotIt: "알겠어요"
@ -748,22 +747,8 @@ gallery: "갤러리"
recentPosts: "최근 포스트" recentPosts: "최근 포스트"
popularPosts: "인기 포스트" popularPosts: "인기 포스트"
shareWithNote: "노트로 공유" shareWithNote: "노트로 공유"
ads: "광고" expiration: "투표 기한"
expiration: "기한"
memo: "메모"
priority: "우선순위"
high: "높음"
middle: "보통" middle: "보통"
low: "낮음"
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
ratio: "비율"
_ad:
back: "뒤로"
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
_forgotPassword:
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오."
_gallery: _gallery:
my: "내 갤러리" my: "내 갤러리"
liked: "좋아요 한 갤러리" liked: "좋아요 한 갤러리"

View File

@ -649,8 +649,6 @@ user: "Użytkownicy"
administration: "Zarządzanie" administration: "Zarządzanie"
expiration: "Ankieta kończy się" expiration: "Ankieta kończy się"
middle: "Średnie" middle: "Średnie"
_ad:
back: "Wróć"
_gallery: _gallery:
unlike: "Cofnij polubienie" unlike: "Cofnij polubienie"
_email: _email:

View File

@ -749,8 +749,6 @@ popularPosts: "Популярные публикации"
shareWithNote: "Поделиться заметкой" shareWithNote: "Поделиться заметкой"
expiration: "Опрос длится" expiration: "Опрос длится"
middle: "Средне" middle: "Средне"
_ad:
back: "Выход"
_gallery: _gallery:
my: "Личная" my: "Личная"
liked: "Понравившееся" liked: "Понравившееся"

View File

@ -691,8 +691,6 @@ user: "Користувачі"
administration: "Управління" administration: "Управління"
expiration: "Опитування закінчується" expiration: "Опитування закінчується"
middle: "Середній" middle: "Середній"
_ad:
back: "Назад"
_gallery: _gallery:
unlike: "Не вподобати" unlike: "Не вподобати"
_email: _email:

View File

@ -7,7 +7,6 @@ search: "搜索"
notifications: "通知" notifications: "通知"
username: "用户名" username: "用户名"
password: "密码" password: "密码"
forgotPassword: "忘记密码"
fetchingAsApObject: "联合查询中" fetchingAsApObject: "联合查询中"
ok: "OK" ok: "OK"
gotIt: "我明白了" gotIt: "我明白了"
@ -174,7 +173,7 @@ metadata: "元数据"
withNFiles: "{n}个文件" withNFiles: "{n}个文件"
monitor: "监视器" monitor: "监视器"
jobQueue: "作业队列" jobQueue: "作业队列"
cpuAndMemory: "CPU和内存" cpuAndMemory: "CPU使用量"
network: "网络" network: "网络"
disk: "存储" disk: "存储"
instanceInfo: "实例信息" instanceInfo: "实例信息"
@ -748,22 +747,8 @@ gallery: "图库"
recentPosts: "最新发布" recentPosts: "最新发布"
popularPosts: "热门投稿" popularPosts: "热门投稿"
shareWithNote: "在帖子中分享" shareWithNote: "在帖子中分享"
ads: "广告"
expiration: "截止时间" expiration: "截止时间"
memo: "便笺"
priority: "优先级"
high: "高"
middle: "中" middle: "中"
low: "低"
emailNotConfiguredWarning: "电子邮件地址未设置。"
ratio: "比率"
_ad:
back: "返回"
reduceFrequencyOfThisAd: "减少此广告的频率"
_forgotPassword:
enterEmail: "请输入您用来注册帐户的电子邮件地址。密码重置链接将发送到该地址。"
ifNoEmail: "如果您没有使用电子邮件地址注册,请联系管理员。"
contactAdmin: "该实例不支持电子邮件。如果您想重设密码,请联系管理员。"
_gallery: _gallery:
my: "我的图库" my: "我的图库"
liked: "喜欢的图片" liked: "喜欢的图片"

View File

@ -733,8 +733,6 @@ noBotProtectionWarning: "尚未設定Bot防護。"
configure: "設定" configure: "設定"
expiration: "期限" expiration: "期限"
middle: "中" middle: "中"
_ad:
back: "返回"
_gallery: _gallery:
unlike: "收回喜歡" unlike: "收回喜歡"
_email: _email:

View File

@ -1,14 +0,0 @@
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"`);
}
}

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.81.0", "version": "12.80.3",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -9,9 +9,8 @@
<div class="menu" v-else> <div class="menu" v-else>
<div class="body"> <div class="body">
<div>Ads by {{ host }}</div> <div>Ads by {{ host }}</div>
<!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>--> <!--<MkButton>{{ $ts.stopThisAd }}</MkButton>-->
<MkButton v-if="ad.ratio !== 0" class="button" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton> <button class="_textButton" @click="toggleMenu">{{ $ts.close }}</button>
<button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -20,11 +19,9 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import { Instance, instance } from '@client/instance'; import { instance } from '@client/instance';
import { host } from '@client/config'; import { host } from '@client/config';
import MkButton from '@client/components/ui/button.vue'; import MkButton from '@client/components/ui/button.vue';
import { defaultStore } from '@client/store';
import * as os from '@client/os';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -48,65 +45,35 @@ export default defineComponent({
showMenu.value = !showMenu.value; showMenu.value = !showMenu.value;
}; };
const choseAd = (): Instance['ads'][number] | null => { let ad = null;
if (props.specify) { if (props.specify) {
return props.specify as Instance['ads'][number]; ad = props.specify;
} } else {
let ads = instance.ads.filter(ad => props.prefer.includes(ad.place));
const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? {
...ad,
ratio: 0
} : ad);
let ads = allAds.filter(ad => props.prefer.includes(ad.place));
if (ads.length === 0) { if (ads.length === 0) {
ads = allAds.filter(ad => ad.place === 'square'); ads = instance.ads.filter(ad => ad.place === 'square');
} }
const lowPriorityAds = ads.filter(ad => ad.ratio === 0); const high = ads.filter(ad => ad.priority === 'high');
ads = ads.filter(ad => ad.ratio !== 0); const middle = ads.filter(ad => ad.priority === 'middle');
const low = ads.filter(ad => ad.priority === 'low');
if (ads.length === 0) { if (high.length > 0) {
if (lowPriorityAds.length !== 0) { ad = high[Math.floor(Math.random() * high.length)];
return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)]; } else if (middle.length > 0) {
} else { ad = middle[Math.floor(Math.random() * middle.length)];
return null; } else if (low.length > 0) {
ad = low[Math.floor(Math.random() * low.length)];
} }
} }
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 { return {
ad: chosen, ad,
showMenu, showMenu,
toggleMenu, toggleMenu,
host, host,
reduceFrequency,
}; };
} }
}); });
@ -190,10 +157,6 @@ export default defineComponent({
margin: 0 auto; margin: 0 auto;
max-width: 400px; max-width: 400px;
border: solid 1px var(--divider); border: solid 1px var(--divider);
> .button {
margin: 8px auto;
}
} }
} }
} }

View File

@ -9,6 +9,7 @@
</div> </div>
</div> </div>
<div class="gqnyydlz" :style="{ background: color }" v-else> <div class="gqnyydlz" :style="{ background: color }" v-else>
<i class="fas fa-eye-slash" @click="hide = true"></i>
<a <a
:href="image.url" :href="image.url"
:title="image.name" :title="image.name"
@ -17,7 +18,6 @@
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name" :cover="false"/> <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> <div class="gif" v-if="image.type === 'image/gif'">GIF</div>
</a> </a>
<i class="fas fa-eye-slash" @click="hide = true"></i>
</div> </div>
</template> </template>

View File

@ -226,12 +226,12 @@ export default defineComponent({
.mk-modal { .mk-modal {
> .bg { > .bg {
z-index: 20000; z-index: 10000;
} }
> .content:not(.popup) { > .content:not(.popup) {
position: fixed; position: fixed;
z-index: 20000; z-index: 10000;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -263,7 +263,7 @@ export default defineComponent({
> .content.popup { > .content.popup {
position: absolute; position: absolute;
z-index: 20000; z-index: 10000;
&.fixed { &.fixed {
position: fixed; position: fixed;

View File

@ -3,17 +3,10 @@ import { api } from './os';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
export type Instance = { type Instance = {
emojis: { emojis: {
category: string; category: string;
}[]; }[];
ads: {
id: string;
ratio: number;
place: string;
url: string;
imageUrl: string;
}[];
}; };
const data = localStorage.getItem('instance'); const data = localStorage.getItem('instance');

View File

@ -15,17 +15,12 @@
<MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio> <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio>
<MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio> <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio>
</div> </div>
<!--
<div style="margin: 32px 0;"> <div style="margin: 32px 0;">
{{ $ts.priority }} {{ $ts.priority }}
<MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> <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="middle">{{ $ts.middle }}</MkRadio>
<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
</div> </div>
-->
<MkInput v-model:value="ad.ratio" type="number">
<span>{{ $ts.ratio }}</span>
</MkInput>
<MkInput v-model:value="ad.expiresAt" type="date"> <MkInput v-model:value="ad.expiresAt" type="date">
<span>{{ $ts.expiration }}</span> <span>{{ $ts.expiration }}</span>
</MkInput> </MkInput>
@ -87,7 +82,6 @@ export default defineComponent({
memo: '', memo: '',
place: 'square', place: 'square',
priority: 'middle', priority: 'middle',
ratio: 1,
url: '', url: '',
imageUrl: null, imageUrl: null,
expiresAt: null, expiresAt: null,

View File

@ -43,7 +43,6 @@
<FormGroup> <FormGroup>
<template #label>{{ $ts.info }}</template> <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 === '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> </FormGroup>
</FormBase> </FormBase>
</div> </div>
@ -106,7 +105,6 @@ export default defineComponent({
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
case 'ads': return defineAsyncComponent(() => import('./ads.vue')); case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
case 'database': return defineAsyncComponent(() => import('./database.vue')); case 'database': return defineAsyncComponent(() => import('./database.vue'));
case 'logs': return defineAsyncComponent(() => import('./logs.vue'));
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
case 'settings': return defineAsyncComponent(() => import('./settings.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue')); case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue'));

View File

@ -123,7 +123,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, markRaw } from 'vue'; import { defineComponent } from 'vue';
import Chart from 'chart.js'; import Chart from 'chart.js';
import XModalWindow from '@client/components/ui/modal-window.vue'; import XModalWindow from '@client/components/ui/modal-window.vue';
import MkUsersDialog from '@client/components/users-dialog.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'); Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
this.chartInstance = markRaw(new Chart(this.canvas, { this.chartInstance = new Chart(this.canvas, {
type: 'line', type: 'line',
data: { data: {
labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(), labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(),
@ -331,7 +331,7 @@ export default defineComponent({
mode: 'index', mode: 'index',
} }
} }
})); });
}, },
getDate(ago: number) { getDate(ago: number) {

View File

@ -5,13 +5,13 @@
<span>{{ $ts.domain }}</span> <span>{{ $ts.domain }}</span>
</MkInput> </MkInput>
<MkSelect v-model:value="logLevel"> <MkSelect v-model:value="logLevel">
<template #label>Level</template> <template #label>{{ $ts.level }}</template>
<option value="all">All</option> <option value="all">{{ $ts.levels.all }}</option>
<option value="info">Info</option> <option value="info">{{ $ts.levels.info }}</option>
<option value="success">Success</option> <option value="success">{{ $ts.levels.success }}</option>
<option value="warning">Warning</option> <option value="warning">{{ $ts.levels.warning }}</option>
<option value="error">Error</option> <option value="error">{{ $ts.levels.error }}</option>
<option value="debug">Debug</option> <option value="debug">{{ $ts.levels.debug }}</option>
</MkSelect> </MkSelect>
</div> </div>
@ -45,8 +45,6 @@ export default defineComponent({
MkTextarea, MkTextarea,
}, },
emits: ['info'],
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
@ -74,10 +72,6 @@ export default defineComponent({
this.fetchLogs(); this.fetchLogs();
}, },
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: { methods: {
fetchLogs() { fetchLogs() {
os.api('admin/logs', { os.api('admin/logs', {

View File

@ -27,7 +27,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, markRaw } from 'vue'; import { defineComponent } from 'vue';
import Chart from 'chart.js'; import Chart from 'chart.js';
import number from '../../filters/number'; import number from '../../filters/number';
@ -69,7 +69,7 @@ export default defineComponent({
Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg'); Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
this.chart = markRaw(new Chart(this.$refs.chart, { this.chart = new Chart(this.$refs.chart, {
type: 'line', type: 'line',
data: { data: {
labels: [], labels: [],
@ -152,7 +152,7 @@ export default defineComponent({
mode: 'index', mode: 'index',
} }
} }
})); });
this.connection.on('stats', this.onStats); this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog); this.connection.on('statsLog', this.onStatsLog);

View File

@ -55,10 +55,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account', where: 'account',
default: [] default: []
}, },
mutedAds: {
where: 'account',
default: [] as string[]
},
menu: { menu: {
where: 'deviceAccount', where: 'deviceAccount',

View File

@ -64,8 +64,11 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.wtdtxvec { .wtdtxvec {
--margin: 8px; --margin: 8px;
--panelShadow: none;
padding: 0 var(--margin); padding: 0 var(--margin);
::v-deep(._panel) {
box-shadow: none;
}
} }
</style> </style>

View File

@ -114,7 +114,6 @@ export default defineComponent({
overflow: hidden; overflow: hidden;
font-size: 0.9em; font-size: 0.9em;
color: var(--fg); color: var(--fg);
padding-right: 8px;
> .a { > .a {
display: block; display: block;
@ -130,9 +129,6 @@ export default defineComponent({
font-size: 75%; font-size: 75%;
opacity: 0.7; opacity: 0.7;
line-height: $bodyInfoHieght; line-height: $bodyInfoHieght;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
} }

View File

@ -23,17 +23,11 @@ export class Ad {
}) })
public place: string; public place: string;
// 今は使われていないが将来的に活用される可能性はある
@Column('varchar', { @Column('varchar', {
length: 32, nullable: false length: 32, nullable: false
}) })
public priority: string; public priority: string;
@Column('integer', {
default: 1, nullable: false
})
public ratio: number;
@Column('varchar', { @Column('varchar', {
length: 1024, nullable: false length: 1024, nullable: false
}) })

View File

@ -1,6 +1,7 @@
import * as httpSignature from 'http-signature'; import * as httpSignature from 'http-signature';
import config from '@/config'; import config from '@/config';
import { User } from '../models/entities/user';
import { program } from '../argv'; import { program } from '../argv';
import processDeliver from './processors/deliver'; import processDeliver from './processors/deliver';
@ -10,9 +11,14 @@ import procesObjectStorage from './processors/object-storage';
import { queueLogger } from './logger'; import { queueLogger } from './logger';
import { DriveFile } from '../models/entities/drive-file'; import { DriveFile } from '../models/entities/drive-file';
import { getJobInfo } from './get-job-info'; import { getJobInfo } from './get-job-info';
import { IActivity } from '../remote/activitypub/type';
import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues'; import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues';
import { ThinUser } from './types';
import { IActivity } from '@/remote/activitypub/type'; export type InboxJobData = {
activity: IActivity,
/** HTTP-Signature */
signature: httpSignature.IParsedSignature
};
function renderError(e: Error): any { function renderError(e: Error): any {
return { return {
@ -59,9 +65,8 @@ objectStorageQueue
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
export function deliver(user: ThinUser, content: unknown, to: string | null) { export function deliver(user: { id: User['id']; host: null; }, content: any, to: any) {
if (content == null) return null; if (content == null) return null;
if (to == null) return null;
const data = { const data = {
user, user,
@ -80,7 +85,7 @@ export function deliver(user: ThinUser, content: unknown, to: string | null) {
}); });
} }
export function inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { export function inbox(activity: any, signature: httpSignature.IParsedSignature) {
const data = { const data = {
activity: activity, activity: activity,
signature signature
@ -97,7 +102,7 @@ export function inbox(activity: IActivity, signature: httpSignature.IParsedSigna
}); });
} }
export function createDeleteDriveFilesJob(user: ThinUser) { export function createDeleteDriveFilesJob(user: { id: User['id'] }) {
return dbQueue.add('deleteDriveFiles', { return dbQueue.add('deleteDriveFiles', {
user: user user: user
}, { }, {
@ -106,7 +111,7 @@ export function createDeleteDriveFilesJob(user: ThinUser) {
}); });
} }
export function createExportNotesJob(user: ThinUser) { export function createExportNotesJob(user: { id: User['id'] }) {
return dbQueue.add('exportNotes', { return dbQueue.add('exportNotes', {
user: user user: user
}, { }, {
@ -115,7 +120,7 @@ export function createExportNotesJob(user: ThinUser) {
}); });
} }
export function createExportFollowingJob(user: ThinUser) { export function createExportFollowingJob(user: { id: User['id'] }) {
return dbQueue.add('exportFollowing', { return dbQueue.add('exportFollowing', {
user: user user: user
}, { }, {
@ -124,7 +129,7 @@ export function createExportFollowingJob(user: ThinUser) {
}); });
} }
export function createExportMuteJob(user: ThinUser) { export function createExportMuteJob(user: { id: User['id'] }) {
return dbQueue.add('exportMute', { return dbQueue.add('exportMute', {
user: user user: user
}, { }, {
@ -133,7 +138,7 @@ export function createExportMuteJob(user: ThinUser) {
}); });
} }
export function createExportBlockingJob(user: ThinUser) { export function createExportBlockingJob(user: { id: User['id'] }) {
return dbQueue.add('exportBlocking', { return dbQueue.add('exportBlocking', {
user: user user: user
}, { }, {
@ -142,7 +147,7 @@ export function createExportBlockingJob(user: ThinUser) {
}); });
} }
export function createExportUserListsJob(user: ThinUser) { export function createExportUserListsJob(user: { id: User['id'] }) {
return dbQueue.add('exportUserLists', { return dbQueue.add('exportUserLists', {
user: user user: user
}, { }, {
@ -151,7 +156,7 @@ export function createExportUserListsJob(user: ThinUser) {
}); });
} }
export function createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { export function createImportFollowingJob(user: { id: User['id'] }, fileId: DriveFile['id']) {
return dbQueue.add('importFollowing', { return dbQueue.add('importFollowing', {
user: user, user: user,
fileId: fileId fileId: fileId
@ -161,7 +166,7 @@ export function createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']
}); });
} }
export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { export function createImportUserListsJob(user: { id: User['id'] }, fileId: DriveFile['id']) {
return dbQueue.add('importUserLists', { return dbQueue.add('importUserLists', {
user: user, user: user,
fileId: fileId fileId: fileId

View File

@ -1,8 +1,8 @@
import * as Bull from 'bull'; import * as Queue from 'bull';
import config from '@/config'; import config from '@/config';
export function initialize<T>(name: string, limitPerSec = -1) { export function initialize(name: string, limitPerSec = -1) {
return new Bull<T>(name, { return new Queue(name, {
redis: { redis: {
port: config.redis.port, port: config.redis.port,
host: config.redis.host, host: config.redis.host,

View File

@ -4,11 +4,10 @@ import { queueLogger } from '../../logger';
import { deleteFileSync } from '../../../services/drive/delete-file'; import { deleteFileSync } from '../../../services/drive/delete-file';
import { Users, DriveFiles } from '../../../models'; import { Users, DriveFiles } from '../../../models';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
import { DbUserJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('delete-drive-files'); const logger = queueLogger.createSubLogger('delete-drive-files');
export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> {
logger.info(`Deleting drive files of ${job.data.user.id} ...`); logger.info(`Deleting drive files of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);

View File

@ -8,11 +8,10 @@ import dateFormat = require('dateformat');
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, Blockings } from '../../../models'; import { Users, Blockings } from '../../../models';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
import { DbUserJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('export-blocking'); const logger = queueLogger.createSubLogger('export-blocking');
export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting blocking of ${job.data.user.id} ...`); logger.info(`Exporting blocking of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);
@ -62,7 +61,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
} }
const content = getFullApAccount(u.username, u.host); const content = getFullApAccount(u.username, u.host);
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {
if (err) { if (err) {
logger.error(err); logger.error(err);

View File

@ -8,11 +8,10 @@ import dateFormat = require('dateformat');
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, Followings } from '../../../models'; import { Users, Followings } from '../../../models';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
import { DbUserJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('export-following'); const logger = queueLogger.createSubLogger('export-following');
export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting following of ${job.data.user.id} ...`); logger.info(`Exporting following of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);
@ -62,7 +61,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: any):
} }
const content = getFullApAccount(u.username, u.host); const content = getFullApAccount(u.username, u.host);
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {
if (err) { if (err) {
logger.error(err); logger.error(err);

View File

@ -8,11 +8,10 @@ import dateFormat = require('dateformat');
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, Mutings } from '../../../models'; import { Users, Mutings } from '../../../models';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
import { DbUserJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('export-mute'); const logger = queueLogger.createSubLogger('export-mute');
export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { export async function exportMute(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting mute of ${job.data.user.id} ...`); logger.info(`Exporting mute of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);
@ -62,7 +61,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
} }
const content = getFullApAccount(u.username, u.host); const content = getFullApAccount(u.username, u.host);
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {
if (err) { if (err) {
logger.error(err); logger.error(err);

View File

@ -9,11 +9,10 @@ import { Users, Notes, Polls } from '../../../models';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
import { Note } from '../../../models/entities/note'; import { Note } from '../../../models/entities/note';
import { Poll } from '../../../models/entities/poll'; import { Poll } from '../../../models/entities/poll';
import { DbUserJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('export-notes'); const logger = queueLogger.createSubLogger('export-notes');
export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting notes of ${job.data.user.id} ...`); logger.info(`Exporting notes of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);
@ -34,7 +33,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
const stream = fs.createWriteStream(path, { flags: 'a' }); const stream = fs.createWriteStream(path, { flags: 'a' });
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write('[', err => { stream.write('[', err => {
if (err) { if (err) {
logger.error(err); logger.error(err);
@ -73,7 +72,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
poll = await Polls.findOneOrFail({ noteId: note.id }); poll = await Polls.findOneOrFail({ noteId: note.id });
} }
const content = JSON.stringify(serialize(note, poll)); const content = JSON.stringify(serialize(note, poll));
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write(exportedNotesCount === 0 ? content : ',\n' + content, err => { stream.write(exportedNotesCount === 0 ? content : ',\n' + content, err => {
if (err) { if (err) {
logger.error(err); logger.error(err);
@ -93,7 +92,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
job.progress(exportedNotesCount / total); job.progress(exportedNotesCount / total);
} }
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write(']', err => { stream.write(']', err => {
if (err) { if (err) {
logger.error(err); logger.error(err);

View File

@ -8,11 +8,10 @@ import dateFormat = require('dateformat');
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, UserLists, UserListJoinings } from '../../../models'; import { Users, UserLists, UserListJoinings } from '../../../models';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DbUserJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('export-user-lists'); const logger = queueLogger.createSubLogger('export-user-lists');
export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting user lists of ${job.data.user.id} ...`); logger.info(`Exporting user lists of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);
@ -46,7 +45,7 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any):
for (const u of users) { for (const u of users) {
const acct = getFullApAccount(u.username, u.host); const acct = getFullApAccount(u.username, u.host);
const content = `${list.name},${acct}`; const content = `${list.name},${acct}`;
await new Promise<void>((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {
if (err) { if (err) {
logger.error(err); logger.error(err);

View File

@ -7,11 +7,10 @@ import { resolveUser } from '../../../remote/resolve-user';
import { downloadTextFile } from '@/misc/download-text-file'; import { downloadTextFile } from '@/misc/download-text-file';
import { isSelfHost, toPuny } from '@/misc/convert-host'; import { isSelfHost, toPuny } from '@/misc/convert-host';
import { Users, DriveFiles } from '../../../models'; import { Users, DriveFiles } from '../../../models';
import { DbUserImportJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('import-following'); const logger = queueLogger.createSubLogger('import-following');
export async function importFollowing(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
logger.info(`Importing following of ${job.data.user.id} ...`); logger.info(`Importing following of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);

View File

@ -8,11 +8,10 @@ import { downloadTextFile } from '@/misc/download-text-file';
import { isSelfHost, toPuny } from '@/misc/convert-host'; import { isSelfHost, toPuny } from '@/misc/convert-host';
import { DriveFiles, Users, UserLists, UserListJoinings } from '../../../models'; import { DriveFiles, Users, UserLists, UserListJoinings } from '../../../models';
import { genId } from '@/misc/gen-id'; import { genId } from '@/misc/gen-id';
import { DbUserImportJobData } from '@/queue/types';
const logger = queueLogger.createSubLogger('import-user-lists'); const logger = queueLogger.createSubLogger('import-user-lists');
export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { export async function importUserLists(job: Bull.Job, done: any): Promise<void> {
logger.info(`Importing user lists of ${job.data.user.id} ...`); logger.info(`Importing user lists of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id); const user = await Users.findOne(job.data.user.id);

View File

@ -1,5 +1,4 @@
import * as Bull from 'bull'; import * as Bull from 'bull';
import { DbJobData } from '@/queue/types';
import { deleteDriveFiles } from './delete-drive-files'; import { deleteDriveFiles } from './delete-drive-files';
import { exportNotes } from './export-notes'; import { exportNotes } from './export-notes';
import { exportFollowing } from './export-following'; import { exportFollowing } from './export-following';
@ -18,10 +17,10 @@ const jobs = {
exportUserLists, exportUserLists,
importFollowing, importFollowing,
importUserLists importUserLists
} as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; } as any;
export default function(dbQueue: Bull.Queue<DbJobData>) { export default function(dbQueue: Bull.Queue) {
for (const [k, v] of Object.entries(jobs)) { for (const [k, v] of Object.entries(jobs)) {
dbQueue.process(k, v); dbQueue.process(k, v as any);
} }
} }

View File

@ -10,7 +10,6 @@ import { fetchMeta } from '@/misc/fetch-meta';
import { toPuny } from '@/misc/convert-host'; import { toPuny } from '@/misc/convert-host';
import { Cache } from '@/misc/cache'; import { Cache } from '@/misc/cache';
import { Instance } from '../../models/entities/instance'; import { Instance } from '../../models/entities/instance';
import { DeliverJobData } from '../types';
const logger = new Logger('deliver'); const logger = new Logger('deliver');
@ -18,7 +17,7 @@ let latest: string | null = null;
const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60); const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
export default async (job: Bull.Job<DeliverJobData>) => { export default async (job: Bull.Job) => {
const { host } = new URL(job.data.to); const { host } = new URL(job.data.to);
// ブロックしてたら中断 // ブロックしてたら中断

View File

@ -10,7 +10,7 @@ import { fetchMeta } from '@/misc/fetch-meta';
import { toPuny, extractDbHost } from '@/misc/convert-host'; import { toPuny, extractDbHost } from '@/misc/convert-host';
import { getApId } from '../../remote/activitypub/type'; import { getApId } from '../../remote/activitypub/type';
import { fetchInstanceMetadata } from '../../services/fetch-instance-metadata'; import { fetchInstanceMetadata } from '../../services/fetch-instance-metadata';
import { InboxJobData } from '../types'; import { InboxJobData } from '..';
import DbResolver from '../../remote/activitypub/db-resolver'; import DbResolver from '../../remote/activitypub/db-resolver';
import { resolvePerson } from '../../remote/activitypub/models/person'; import { resolvePerson } from '../../remote/activitypub/models/person';
import { LdSignature } from '../../remote/activitypub/misc/ld-signature'; 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; const activity = job.data.activity;
//#region Log //#region Log
const info = Object.assign({}, activity) as any; const info = Object.assign({}, activity);
delete info['@context']; delete info['@context'];
logger.debug(JSON.stringify(info, null, 2)); logger.debug(JSON.stringify(info, null, 2));
//#endregion //#endregion

View File

@ -7,7 +7,7 @@ import { MoreThan, Not, IsNull } from 'typeorm';
const logger = queueLogger.createSubLogger('clean-remote-files'); 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...`); logger.info(`Deleting cached remote files...`);
let deletedCount = 0; let deletedCount = 0;

View File

@ -1,8 +1,7 @@
import { ObjectStorageFileJobData } from '@/queue/types';
import * as Bull from 'bull'; import * as Bull from 'bull';
import { deleteObjectStorageFile } from '../../../services/drive/delete-file'; import { deleteObjectStorageFile } from '../../../services/drive/delete-file';
export default async (job: Bull.Job<ObjectStorageFileJobData>) => { export default async (job: Bull.Job) => {
const key: string = job.data.key; const key: string = job.data.key;
await deleteObjectStorageFile(key); await deleteObjectStorageFile(key);

View File

@ -1,15 +1,14 @@
import * as Bull from 'bull'; import * as Bull from 'bull';
import { ObjectStorageJobData } from '@/queue/types';
import deleteFile from './delete-file'; import deleteFile from './delete-file';
import cleanRemoteFiles from './clean-remote-files'; import cleanRemoteFiles from './clean-remote-files';
const jobs = { const jobs = {
deleteFile, deleteFile,
cleanRemoteFiles, cleanRemoteFiles,
} as Record<string, Bull.ProcessCallbackFunction<ObjectStorageJobData> | Bull.ProcessPromiseFunction<ObjectStorageJobData>>; } as any;
export default function(q: Bull.Queue) { export default function(q: Bull.Queue) {
for (const [k, v] of Object.entries(jobs)) { for (const [k, v] of Object.entries(jobs)) {
q.process(k, 16, v); q.process(k, 16, v as any);
} }
} }

View File

@ -1,8 +1,7 @@
import config from '@/config'; import config from '@/config';
import { initialize as initializeQueue } from './initialize'; import { initialize as initializeQueue } from './initialize';
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types';
export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.deliverJobPerSec || 128); export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128);
export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16); export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue<DbJobData>('db'); export const dbQueue = initializeQueue('db');
export const objectStorageQueue = initializeQueue<ObjectStorageJobData>('objectStorage'); export const objectStorageQueue = initializeQueue('objectStorage');

View File

@ -1,39 +0,0 @@
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'];
};

View File

@ -11,11 +11,6 @@ export default async (actor: IRemoteUser, activity: ILike) => {
await extractEmojis(activity.tag || [], actor.host).catch(() => null); await extractEmojis(activity.tag || [], actor.host).catch(() => null);
return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => { await create(actor, note, activity._misskey_reaction || activity.content || activity.name);
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { return `ok`;
return 'skip: already reacted';
} else {
throw e;
}
}).then(() => 'ok');
}; };

View File

@ -22,9 +22,6 @@ export const meta = {
priority: { priority: {
validator: $.str validator: $.str
}, },
ratio: {
validator: $.num.int().min(0)
},
expiresAt: { expiresAt: {
validator: $.num.int() validator: $.num.int()
}, },
@ -42,7 +39,6 @@ export default define(meta, async (ps) => {
url: ps.url, url: ps.url,
imageUrl: ps.imageUrl, imageUrl: ps.imageUrl,
priority: ps.priority, priority: ps.priority,
ratio: ps.ratio,
place: ps.place, place: ps.place,
memo: ps.memo, memo: ps.memo,
}); });

View File

@ -29,9 +29,6 @@ export const meta = {
priority: { priority: {
validator: $.str validator: $.str
}, },
ratio: {
validator: $.num.int().min(0)
},
expiresAt: { expiresAt: {
validator: $.num.int() validator: $.num.int()
}, },
@ -55,7 +52,6 @@ export default define(meta, async (ps, me) => {
url: ps.url, url: ps.url,
place: ps.place, place: ps.place,
priority: ps.priority, priority: ps.priority,
ratio: ps.ratio,
memo: ps.memo, memo: ps.memo,
imageUrl: ps.imageUrl, imageUrl: ps.imageUrl,
expiresAt: new Date(ps.expiresAt), expiresAt: new Date(ps.expiresAt),

View File

@ -1,26 +0,0 @@
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;
});

View File

@ -509,10 +509,9 @@ export default define(meta, async (ps, me) => {
maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH), maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
emojis: await Emojis.packMany(emojis), emojis: await Emojis.packMany(emojis),
ads: ads.map(ad => ({ ads: ads.map(ad => ({
id: ad.id,
url: ad.url, url: ad.url,
place: ad.place, place: ad.place,
ratio: ad.ratio, priority: ad.priority,
imageUrl: ad.imageUrl, imageUrl: ad.imageUrl,
})), })),
enableEmail: instance.enableEmail, enableEmail: instance.enableEmail,

View File

@ -13,13 +13,12 @@ import { createNotification } from '../../create-notification';
import deleteReaction from './delete'; import deleteReaction from './delete';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error';
import { NoteReaction } from '../../../models/entities/note-reaction'; 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) => { export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => {
// TODO: cache // TODO: cache
reaction = await toDbReaction(reaction, user.host); reaction = await toDbReaction(reaction, user.host);
const record: NoteReaction = { let record: NoteReaction = {
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
noteId: note.id, noteId: note.id,
@ -32,18 +31,17 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
await NoteReactions.insert(record); await NoteReactions.insert(record);
} catch (e) { } catch (e) {
if (isDuplicateKeyValueError(e)) { if (isDuplicateKeyValueError(e)) {
const exists = await NoteReactions.findOneOrFail({ record = await NoteReactions.findOneOrFail({
noteId: note.id, noteId: note.id,
userId: user.id, userId: user.id,
}); });
if (exists.reaction !== reaction) { if (record.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える // 別のリアクションがすでにされていたら置き換える
await deleteReaction(user, note); await deleteReaction(user, note);
await NoteReactions.insert(record);
} else { } else {
// 同じリアクションがすでにされていたらエラー // 同じリアクションがすでにされていたら何もしない
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); return;
} }
} else { } else {
throw e; throw e;