mirror of
https://iceshrimp.dev/iceshrimp/iceshrimp
synced 2024-12-25 20:18:05 +09:00
Merge branch 'develop'
This commit is contained in:
commit
182cac6bce
@ -1,11 +1,13 @@
|
|||||||
---
|
---
|
||||||
_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!"
|
||||||
@ -72,6 +74,7 @@ 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"
|
||||||
@ -112,16 +115,24 @@ 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"
|
||||||
@ -139,6 +150,8 @@ 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íť"
|
||||||
@ -204,8 +217,12 @@ 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"
|
||||||
@ -319,6 +336,10 @@ 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"
|
||||||
@ -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."
|
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"
|
||||||
@ -371,6 +398,7 @@ 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ěť"
|
||||||
@ -416,6 +444,8 @@ _timelines:
|
|||||||
_rooms:
|
_rooms:
|
||||||
_roomType:
|
_roomType:
|
||||||
default: "Výchozí"
|
default: "Výchozí"
|
||||||
|
_furnitures:
|
||||||
|
monitor: "Monitorovat"
|
||||||
_pages:
|
_pages:
|
||||||
blocks:
|
blocks:
|
||||||
image: "Obrázky"
|
image: "Obrázky"
|
||||||
@ -438,6 +468,7 @@ _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í"
|
||||||
|
@ -754,6 +754,10 @@ 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."
|
||||||
|
@ -756,6 +756,10 @@ 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."
|
||||||
|
@ -667,6 +667,8 @@ 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:
|
||||||
|
@ -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: "Signalements"
|
reportAbuse: "Signaler"
|
||||||
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,12 +750,16 @@ 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: "Mémo"
|
memo: "Pense-bête"
|
||||||
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."
|
||||||
|
@ -7,6 +7,7 @@ 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!"
|
||||||
@ -589,10 +590,13 @@ 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: "Segnala"
|
abuseReports: "Segnalazioni"
|
||||||
reportAbuse: "Segnala"
|
reportAbuse: "Segnalazioni"
|
||||||
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"
|
||||||
@ -704,6 +708,7 @@ 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"
|
||||||
@ -724,8 +729,22 @@ 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"
|
||||||
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:
|
_gallery:
|
||||||
my: "Le mie pubblicazioni"
|
my: "Le mie pubblicazioni"
|
||||||
liked: "Pubblicazioni che mi piacciono"
|
liked: "Pubblicazioni che mi piacciono"
|
||||||
|
@ -756,6 +756,11 @@ high: "高"
|
|||||||
middle: "中"
|
middle: "中"
|
||||||
low: "低"
|
low: "低"
|
||||||
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
|
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
|
||||||
|
ratio: "比率"
|
||||||
|
|
||||||
|
_ad:
|
||||||
|
back: "戻る"
|
||||||
|
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
|
||||||
|
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"
|
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"
|
||||||
|
@ -649,6 +649,8 @@ memo: "メモ"
|
|||||||
high: "高い"
|
high: "高い"
|
||||||
middle: "中"
|
middle: "中"
|
||||||
low: "低い"
|
low: "低い"
|
||||||
|
_ad:
|
||||||
|
back: "戻る"
|
||||||
_gallery:
|
_gallery:
|
||||||
unlike: "良くないわ"
|
unlike: "良くないわ"
|
||||||
_email:
|
_email:
|
||||||
|
@ -7,6 +7,7 @@ search: "검색"
|
|||||||
notifications: "알림"
|
notifications: "알림"
|
||||||
username: "유저명"
|
username: "유저명"
|
||||||
password: "비밀번호"
|
password: "비밀번호"
|
||||||
|
forgotPassword: "비밀번호 재설정"
|
||||||
fetchingAsApObject: "연합에서 조회 중"
|
fetchingAsApObject: "연합에서 조회 중"
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "알겠어요"
|
gotIt: "알겠어요"
|
||||||
@ -747,8 +748,22 @@ gallery: "갤러리"
|
|||||||
recentPosts: "최근 포스트"
|
recentPosts: "최근 포스트"
|
||||||
popularPosts: "인기 포스트"
|
popularPosts: "인기 포스트"
|
||||||
shareWithNote: "노트로 공유"
|
shareWithNote: "노트로 공유"
|
||||||
expiration: "투표 기한"
|
ads: "광고"
|
||||||
|
expiration: "기한"
|
||||||
|
memo: "메모"
|
||||||
|
priority: "우선순위"
|
||||||
|
high: "높음"
|
||||||
middle: "보통"
|
middle: "보통"
|
||||||
|
low: "낮음"
|
||||||
|
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
|
||||||
|
ratio: "비율"
|
||||||
|
_ad:
|
||||||
|
back: "뒤로"
|
||||||
|
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
|
||||||
|
_forgotPassword:
|
||||||
|
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
|
||||||
|
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
|
||||||
|
contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오."
|
||||||
_gallery:
|
_gallery:
|
||||||
my: "내 갤러리"
|
my: "내 갤러리"
|
||||||
liked: "좋아요 한 갤러리"
|
liked: "좋아요 한 갤러리"
|
||||||
|
@ -649,6 +649,8 @@ 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:
|
||||||
|
@ -749,6 +749,8 @@ popularPosts: "Популярные публикации"
|
|||||||
shareWithNote: "Поделиться заметкой"
|
shareWithNote: "Поделиться заметкой"
|
||||||
expiration: "Опрос длится"
|
expiration: "Опрос длится"
|
||||||
middle: "Средне"
|
middle: "Средне"
|
||||||
|
_ad:
|
||||||
|
back: "Выход"
|
||||||
_gallery:
|
_gallery:
|
||||||
my: "Личная"
|
my: "Личная"
|
||||||
liked: "Понравившееся"
|
liked: "Понравившееся"
|
||||||
|
@ -691,6 +691,8 @@ user: "Користувачі"
|
|||||||
administration: "Управління"
|
administration: "Управління"
|
||||||
expiration: "Опитування закінчується"
|
expiration: "Опитування закінчується"
|
||||||
middle: "Середній"
|
middle: "Середній"
|
||||||
|
_ad:
|
||||||
|
back: "Назад"
|
||||||
_gallery:
|
_gallery:
|
||||||
unlike: "Не вподобати"
|
unlike: "Не вподобати"
|
||||||
_email:
|
_email:
|
||||||
|
@ -7,6 +7,7 @@ search: "搜索"
|
|||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
username: "用户名"
|
username: "用户名"
|
||||||
password: "密码"
|
password: "密码"
|
||||||
|
forgotPassword: "忘记密码"
|
||||||
fetchingAsApObject: "联合查询中"
|
fetchingAsApObject: "联合查询中"
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "我明白了"
|
gotIt: "我明白了"
|
||||||
@ -173,7 +174,7 @@ metadata: "元数据"
|
|||||||
withNFiles: "{n}个文件"
|
withNFiles: "{n}个文件"
|
||||||
monitor: "监视器"
|
monitor: "监视器"
|
||||||
jobQueue: "作业队列"
|
jobQueue: "作业队列"
|
||||||
cpuAndMemory: "CPU使用量"
|
cpuAndMemory: "CPU和内存"
|
||||||
network: "网络"
|
network: "网络"
|
||||||
disk: "存储"
|
disk: "存储"
|
||||||
instanceInfo: "实例信息"
|
instanceInfo: "实例信息"
|
||||||
@ -747,8 +748,22 @@ 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: "喜欢的图片"
|
||||||
|
@ -733,6 +733,8 @@ noBotProtectionWarning: "尚未設定Bot防護。"
|
|||||||
configure: "設定"
|
configure: "設定"
|
||||||
expiration: "期限"
|
expiration: "期限"
|
||||||
middle: "中"
|
middle: "中"
|
||||||
|
_ad:
|
||||||
|
back: "返回"
|
||||||
_gallery:
|
_gallery:
|
||||||
unlike: "收回喜歡"
|
unlike: "收回喜歡"
|
||||||
_email:
|
_email:
|
||||||
|
14
migration/1620364649428-ad2.ts
Normal file
14
migration/1620364649428-ad2.ts
Normal file
@ -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"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||||
"version": "12.80.3",
|
"version": "12.81.0",
|
||||||
"codename": "indigo",
|
"codename": "indigo",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
<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>{{ $ts.stopThisAd }}</MkButton>-->
|
<!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>-->
|
||||||
<button class="_textButton" @click="toggleMenu">{{ $ts.close }}</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -19,9 +20,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { instance } from '@client/instance';
|
import { Instance, 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: {
|
||||||
@ -45,35 +48,65 @@ export default defineComponent({
|
|||||||
showMenu.value = !showMenu.value;
|
showMenu.value = !showMenu.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
let ad = null;
|
const choseAd = (): Instance['ads'][number] | null => {
|
||||||
|
if (props.specify) {
|
||||||
|
return props.specify as Instance['ads'][number];
|
||||||
|
}
|
||||||
|
|
||||||
if (props.specify) {
|
const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? {
|
||||||
ad = props.specify;
|
...ad,
|
||||||
} else {
|
ratio: 0
|
||||||
let ads = instance.ads.filter(ad => props.prefer.includes(ad.place));
|
} : ad);
|
||||||
|
|
||||||
|
let ads = allAds.filter(ad => props.prefer.includes(ad.place));
|
||||||
|
|
||||||
if (ads.length === 0) {
|
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 lowPriorityAds = ads.filter(ad => ad.ratio === 0);
|
||||||
const middle = ads.filter(ad => ad.priority === 'middle');
|
ads = ads.filter(ad => ad.ratio !== 0);
|
||||||
const low = ads.filter(ad => ad.priority === 'low');
|
|
||||||
|
|
||||||
if (high.length > 0) {
|
if (ads.length === 0) {
|
||||||
ad = high[Math.floor(Math.random() * high.length)];
|
if (lowPriorityAds.length !== 0) {
|
||||||
} else if (middle.length > 0) {
|
return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)];
|
||||||
ad = middle[Math.floor(Math.random() * middle.length)];
|
} else {
|
||||||
} else if (low.length > 0) {
|
return null;
|
||||||
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,
|
ad: chosen,
|
||||||
showMenu,
|
showMenu,
|
||||||
toggleMenu,
|
toggleMenu,
|
||||||
host,
|
host,
|
||||||
|
reduceFrequency,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -157,6 +190,10 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
</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"
|
||||||
@ -18,6 +17,7 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
@ -226,12 +226,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
.mk-modal {
|
.mk-modal {
|
||||||
> .bg {
|
> .bg {
|
||||||
z-index: 10000;
|
z-index: 20000;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .content:not(.popup) {
|
> .content:not(.popup) {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10000;
|
z-index: 20000;
|
||||||
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: 10000;
|
z-index: 20000;
|
||||||
|
|
||||||
&.fixed {
|
&.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -3,10 +3,17 @@ import { api } from './os';
|
|||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
|
||||||
type Instance = {
|
export 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');
|
||||||
|
@ -15,12 +15,17 @@
|
|||||||
<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>
|
||||||
@ -82,6 +87,7 @@ 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,
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
<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>
|
||||||
@ -105,6 +106,7 @@ 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'));
|
||||||
|
@ -123,7 +123,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, markRaw } 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 = new Chart(this.canvas, {
|
this.chartInstance = markRaw(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) {
|
||||||
|
@ -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>{{ $ts.level }}</template>
|
<template #label>Level</template>
|
||||||
<option value="all">{{ $ts.levels.all }}</option>
|
<option value="all">All</option>
|
||||||
<option value="info">{{ $ts.levels.info }}</option>
|
<option value="info">Info</option>
|
||||||
<option value="success">{{ $ts.levels.success }}</option>
|
<option value="success">Success</option>
|
||||||
<option value="warning">{{ $ts.levels.warning }}</option>
|
<option value="warning">Warning</option>
|
||||||
<option value="error">{{ $ts.levels.error }}</option>
|
<option value="error">Error</option>
|
||||||
<option value="debug">{{ $ts.levels.debug }}</option>
|
<option value="debug">Debug</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -45,6 +45,8 @@ export default defineComponent({
|
|||||||
MkTextarea,
|
MkTextarea,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emits: ['info'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
[symbols.PAGE_INFO]: {
|
[symbols.PAGE_INFO]: {
|
||||||
@ -72,6 +74,10 @@ 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', {
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, markRaw } 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 = new Chart(this.$refs.chart, {
|
this.chart = markRaw(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);
|
||||||
|
@ -55,6 +55,10 @@ 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',
|
||||||
|
@ -64,11 +64,8 @@ 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>
|
||||||
|
@ -114,6 +114,7 @@ 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;
|
||||||
@ -129,6 +130,9 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,17 @@ 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
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
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';
|
||||||
@ -11,14 +10,9 @@ 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';
|
||||||
export type InboxJobData = {
|
import { IActivity } from '@/remote/activitypub/type';
|
||||||
activity: IActivity,
|
|
||||||
/** HTTP-Signature */
|
|
||||||
signature: httpSignature.IParsedSignature
|
|
||||||
};
|
|
||||||
|
|
||||||
function renderError(e: Error): any {
|
function renderError(e: Error): any {
|
||||||
return {
|
return {
|
||||||
@ -65,8 +59,9 @@ 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: { 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 (content == null) return null;
|
||||||
|
if (to == null) return null;
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
user,
|
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 = {
|
const data = {
|
||||||
activity: activity,
|
activity: activity,
|
||||||
signature
|
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', {
|
return dbQueue.add('deleteDriveFiles', {
|
||||||
user: user
|
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', {
|
return dbQueue.add('exportNotes', {
|
||||||
user: user
|
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', {
|
return dbQueue.add('exportFollowing', {
|
||||||
user: user
|
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', {
|
return dbQueue.add('exportMute', {
|
||||||
user: user
|
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', {
|
return dbQueue.add('exportBlocking', {
|
||||||
user: user
|
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', {
|
return dbQueue.add('exportUserLists', {
|
||||||
user: user
|
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', {
|
return dbQueue.add('importFollowing', {
|
||||||
user: user,
|
user: user,
|
||||||
fileId: fileId
|
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', {
|
return dbQueue.add('importUserLists', {
|
||||||
user: user,
|
user: user,
|
||||||
fileId: fileId
|
fileId: fileId
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import * as Queue from 'bull';
|
import * as Bull from 'bull';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
|
||||||
export function initialize(name: string, limitPerSec = -1) {
|
export function initialize<T>(name: string, limitPerSec = -1) {
|
||||||
return new Queue(name, {
|
return new Bull<T>(name, {
|
||||||
redis: {
|
redis: {
|
||||||
port: config.redis.port,
|
port: config.redis.port,
|
||||||
host: config.redis.host,
|
host: config.redis.host,
|
||||||
|
@ -4,10 +4,11 @@ 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, 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} ...`);
|
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);
|
||||||
|
@ -8,10 +8,11 @@ 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, 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} ...`);
|
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);
|
||||||
@ -61,7 +62,7 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = getFullApAccount(u.username, u.host);
|
const content = getFullApAccount(u.username, u.host);
|
||||||
await new Promise((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -8,10 +8,11 @@ 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, 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} ...`);
|
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);
|
||||||
@ -61,7 +62,7 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = getFullApAccount(u.username, u.host);
|
const content = getFullApAccount(u.username, u.host);
|
||||||
await new Promise((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -8,10 +8,11 @@ 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, 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} ...`);
|
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);
|
||||||
@ -61,7 +62,7 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = getFullApAccount(u.username, u.host);
|
const content = getFullApAccount(u.username, u.host);
|
||||||
await new Promise((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -9,10 +9,11 @@ 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, 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} ...`);
|
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);
|
||||||
@ -33,7 +34,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
|
|||||||
|
|
||||||
const stream = fs.createWriteStream(path, { flags: 'a' });
|
const stream = fs.createWriteStream(path, { flags: 'a' });
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write('[', err => {
|
stream.write('[', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(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 });
|
poll = await Polls.findOneOrFail({ noteId: note.id });
|
||||||
}
|
}
|
||||||
const content = JSON.stringify(serialize(note, poll));
|
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 => {
|
stream.write(exportedNotesCount === 0 ? content : ',\n' + content, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
@ -92,7 +93,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
|
|||||||
job.progress(exportedNotesCount / total);
|
job.progress(exportedNotesCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(']', err => {
|
stream.write(']', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -8,10 +8,11 @@ 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, 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} ...`);
|
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);
|
||||||
@ -45,7 +46,7 @@ export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
|
|||||||
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((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -7,10 +7,11 @@ 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, 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} ...`);
|
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);
|
||||||
|
@ -8,10 +8,11 @@ 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, 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} ...`);
|
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);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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';
|
||||||
@ -17,10 +18,10 @@ const jobs = {
|
|||||||
exportUserLists,
|
exportUserLists,
|
||||||
importFollowing,
|
importFollowing,
|
||||||
importUserLists
|
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)) {
|
for (const [k, v] of Object.entries(jobs)) {
|
||||||
dbQueue.process(k, v as any);
|
dbQueue.process(k, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ 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');
|
||||||
|
|
||||||
@ -17,7 +18,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) => {
|
export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
const { host } = new URL(job.data.to);
|
const { host } = new URL(job.data.to);
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
|
@ -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 '..';
|
import { InboxJobData } from '../types';
|
||||||
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);
|
const info = Object.assign({}, activity) as any;
|
||||||
delete info['@context'];
|
delete info['@context'];
|
||||||
logger.debug(JSON.stringify(info, null, 2));
|
logger.debug(JSON.stringify(info, null, 2));
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -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;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
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) => {
|
export default async (job: Bull.Job<ObjectStorageFileJobData>) => {
|
||||||
const key: string = job.data.key;
|
const key: string = job.data.key;
|
||||||
|
|
||||||
await deleteObjectStorageFile(key);
|
await deleteObjectStorageFile(key);
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
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 any;
|
} as Record<string, Bull.ProcessCallbackFunction<ObjectStorageJobData> | Bull.ProcessPromiseFunction<ObjectStorageJobData>>;
|
||||||
|
|
||||||
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 as any);
|
q.process(k, 16, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
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('deliver', config.deliverJobPerSec || 128);
|
export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.deliverJobPerSec || 128);
|
||||||
export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16);
|
export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16);
|
||||||
export const dbQueue = initializeQueue('db');
|
export const dbQueue = initializeQueue<DbJobData>('db');
|
||||||
export const objectStorageQueue = initializeQueue('objectStorage');
|
export const objectStorageQueue = initializeQueue<ObjectStorageJobData>('objectStorage');
|
||||||
|
39
src/queue/types.ts
Normal file
39
src/queue/types.ts
Normal file
@ -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'];
|
||||||
|
};
|
@ -11,6 +11,11 @@ export default async (actor: IRemoteUser, activity: ILike) => {
|
|||||||
|
|
||||||
await extractEmojis(activity.tag || [], actor.host).catch(() => null);
|
await extractEmojis(activity.tag || [], actor.host).catch(() => null);
|
||||||
|
|
||||||
await create(actor, note, activity._misskey_reaction || activity.content || activity.name);
|
return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => {
|
||||||
return `ok`;
|
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') {
|
||||||
|
return 'skip: already reacted';
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}).then(() => 'ok');
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,9 @@ export const meta = {
|
|||||||
priority: {
|
priority: {
|
||||||
validator: $.str
|
validator: $.str
|
||||||
},
|
},
|
||||||
|
ratio: {
|
||||||
|
validator: $.num.int().min(0)
|
||||||
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
validator: $.num.int()
|
validator: $.num.int()
|
||||||
},
|
},
|
||||||
@ -39,6 +42,7 @@ 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,
|
||||||
});
|
});
|
||||||
|
@ -29,6 +29,9 @@ export const meta = {
|
|||||||
priority: {
|
priority: {
|
||||||
validator: $.str
|
validator: $.str
|
||||||
},
|
},
|
||||||
|
ratio: {
|
||||||
|
validator: $.num.int().min(0)
|
||||||
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
validator: $.num.int()
|
validator: $.num.int()
|
||||||
},
|
},
|
||||||
@ -52,6 +55,7 @@ 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),
|
||||||
|
26
src/server/api/endpoints/admin/get-index-stats.ts
Normal file
26
src/server/api/endpoints/admin/get-index-stats.ts
Normal file
@ -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;
|
||||||
|
});
|
@ -509,9 +509,10 @@ 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,
|
||||||
priority: ad.priority,
|
ratio: ad.ratio,
|
||||||
imageUrl: ad.imageUrl,
|
imageUrl: ad.imageUrl,
|
||||||
})),
|
})),
|
||||||
enableEmail: instance.enableEmail,
|
enableEmail: instance.enableEmail,
|
||||||
|
@ -13,12 +13,13 @@ 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);
|
||||||
|
|
||||||
let record: NoteReaction = {
|
const record: NoteReaction = {
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
@ -31,17 +32,18 @@ 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)) {
|
||||||
record = await NoteReactions.findOneOrFail({
|
const exists = await NoteReactions.findOneOrFail({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (record.reaction !== reaction) {
|
if (exists.reaction !== reaction) {
|
||||||
// 別のリアクションがすでにされていたら置き換える
|
// 別のリアクションがすでにされていたら置き換える
|
||||||
await deleteReaction(user, note);
|
await deleteReaction(user, note);
|
||||||
|
await NoteReactions.insert(record);
|
||||||
} else {
|
} else {
|
||||||
// 同じリアクションがすでにされていたら何もしない
|
// 同じリアクションがすでにされていたらエラー
|
||||||
return;
|
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
|
Loading…
Reference in New Issue
Block a user