forked from FoundKeyGang/FoundKey
resolve merge conflicts
This commit is contained in:
commit
7e902c8b9d
82 changed files with 585 additions and 995 deletions
2
.mailmap
2
.mailmap
|
@ -6,7 +6,7 @@ Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast+git@toast.cafe>
|
||||||
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast@toast.cafe>
|
Chloe Kudryavtsev <code@toast.bunkerlabs.net> <toast@toast.cafe>
|
||||||
Dr. Gutfuck LLC <40531868+gutfuckllc@users.noreply.github.com>
|
Dr. Gutfuck LLC <40531868+gutfuckllc@users.noreply.github.com>
|
||||||
Ehsan Javadynia <31900907+ehsanjavadynia@users.noreply.github.com> <ehsan.javadynia@gmail.com>
|
Ehsan Javadynia <31900907+ehsanjavadynia@users.noreply.github.com> <ehsan.javadynia@gmail.com>
|
||||||
Francis Dinh <normandy@biribiri.dev>
|
Norm <normandy@biribiri.dev>
|
||||||
Hakaba Hitoyo <tsukadayoshio@gmail.com> Hakaba Hitoyo <example@example.com>
|
Hakaba Hitoyo <tsukadayoshio@gmail.com> Hakaba Hitoyo <example@example.com>
|
||||||
Johann150 <johann.galle@protonmail.com> <johann@qwertqwefsday.eu>
|
Johann150 <johann.galle@protonmail.com> <johann@qwertqwefsday.eu>
|
||||||
Michcio <public+git@meekchopp.es> <michcio@noreply.akkoma>
|
Michcio <public+git@meekchopp.es> <michcio@noreply.akkoma>
|
||||||
|
|
|
@ -9,7 +9,7 @@ services:
|
||||||
- redis
|
- redis
|
||||||
# - es
|
# - es
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "127.0.0.1:3000:3000"
|
||||||
networks:
|
networks:
|
||||||
- internal_network
|
- internal_network
|
||||||
- external_network
|
- external_network
|
||||||
|
|
|
@ -134,7 +134,7 @@ Run `NODE_ENV=production npm start` to launch FoundKey manually. To stop the ser
|
||||||
|
|
||||||
### Launch with systemd
|
### Launch with systemd
|
||||||
|
|
||||||
Run `systemctl --edit --full --force foundkey.service`, and paste the following:
|
Run `systemctl edit --full --force foundkey.service`, and paste the following:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
|
@ -144,7 +144,7 @@ Description=FoundKey daemon
|
||||||
Type=simple
|
Type=simple
|
||||||
User=foundkey
|
User=foundkey
|
||||||
ExecStart=/usr/bin/npm start
|
ExecStart=/usr/bin/npm start
|
||||||
WorkingDirectory=/home/foundkey/foundkey
|
WorkingDirectory=/home/foundkey/FoundKey
|
||||||
Environment="NODE_ENV=production"
|
Environment="NODE_ENV=production"
|
||||||
TimeoutSec=60
|
TimeoutSec=60
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
|
@ -178,7 +178,7 @@ command_args="start"
|
||||||
command_user="foundkey"
|
command_user="foundkey"
|
||||||
|
|
||||||
supervisor="supervise-daemon"
|
supervisor="supervise-daemon"
|
||||||
supervise_daemon_args=" -d /home/foundkey/foundkey -e NODE_ENV=\"production\""
|
supervise_daemon_args=" -d /home/foundkey/FoundKey -e NODE_ENV=\"production\""
|
||||||
|
|
||||||
pidfile="/run/${RC_SVCNAME}.pid"
|
pidfile="/run/${RC_SVCNAME}.pid"
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "أنشئ حسابًا"
|
||||||
save: "حفظ"
|
save: "حفظ"
|
||||||
users: "المستخدمون"
|
users: "المستخدمون"
|
||||||
addUser: "اضافة مستخدم"
|
addUser: "اضافة مستخدم"
|
||||||
favorite: "أضفها للمفضلة"
|
|
||||||
favorites: "المفضلات"
|
|
||||||
unfavorite: "إزالة من المفضلة"
|
|
||||||
pin: "دبّسها على الصفحة الشخصية"
|
pin: "دبّسها على الصفحة الشخصية"
|
||||||
unpin: "ألغ تدبيسها من ملفك الشخصي"
|
unpin: "ألغ تدبيسها من ملفك الشخصي"
|
||||||
copyContent: "انسخ المحتوى"
|
copyContent: "انسخ المحتوى"
|
||||||
|
@ -581,7 +578,6 @@ loadRawImages: "حمّل الصور الأصلية بدلًا من المصغر
|
||||||
disableShowingAnimatedImages: "لا تشغّل الصور المتحركة"
|
disableShowingAnimatedImages: "لا تشغّل الصور المتحركة"
|
||||||
verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق."
|
verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق."
|
||||||
emailVerified: "تُحقّق من بريدك الإلكتروني"
|
emailVerified: "تُحقّق من بريدك الإلكتروني"
|
||||||
noteFavoritesCount: "عدد الملاحظات المفضلة"
|
|
||||||
pageLikesCount: "عدد الصفحات التي أعجبت بها"
|
pageLikesCount: "عدد الصفحات التي أعجبت بها"
|
||||||
pageLikedCount: "عدد صفحاتك المُعجب بها"
|
pageLikedCount: "عدد صفحاتك المُعجب بها"
|
||||||
contact: "التواصل"
|
contact: "التواصل"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "নিবন্ধন করুন"
|
||||||
save: "সংরক্ষণ"
|
save: "সংরক্ষণ"
|
||||||
users: "ব্যবহারকারীগণ"
|
users: "ব্যবহারকারীগণ"
|
||||||
addUser: "ব্যবহারকারী যোগ করুন"
|
addUser: "ব্যবহারকারী যোগ করুন"
|
||||||
favorite: "পছন্দ"
|
|
||||||
favorites: "পছন্দগুলি"
|
|
||||||
unfavorite: "পছন্দ না"
|
|
||||||
pin: "পিন করা"
|
pin: "পিন করা"
|
||||||
unpin: "পিন সরান"
|
unpin: "পিন সরান"
|
||||||
copyContent: "বিষয়বস্তু কপি করুন"
|
copyContent: "বিষয়বস্তু কপি করুন"
|
||||||
|
@ -633,7 +630,6 @@ disableShowingAnimatedImages: "অ্যানিমেটেড চিত্র
|
||||||
verificationEmailSent: "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে। সেটআপ সম্পূর্ণ করতে ইমেল এর\
|
verificationEmailSent: "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে। সেটআপ সম্পূর্ণ করতে ইমেল এর\
|
||||||
\ লিঙ্ক অনুসরণ করুন।"
|
\ লিঙ্ক অনুসরণ করুন।"
|
||||||
emailVerified: "ইমেইল নিশ্চিত করা হয়েছে"
|
emailVerified: "ইমেইল নিশ্চিত করা হয়েছে"
|
||||||
noteFavoritesCount: "পছন্দ করা নোটের সংখ্যা"
|
|
||||||
pageLikesCount: "পেজ লাইক করেছেন"
|
pageLikesCount: "পেজ লাইক করেছেন"
|
||||||
pageLikedCount: "পেজ লাইক পেয়েছেন"
|
pageLikedCount: "পেজ লাইক পেয়েছেন"
|
||||||
contact: "পরিচিতি সমূহ"
|
contact: "পরিচিতি সমূহ"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Registrar-se"
|
||||||
save: "Desar"
|
save: "Desar"
|
||||||
users: "Usuaris"
|
users: "Usuaris"
|
||||||
addUser: "Afegir un usuari"
|
addUser: "Afegir un usuari"
|
||||||
favorite: "Afegir a preferits"
|
|
||||||
favorites: "Favorits"
|
|
||||||
unfavorite: "Eliminar dels preferits"
|
|
||||||
pin: "Fixar al perfil"
|
pin: "Fixar al perfil"
|
||||||
unpin: "Para de fixar del perfil"
|
unpin: "Para de fixar del perfil"
|
||||||
copyContent: "Copiar el contingut"
|
copyContent: "Copiar el contingut"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Registrace"
|
||||||
save: "Uložit"
|
save: "Uložit"
|
||||||
users: "Uživatelé"
|
users: "Uživatelé"
|
||||||
addUser: "Přidat uživatele"
|
addUser: "Přidat uživatele"
|
||||||
favorite: "Oblíbené"
|
|
||||||
favorites: "Oblíbené"
|
|
||||||
unfavorite: "Odebrat z oblízených"
|
|
||||||
pin: "Připnout"
|
pin: "Připnout"
|
||||||
unpin: "Odepnout"
|
unpin: "Odepnout"
|
||||||
copyContent: "Zkopírovat obsah"
|
copyContent: "Zkopírovat obsah"
|
||||||
|
|
|
@ -33,9 +33,6 @@ signup: "Registrieren"
|
||||||
save: "Speichern"
|
save: "Speichern"
|
||||||
users: "Benutzer"
|
users: "Benutzer"
|
||||||
addUser: "Benutzer hinzufügen"
|
addUser: "Benutzer hinzufügen"
|
||||||
favorite: "Zu Favoriten hinzufügen"
|
|
||||||
favorites: "Favoriten"
|
|
||||||
unfavorite: "Aus Favoriten entfernen"
|
|
||||||
pin: "An dein Profil anheften"
|
pin: "An dein Profil anheften"
|
||||||
unpin: "Von deinem Profil lösen"
|
unpin: "Von deinem Profil lösen"
|
||||||
copyContent: "Inhalt kopieren"
|
copyContent: "Inhalt kopieren"
|
||||||
|
@ -654,7 +651,6 @@ disableShowingAnimatedImages: "Animierte Bilder nicht abspielen"
|
||||||
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet.\
|
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet.\
|
||||||
\ Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
|
\ Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
|
||||||
emailVerified: "Email-Adresse bestätigt"
|
emailVerified: "Email-Adresse bestätigt"
|
||||||
noteFavoritesCount: "Anzahl an als Favorit markierter Notizen"
|
|
||||||
pageLikesCount: "Anzahl an als \"Gefällt mir\" markierter Seiten"
|
pageLikesCount: "Anzahl an als \"Gefällt mir\" markierter Seiten"
|
||||||
pageLikedCount: "Anzahl erhaltener \"Gefällt mir\" auf Seiten"
|
pageLikedCount: "Anzahl erhaltener \"Gefällt mir\" auf Seiten"
|
||||||
contact: "Kontakt"
|
contact: "Kontakt"
|
||||||
|
@ -820,6 +816,7 @@ _ffVisibility:
|
||||||
public: "Öffentlich"
|
public: "Öffentlich"
|
||||||
followers: "Nur für Follower sichtbar"
|
followers: "Nur für Follower sichtbar"
|
||||||
private: "Privat"
|
private: "Privat"
|
||||||
|
nobody: Niemand (auch nicht du)
|
||||||
_signup:
|
_signup:
|
||||||
almostThere: "Fast geschafft"
|
almostThere: "Fast geschafft"
|
||||||
emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich\
|
emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich\
|
||||||
|
@ -1381,7 +1378,7 @@ addTag: Schlagwörter hinzufügen
|
||||||
removeTag: Schlagwörter entfernen
|
removeTag: Schlagwörter entfernen
|
||||||
exportAll: Alle exportieren
|
exportAll: Alle exportieren
|
||||||
exportSelected: Gewählte exportieren
|
exportSelected: Gewählte exportieren
|
||||||
federateBlocks: Andere Instanzen mitteilen, wenn ich jemanden blockiere
|
federateBlocks: Anderen Instanzen mitteilen, wenn ich jemanden blockiere
|
||||||
selectMode: Auswählen
|
selectMode: Auswählen
|
||||||
selectAll: Alle auswählen
|
selectAll: Alle auswählen
|
||||||
renoteUnmute: Renotes zeigen
|
renoteUnmute: Renotes zeigen
|
||||||
|
|
|
@ -645,7 +645,6 @@ disableShowingAnimatedImages: "Don't play animated images"
|
||||||
verificationEmailSent: "A verification email has been sent. Please follow the included\
|
verificationEmailSent: "A verification email has been sent. Please follow the included\
|
||||||
\ link to complete verification."
|
\ link to complete verification."
|
||||||
emailVerified: "Email has been verified"
|
emailVerified: "Email has been verified"
|
||||||
noteFavoritesCount: "Number of favorite notes"
|
|
||||||
pageLikesCount: "Number of liked Pages"
|
pageLikesCount: "Number of liked Pages"
|
||||||
pageLikedCount: "Number of received Page likes"
|
pageLikedCount: "Number of received Page likes"
|
||||||
contact: "Contact"
|
contact: "Contact"
|
||||||
|
|
|
@ -33,9 +33,6 @@ signup: "Registrarse"
|
||||||
save: "Guardar"
|
save: "Guardar"
|
||||||
users: "Usuarios"
|
users: "Usuarios"
|
||||||
addUser: "Agregar usuario"
|
addUser: "Agregar usuario"
|
||||||
favorite: "Favorito"
|
|
||||||
favorites: "Favoritos"
|
|
||||||
unfavorite: "Quitar de favoritos"
|
|
||||||
pin: "Fijar"
|
pin: "Fijar"
|
||||||
unpin: "Desfijar"
|
unpin: "Desfijar"
|
||||||
copyContent: "Copiar contenido"
|
copyContent: "Copiar contenido"
|
||||||
|
@ -638,7 +635,6 @@ verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación
|
||||||
\ favor, acceda al enlace proporcionado en el correo electrónico para completar\
|
\ favor, acceda al enlace proporcionado en el correo electrónico para completar\
|
||||||
\ la configuración."
|
\ la configuración."
|
||||||
emailVerified: "Su dirección de correo electrónico ha sido verificada."
|
emailVerified: "Su dirección de correo electrónico ha sido verificada."
|
||||||
noteFavoritesCount: "Número de notas favoritas"
|
|
||||||
pageLikesCount: "Número de favoritos en la página"
|
pageLikesCount: "Número de favoritos en la página"
|
||||||
pageLikedCount: "Número de favoritos de su página"
|
pageLikedCount: "Número de favoritos de su página"
|
||||||
contact: "Contacto"
|
contact: "Contacto"
|
||||||
|
|
|
@ -34,9 +34,6 @@ signup: "S’inscrire"
|
||||||
save: "Enregistrer"
|
save: "Enregistrer"
|
||||||
users: "Utilisateur·rice·s"
|
users: "Utilisateur·rice·s"
|
||||||
addUser: "Ajouter un·e utilisateur·rice"
|
addUser: "Ajouter un·e utilisateur·rice"
|
||||||
favorite: "Ajouter aux favoris"
|
|
||||||
favorites: "Favoris"
|
|
||||||
unfavorite: "Retirer des favoris"
|
|
||||||
pin: "Épingler sur le profil"
|
pin: "Épingler sur le profil"
|
||||||
unpin: "Désépingler"
|
unpin: "Désépingler"
|
||||||
copyContent: "Copier le contenu"
|
copyContent: "Copier le contenu"
|
||||||
|
@ -647,7 +644,6 @@ disableShowingAnimatedImages: "Désactiver l'animation des images"
|
||||||
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\
|
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au\
|
||||||
\ lien pour compléter la vérification."
|
\ lien pour compléter la vérification."
|
||||||
emailVerified: "Votre adresse e-mail a été vérifiée"
|
emailVerified: "Votre adresse e-mail a été vérifiée"
|
||||||
noteFavoritesCount: "Nombre de notes dans les favoris"
|
|
||||||
pageLikesCount: "Nombre de pages aimées"
|
pageLikesCount: "Nombre de pages aimées"
|
||||||
pageLikedCount: "Nombre de vos pages aimées"
|
pageLikedCount: "Nombre de vos pages aimées"
|
||||||
contact: "Contact"
|
contact: "Contact"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Daftar"
|
||||||
save: "Simpan"
|
save: "Simpan"
|
||||||
users: "Pengguna"
|
users: "Pengguna"
|
||||||
addUser: "Tambah pengguna"
|
addUser: "Tambah pengguna"
|
||||||
favorite: "Favorit"
|
|
||||||
favorites: "Favorit"
|
|
||||||
unfavorite: "Hapus favorit"
|
|
||||||
pin: "Sematkan ke profil"
|
pin: "Sematkan ke profil"
|
||||||
unpin: "Lepas sematan dari profil"
|
unpin: "Lepas sematan dari profil"
|
||||||
copyContent: "Salin konten"
|
copyContent: "Salin konten"
|
||||||
|
@ -639,7 +636,6 @@ disableShowingAnimatedImages: "Jangan mainkan gambar bergerak"
|
||||||
verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang\
|
verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang\
|
||||||
\ telah disertakan untuk menyelesaikan verifikasi."
|
\ telah disertakan untuk menyelesaikan verifikasi."
|
||||||
emailVerified: "Surel telah diverifikasi"
|
emailVerified: "Surel telah diverifikasi"
|
||||||
noteFavoritesCount: "Jumlah catatan yang difavoritkan"
|
|
||||||
pageLikesCount: "Jumlah suka yang diterima Halaman"
|
pageLikesCount: "Jumlah suka yang diterima Halaman"
|
||||||
pageLikedCount: "Jumlah Halaman yang disukai"
|
pageLikedCount: "Jumlah Halaman yang disukai"
|
||||||
contact: "Kontak"
|
contact: "Kontak"
|
||||||
|
|
|
@ -33,9 +33,6 @@ signup: "Iscriviti"
|
||||||
save: "Salva"
|
save: "Salva"
|
||||||
users: "Utente"
|
users: "Utente"
|
||||||
addUser: "Aggiungi utente"
|
addUser: "Aggiungi utente"
|
||||||
favorite: "Preferiti"
|
|
||||||
favorites: "Preferiti"
|
|
||||||
unfavorite: "Rimuovi nota dai preferiti"
|
|
||||||
pin: "Fissa sul profilo"
|
pin: "Fissa sul profilo"
|
||||||
unpin: "Non fissare sul profilo"
|
unpin: "Non fissare sul profilo"
|
||||||
copyContent: "Copia il contenuto"
|
copyContent: "Copia il contenuto"
|
||||||
|
@ -625,7 +622,6 @@ disableShowingAnimatedImages: "Disabilita le immagini animate"
|
||||||
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere\
|
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere\
|
||||||
\ al collegamento per compiere la verifica."
|
\ al collegamento per compiere la verifica."
|
||||||
emailVerified: "Il tuo indirizzo email è stato verificato"
|
emailVerified: "Il tuo indirizzo email è stato verificato"
|
||||||
noteFavoritesCount: "Conteggio note tra i preferiti"
|
|
||||||
pageLikesCount: "Numero di pagine che ti piacciono"
|
pageLikesCount: "Numero di pagine che ti piacciono"
|
||||||
pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\""
|
pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\""
|
||||||
contact: "Contatti"
|
contact: "Contatti"
|
||||||
|
|
|
@ -31,9 +31,6 @@ signup: "新規登録"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
users: "ユーザー"
|
users: "ユーザー"
|
||||||
addUser: "ユーザーを追加"
|
addUser: "ユーザーを追加"
|
||||||
favorite: "お気に入り"
|
|
||||||
favorites: "お気に入り"
|
|
||||||
unfavorite: "お気に入り解除"
|
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
copyContent: "内容をコピー"
|
copyContent: "内容をコピー"
|
||||||
|
@ -587,7 +584,6 @@ loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
||||||
disableShowingAnimatedImages: "アニメーション画像を再生しない"
|
disableShowingAnimatedImages: "アニメーション画像を再生しない"
|
||||||
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
|
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
|
||||||
emailVerified: "メールアドレスが確認されました"
|
emailVerified: "メールアドレスが確認されました"
|
||||||
noteFavoritesCount: "お気に入りノートの数"
|
|
||||||
pageLikesCount: "Pageにいいねした数"
|
pageLikesCount: "Pageにいいねした数"
|
||||||
pageLikedCount: "Pageにいいねされた数"
|
pageLikedCount: "Pageにいいねされた数"
|
||||||
contact: "連絡先"
|
contact: "連絡先"
|
||||||
|
@ -752,6 +748,7 @@ _ffVisibility:
|
||||||
followers: "フォロワーだけに公開"
|
followers: "フォロワーだけに公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
|
||||||
|
nobody: 誰にも見せない (あなたにさえも)
|
||||||
_signup:
|
_signup:
|
||||||
almostThere: "ほとんど完了です"
|
almostThere: "ほとんど完了です"
|
||||||
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
|
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
|
||||||
|
|
|
@ -30,9 +30,6 @@ signup: "新規登録"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
users: "ユーザー"
|
users: "ユーザー"
|
||||||
addUser: "ユーザーを追加や"
|
addUser: "ユーザーを追加や"
|
||||||
favorite: "お気に入り"
|
|
||||||
favorites: "お気に入り"
|
|
||||||
unfavorite: "やっぱ気に入らん"
|
|
||||||
pin: "ピン留めしとく"
|
pin: "ピン留めしとく"
|
||||||
unpin: "やっぱピン留めせん"
|
unpin: "やっぱピン留めせん"
|
||||||
copyContent: "内容をコピー"
|
copyContent: "内容をコピー"
|
||||||
|
|
|
@ -27,9 +27,6 @@ signup: "ನೋಂದಣಿ"
|
||||||
save: "ಉಳಿಸಿ"
|
save: "ಉಳಿಸಿ"
|
||||||
users: "ಬಳಕೆದಾರ"
|
users: "ಬಳಕೆದಾರ"
|
||||||
addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"
|
addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"
|
||||||
favorite: "ಮೆಚ್ಚಿನ"
|
|
||||||
favorites: "ಮೆಚ್ಚಿನವುಗಳು"
|
|
||||||
unfavorite: "ಮೆಚ್ಚುಗೆ ಅಳಿಸು"
|
|
||||||
pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
|
pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
|
||||||
unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ"
|
unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ"
|
||||||
copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು"
|
copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
---
|
|
||||||
_lang_: "한국어"
|
_lang_: "한국어"
|
||||||
headlineMisskey: "노트로 연결되는 네트워크"
|
headlineMisskey: "노트로 연결되는 네트워크"
|
||||||
introMisskey: "환영합니다! FoundKey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
|
introMisskey: "환영합니다! FoundKey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고\
|
||||||
|
\ 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요\U0001F4E1\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할\
|
||||||
|
\ 수도 있습니다\U0001F44D\n새로운 세계를 탐험해 보세요\U0001F680"
|
||||||
monthAndDay: "{month}월 {day}일"
|
monthAndDay: "{month}월 {day}일"
|
||||||
search: "검색"
|
search: "검색"
|
||||||
notifications: "알림"
|
notifications: "알림"
|
||||||
|
@ -12,7 +13,6 @@ fetchingAsApObject: "연합에서 조회 중"
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "알겠어요"
|
gotIt: "알겠어요"
|
||||||
cancel: "취소"
|
cancel: "취소"
|
||||||
enterUsername: "유저명 입력"
|
|
||||||
renotedBy: "{user}님이 Renote"
|
renotedBy: "{user}님이 Renote"
|
||||||
noNotes: "노트가 없습니다"
|
noNotes: "노트가 없습니다"
|
||||||
noNotifications: "표시할 알림이 없습니다"
|
noNotifications: "표시할 알림이 없습니다"
|
||||||
|
@ -28,16 +28,9 @@ login: "로그인"
|
||||||
loggingIn: "로그인 중"
|
loggingIn: "로그인 중"
|
||||||
logout: "로그아웃"
|
logout: "로그아웃"
|
||||||
signup: "회원 가입"
|
signup: "회원 가입"
|
||||||
uploading: "업로드 중"
|
|
||||||
save: "저장"
|
save: "저장"
|
||||||
users: "유저"
|
users: "유저"
|
||||||
addUser: "유저 추가"
|
addUser: "유저 추가"
|
||||||
favorite: "즐겨찾기"
|
|
||||||
favorites: "즐겨찾기"
|
|
||||||
unfavorite: "즐겨찾기에서 제거"
|
|
||||||
favorited: "즐겨찾기에 등록했습니다"
|
|
||||||
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다"
|
|
||||||
cantFavorite: "즐겨찾기에 등록하지 못했습니다"
|
|
||||||
pin: "프로필에 고정"
|
pin: "프로필에 고정"
|
||||||
unpin: "프로필에서 고정 해제"
|
unpin: "프로필에서 고정 해제"
|
||||||
copyContent: "내용 복사"
|
copyContent: "내용 복사"
|
||||||
|
@ -48,7 +41,6 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니
|
||||||
addToList: "리스트에 추가"
|
addToList: "리스트에 추가"
|
||||||
sendMessage: "메시지 보내기"
|
sendMessage: "메시지 보내기"
|
||||||
copyUsername: "유저명 복사"
|
copyUsername: "유저명 복사"
|
||||||
searchUser: "사용자 검색"
|
|
||||||
reply: "답글"
|
reply: "답글"
|
||||||
loadMore: "더 보기"
|
loadMore: "더 보기"
|
||||||
showMore: "더 보기"
|
showMore: "더 보기"
|
||||||
|
@ -68,7 +60,6 @@ unfollowConfirm: "{name}님을 언팔로우하시겠습니까?"
|
||||||
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
|
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
|
||||||
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
|
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
|
||||||
lists: "리스트"
|
lists: "리스트"
|
||||||
noLists: "리스트가 없습니다"
|
|
||||||
note: "노트"
|
note: "노트"
|
||||||
notes: "노트"
|
notes: "노트"
|
||||||
following: "팔로잉"
|
following: "팔로잉"
|
||||||
|
@ -80,7 +71,8 @@ error: "오류"
|
||||||
somethingHappened: "오류가 발생했습니다"
|
somethingHappened: "오류가 발생했습니다"
|
||||||
retry: "다시 시도"
|
retry: "다시 시도"
|
||||||
pageLoadError: "페이지를 불러오지 못했습니다."
|
pageLoadError: "페이지를 불러오지 못했습니다."
|
||||||
pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후 다시 시도해 주세요."
|
pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후\
|
||||||
|
\ 다시 시도해 주세요."
|
||||||
serverIsDead: "서버로부터 응답이 없습니다. 잠시 후 다시 시도해주세요."
|
serverIsDead: "서버로부터 응답이 없습니다. 잠시 후 다시 시도해주세요."
|
||||||
youShouldUpgradeClient: "이 페이지를 표시하려면 새로고침하여 새로운 버전의 클라이언트를 이용해 주십시오."
|
youShouldUpgradeClient: "이 페이지를 표시하려면 새로고침하여 새로운 버전의 클라이언트를 이용해 주십시오."
|
||||||
enterListName: "리스트 이름을 입력"
|
enterListName: "리스트 이름을 입력"
|
||||||
|
@ -92,21 +84,15 @@ followRequest: "팔로우 요청"
|
||||||
followRequests: "팔로우 요청"
|
followRequests: "팔로우 요청"
|
||||||
unfollow: "팔로우 해제"
|
unfollow: "팔로우 해제"
|
||||||
followRequestPending: "팔로우 허가 대기중"
|
followRequestPending: "팔로우 허가 대기중"
|
||||||
enterEmoji: "이모지 입력"
|
|
||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
unrenote: "Renote 취소"
|
unrenote: "Renote 취소"
|
||||||
renoted: "Renote 하였습니다"
|
|
||||||
cantRenote: "이 게시물은 Renote할 수 없습니다."
|
|
||||||
cantReRenote: "Renote를 Renote할 수 없습니다."
|
|
||||||
quote: "인용"
|
quote: "인용"
|
||||||
pinnedNote: "고정해놓은 노트"
|
pinnedNote: "고정해놓은 노트"
|
||||||
pinned: "프로필에 고정"
|
|
||||||
you: "당신"
|
you: "당신"
|
||||||
clickToShow: "클릭하여 보기"
|
clickToShow: "클릭하여 보기"
|
||||||
sensitive: "열람주의"
|
sensitive: "열람주의"
|
||||||
add: "추가"
|
add: "추가"
|
||||||
reaction: "리액션"
|
reaction: "리액션"
|
||||||
reactionSetting: "선택기에 표시할 리액션"
|
|
||||||
reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다."
|
reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다."
|
||||||
attachCancel: "첨부 취소"
|
attachCancel: "첨부 취소"
|
||||||
markAsSensitive: "열람주의로 설정"
|
markAsSensitive: "열람주의로 설정"
|
||||||
|
@ -130,14 +116,13 @@ editWidgetsExit: "편집 종료"
|
||||||
customEmojis: "커스텀 이모지"
|
customEmojis: "커스텀 이모지"
|
||||||
emoji: "이모지"
|
emoji: "이모지"
|
||||||
emojis: "이모지"
|
emojis: "이모지"
|
||||||
emojiName: "이모지 이름"
|
|
||||||
emojiUrl: "이모지 URL"
|
|
||||||
addEmoji: "이모지 추가"
|
addEmoji: "이모지 추가"
|
||||||
settingGuide: "추천 설정"
|
|
||||||
cacheRemoteFiles: "리모트 파일을 캐시"
|
cacheRemoteFiles: "리모트 파일을 캐시"
|
||||||
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라\
|
||||||
|
\ 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
||||||
flagAsBot: "나는 봇입니다"
|
flagAsBot: "나는 봇입니다"
|
||||||
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
|
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여\
|
||||||
|
\ 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
|
||||||
flagAsCat: "나는 고양이다냥"
|
flagAsCat: "나는 고양이다냥"
|
||||||
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
|
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
|
||||||
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
|
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
|
||||||
|
@ -147,40 +132,32 @@ addAccount: "계정 추가"
|
||||||
loginFailed: "로그인에 실패했습니다"
|
loginFailed: "로그인에 실패했습니다"
|
||||||
showOnRemote: "리모트에서 보기"
|
showOnRemote: "리모트에서 보기"
|
||||||
general: "일반"
|
general: "일반"
|
||||||
wallpaper: "배경"
|
|
||||||
setWallpaper: "배경화면 설정"
|
setWallpaper: "배경화면 설정"
|
||||||
removeWallpaper: "배경 제거"
|
removeWallpaper: "배경 제거"
|
||||||
searchWith: "검색: {q}"
|
|
||||||
youHaveNoLists: "리스트가 없습니다"
|
youHaveNoLists: "리스트가 없습니다"
|
||||||
followConfirm: "{name}님을 팔로우 하시겠습니까?"
|
followConfirm: "{name}님을 팔로우 하시겠습니까?"
|
||||||
proxyAccount: "프록시 계정"
|
proxyAccount: "프록시 계정"
|
||||||
proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 인스턴스로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다."
|
proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트\
|
||||||
|
\ 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 인스턴스로 배달되지 않기 때문에, 대신 프록시 계정이\
|
||||||
|
\ 해당 유저를 팔로우하도록 합니다."
|
||||||
host: "호스트"
|
host: "호스트"
|
||||||
selectUser: "유저 선택"
|
selectUser: "유저 선택"
|
||||||
recipient: "수신인"
|
recipient: "수신인"
|
||||||
annotation: "내용에 대한 주석"
|
annotation: "내용에 대한 주석"
|
||||||
federation: "연합"
|
federation: "연합"
|
||||||
instances: "인스턴스"
|
|
||||||
registeredAt: "등록 날짜"
|
registeredAt: "등록 날짜"
|
||||||
latestRequestSentAt: "마지막으로 요청을 보낸 시간"
|
latestRequestSentAt: "마지막으로 요청을 보낸 시간"
|
||||||
latestRequestReceivedAt: "마지막으로 요청을 받은 시간"
|
latestRequestReceivedAt: "마지막으로 요청을 받은 시간"
|
||||||
latestStatus: "마지막 상태"
|
latestStatus: "마지막 상태"
|
||||||
storageUsage: "스토리지 사용량"
|
|
||||||
charts: "차트"
|
charts: "차트"
|
||||||
perHour: "1시간마다"
|
perHour: "1시간마다"
|
||||||
perDay: "1일마다"
|
perDay: "1일마다"
|
||||||
stopActivityDelivery: "액티비티 보내지 않기"
|
stopActivityDelivery: "액티비티 보내지 않기"
|
||||||
blockThisInstance: "이 인스턴스를 차단"
|
blockThisInstance: "이 인스턴스를 차단"
|
||||||
operations: "작업"
|
|
||||||
software: "소프트웨어"
|
software: "소프트웨어"
|
||||||
version: "버전"
|
version: "버전"
|
||||||
metadata: "메타데이터"
|
|
||||||
withNFiles: "{n}개의 파일"
|
withNFiles: "{n}개의 파일"
|
||||||
monitor: "모니터"
|
|
||||||
jobQueue: "작업 대기열"
|
jobQueue: "작업 대기열"
|
||||||
cpuAndMemory: "CPU와 메모리"
|
|
||||||
network: "네트워크"
|
|
||||||
disk: "디스크"
|
|
||||||
instanceInfo: "인스턴스 정보"
|
instanceInfo: "인스턴스 정보"
|
||||||
statistics: "통계"
|
statistics: "통계"
|
||||||
clearQueue: "대기열 비우기"
|
clearQueue: "대기열 비우기"
|
||||||
|
@ -189,7 +166,8 @@ clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되
|
||||||
clearCachedFiles: "캐시 비우기"
|
clearCachedFiles: "캐시 비우기"
|
||||||
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
|
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
|
||||||
blockedInstances: "차단된 인스턴스"
|
blockedInstances: "차단된 인스턴스"
|
||||||
blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
|
blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와\
|
||||||
|
\ 통신할 수 없게 됩니다."
|
||||||
muteAndBlock: "뮤트 및 차단"
|
muteAndBlock: "뮤트 및 차단"
|
||||||
mutedUsers: "뮤트한 유저"
|
mutedUsers: "뮤트한 유저"
|
||||||
blockedUsers: "차단한 유저"
|
blockedUsers: "차단한 유저"
|
||||||
|
@ -211,9 +189,6 @@ all: "전체"
|
||||||
subscribing: "구독 중"
|
subscribing: "구독 중"
|
||||||
publishing: "배포 중"
|
publishing: "배포 중"
|
||||||
notResponding: "응답 없음"
|
notResponding: "응답 없음"
|
||||||
instanceFollowing: "인스턴스의 팔로잉"
|
|
||||||
instanceFollowers: "인스턴스의 팔로워"
|
|
||||||
instanceUsers: "인스턴스의 유저"
|
|
||||||
changePassword: "비밀번호 변경"
|
changePassword: "비밀번호 변경"
|
||||||
security: "보안"
|
security: "보안"
|
||||||
retypedNotMatch: "입력이 일치하지 않습니다."
|
retypedNotMatch: "입력이 일치하지 않습니다."
|
||||||
|
@ -229,7 +204,6 @@ lookup: "조회"
|
||||||
announcements: "공지사항"
|
announcements: "공지사항"
|
||||||
imageUrl: "이미지 URL"
|
imageUrl: "이미지 URL"
|
||||||
remove: "삭제"
|
remove: "삭제"
|
||||||
removed: "삭제하였습니다"
|
|
||||||
removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
||||||
deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
||||||
resetAreYouSure: "초기화 하시겠습니까?"
|
resetAreYouSure: "초기화 하시겠습니까?"
|
||||||
|
@ -237,7 +211,8 @@ saved: "저장하였습니다"
|
||||||
messaging: "대화"
|
messaging: "대화"
|
||||||
upload: "업로드"
|
upload: "업로드"
|
||||||
keepOriginalUploading: "원본 이미지를 유지"
|
keepOriginalUploading: "원본 이미지를 유지"
|
||||||
keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서 웹 공개용 이미지를 생성합니다."
|
keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서\
|
||||||
|
\ 웹 공개용 이미지를 생성합니다."
|
||||||
fromDrive: "드라이브에서"
|
fromDrive: "드라이브에서"
|
||||||
fromUrl: "URL로부터"
|
fromUrl: "URL로부터"
|
||||||
uploadFromUrl: "URL 업로드"
|
uploadFromUrl: "URL 업로드"
|
||||||
|
@ -269,7 +244,6 @@ lightThemes: "밝은 테마"
|
||||||
darkThemes: "어두운 테마"
|
darkThemes: "어두운 테마"
|
||||||
syncDeviceDarkMode: "디바이스의 다크 모드 설정과 동기화"
|
syncDeviceDarkMode: "디바이스의 다크 모드 설정과 동기화"
|
||||||
drive: "드라이브"
|
drive: "드라이브"
|
||||||
fileName: "파일명"
|
|
||||||
selectFile: "파일 선택"
|
selectFile: "파일 선택"
|
||||||
selectFiles: "파일 선택"
|
selectFiles: "파일 선택"
|
||||||
selectFolder: "폴더 선택"
|
selectFolder: "폴더 선택"
|
||||||
|
@ -280,8 +254,6 @@ createFolder: "폴더 만들기"
|
||||||
renameFolder: "폴더 이름 바꾸기"
|
renameFolder: "폴더 이름 바꾸기"
|
||||||
deleteFolder: "폴더 삭제"
|
deleteFolder: "폴더 삭제"
|
||||||
addFile: "파일 추가"
|
addFile: "파일 추가"
|
||||||
emptyDrive: "드라이브가 비어 있습니다"
|
|
||||||
emptyFolder: "폴더가 비어 있습니다"
|
|
||||||
unableToDelete: "삭제할 수 없습니다"
|
unableToDelete: "삭제할 수 없습니다"
|
||||||
inputNewFileName: "바꿀 파일명을 입력해 주세요"
|
inputNewFileName: "바꿀 파일명을 입력해 주세요"
|
||||||
inputNewDescription: "새 캡션을 입력해 주세요"
|
inputNewDescription: "새 캡션을 입력해 주세요"
|
||||||
|
@ -318,7 +290,6 @@ pages: "페이지"
|
||||||
enableLocalTimeline: "로컬 타임라인 활성화"
|
enableLocalTimeline: "로컬 타임라인 활성화"
|
||||||
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
||||||
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
||||||
registration: "등록"
|
|
||||||
enableRegistration: "신규 회원가입을 활성화"
|
enableRegistration: "신규 회원가입을 활성화"
|
||||||
invite: "초대"
|
invite: "초대"
|
||||||
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
|
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
|
||||||
|
@ -327,22 +298,12 @@ inMb: "메가바이트 단위"
|
||||||
iconUrl: "아이콘 URL"
|
iconUrl: "아이콘 URL"
|
||||||
bannerUrl: "배너 이미지 URL"
|
bannerUrl: "배너 이미지 URL"
|
||||||
backgroundImageUrl: "배경 이미지 URL"
|
backgroundImageUrl: "배경 이미지 URL"
|
||||||
basicInfo: "기본 정보"
|
|
||||||
pinnedUsers: "고정된 유저"
|
pinnedUsers: "고정된 유저"
|
||||||
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
|
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
|
||||||
pinnedPages: "고정한 페이지"
|
|
||||||
pinnedPagesDescription: "인스턴스의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
|
|
||||||
pinnedClipId: "고정할 클립의 ID"
|
|
||||||
pinnedNotes: "고정해놓은 노트"
|
|
||||||
hcaptcha: "hCaptcha"
|
|
||||||
enableHcaptcha: "hCaptcha 활성화"
|
|
||||||
hcaptchaSiteKey: "사이트 키"
|
hcaptchaSiteKey: "사이트 키"
|
||||||
hcaptchaSecretKey: "시크릿 키"
|
hcaptchaSecretKey: "시크릿 키"
|
||||||
recaptcha: "reCAPTCHA"
|
|
||||||
enableRecaptcha: "reCAPTCHA 활성화"
|
|
||||||
recaptchaSiteKey: "사이트 키"
|
recaptchaSiteKey: "사이트 키"
|
||||||
recaptchaSecretKey: "시크릿 키"
|
recaptchaSecretKey: "시크릿 키"
|
||||||
avoidMultiCaptchaConfirm: "여러 Captcha를 사용하는 경우 간섭이 발생할 가능성이 있습니다. 다른 Captcha를 비활성화하시겠습니까? 취소를 눌러 여러 Captcha를 활성화한 상태로 두는 것도 가능합니다."
|
|
||||||
antennas: "안테나"
|
antennas: "안테나"
|
||||||
manageAntennas: "안테나 관리"
|
manageAntennas: "안테나 관리"
|
||||||
name: "이름"
|
name: "이름"
|
||||||
|
@ -352,7 +313,6 @@ antennaExcludeKeywords: "제외할 키워드"
|
||||||
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
||||||
notifyAntenna: "새로운 노트를 알림"
|
notifyAntenna: "새로운 노트를 알림"
|
||||||
withFileAntenna: "파일이 첨부된 노트만"
|
withFileAntenna: "파일이 첨부된 노트만"
|
||||||
enableServiceworker: "ServiceWorker 사용"
|
|
||||||
antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다"
|
antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다"
|
||||||
caseSensitive: "대소문자를 구분"
|
caseSensitive: "대소문자를 구분"
|
||||||
withReplies: "답글 포함"
|
withReplies: "답글 포함"
|
||||||
|
@ -367,11 +327,8 @@ popularUsers: "인기 유저"
|
||||||
recentlyUpdatedUsers: "최근 활동한 유저"
|
recentlyUpdatedUsers: "최근 활동한 유저"
|
||||||
recentlyRegisteredUsers: "최근 가입한 유저"
|
recentlyRegisteredUsers: "최근 가입한 유저"
|
||||||
recentlyDiscoveredUsers: "최근 발견한 유저"
|
recentlyDiscoveredUsers: "최근 발견한 유저"
|
||||||
exploreUsersCount: "{count}명의 유저가 있습니다"
|
|
||||||
exploreFediverse: "연합우주를 탐색"
|
|
||||||
popularTags: "인기 태그"
|
popularTags: "인기 태그"
|
||||||
userList: "리스트"
|
userList: "리스트"
|
||||||
about: "정보"
|
|
||||||
aboutMisskey: "FoundKey에 대하여"
|
aboutMisskey: "FoundKey에 대하여"
|
||||||
administrator: "관리자"
|
administrator: "관리자"
|
||||||
token: "토큰"
|
token: "토큰"
|
||||||
|
@ -391,7 +348,6 @@ share: "공유"
|
||||||
notFound: "찾을 수 없습니다"
|
notFound: "찾을 수 없습니다"
|
||||||
notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다."
|
notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다."
|
||||||
uploadFolder: "기본 업로드 위치"
|
uploadFolder: "기본 업로드 위치"
|
||||||
cacheClear: "캐시 지우기"
|
|
||||||
markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시"
|
markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시"
|
||||||
markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시"
|
markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시"
|
||||||
markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시"
|
markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시"
|
||||||
|
@ -422,7 +378,6 @@ noMessagesYet: "아직 대화가 없습니다"
|
||||||
newMessageExists: "새 메시지가 있습니다"
|
newMessageExists: "새 메시지가 있습니다"
|
||||||
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
||||||
signinRequired: "로그인 해주세요"
|
signinRequired: "로그인 해주세요"
|
||||||
invitations: "초대"
|
|
||||||
invitationCode: "초대 코드"
|
invitationCode: "초대 코드"
|
||||||
checking: "확인하는 중입니다"
|
checking: "확인하는 중입니다"
|
||||||
available: "사용 가능합니다"
|
available: "사용 가능합니다"
|
||||||
|
@ -441,7 +396,6 @@ or: "혹은"
|
||||||
language: "언어"
|
language: "언어"
|
||||||
uiLanguage: "UI 표시 언어"
|
uiLanguage: "UI 표시 언어"
|
||||||
groupInvited: "그룹에 초대되었습니다"
|
groupInvited: "그룹에 초대되었습니다"
|
||||||
aboutX: "{x}에 대하여"
|
|
||||||
useOsNativeEmojis: "OS 기본 이모지를 사용"
|
useOsNativeEmojis: "OS 기본 이모지를 사용"
|
||||||
disableDrawer: "드로어 메뉴를 사용하지 않기"
|
disableDrawer: "드로어 메뉴를 사용하지 않기"
|
||||||
youHaveNoGroups: "그룹이 없습니다"
|
youHaveNoGroups: "그룹이 없습니다"
|
||||||
|
@ -449,47 +403,42 @@ joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을
|
||||||
noHistory: "기록이 없습니다"
|
noHistory: "기록이 없습니다"
|
||||||
signinHistory: "로그인 기록"
|
signinHistory: "로그인 기록"
|
||||||
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
|
disableAnimatedMfm: "움직임이 있는 MFM을 비활성화"
|
||||||
doing: "잠시만요"
|
|
||||||
category: "카테고리"
|
category: "카테고리"
|
||||||
tags: "태그"
|
tags: "태그"
|
||||||
docSource: "이 문서의 소스"
|
|
||||||
createAccount: "계정 만들기"
|
createAccount: "계정 만들기"
|
||||||
existingAccount: "기존 계정"
|
existingAccount: "기존 계정"
|
||||||
regenerate: "재생성"
|
|
||||||
fontSize: "글자 크기"
|
fontSize: "글자 크기"
|
||||||
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
|
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
|
||||||
openImageInNewTab: "새 탭에서 이미지 열기"
|
openImageInNewTab: "새 탭에서 이미지 열기"
|
||||||
dashboard: "대시보드"
|
dashboard: "대시보드"
|
||||||
local: "로컬"
|
local: "로컬"
|
||||||
remote: "리모트"
|
remote: "리모트"
|
||||||
total: "합계"
|
|
||||||
weekOverWeekChanges: "지난주보다"
|
|
||||||
dayOverDayChanges: "어제보다"
|
dayOverDayChanges: "어제보다"
|
||||||
appearance: "모양"
|
appearance: "모양"
|
||||||
clientSettings: "클라이언트 설정"
|
clientSettings: "클라이언트 설정"
|
||||||
accountSettings: "계정 설정"
|
|
||||||
numberOfDays: "며칠동안"
|
|
||||||
hideThisNote: "이 노트를 숨기기"
|
|
||||||
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
|
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
|
||||||
objectStorage: "오브젝트 스토리지"
|
objectStorage: "오브젝트 스토리지"
|
||||||
useObjectStorage: "오브젝트 스토리지를 사용"
|
useObjectStorage: "오브젝트 스토리지를 사용"
|
||||||
objectStorageBaseUrl: "Base URL"
|
objectStorageBaseUrl: "Base URL"
|
||||||
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어, AWS S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>' 와 같이 지정합니다."
|
objectStorageBaseUrlDesc: "오브젝트 (미디어) 참조 URL 을 만들 때 사용되는 URL입니다. CDN 또는 프록시를 사용하는\
|
||||||
|
\ 경우 그 URL을 지정하고, 그 외의 경우 사용할 서비스의 가이드에 따라 공개적으로 액세스 할 수 있는 주소를 지정해 주세요. 예를 들어,\
|
||||||
|
\ AWS S3의 경우 'https://<bucket>.s3.amazonaws.com', GCS등의 경우 'https://storage.googleapis.com/<bucket>'\
|
||||||
|
\ 와 같이 지정합니다."
|
||||||
objectStorageBucket: "Bucket"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "사용 서비스의 bucket명을 지정해주세요."
|
objectStorageBucketDesc: "사용 서비스의 bucket명을 지정해주세요."
|
||||||
objectStoragePrefix: "Prefix"
|
objectStoragePrefix: "Prefix"
|
||||||
objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장됩니다."
|
objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장됩니다."
|
||||||
objectStorageEndpoint: "Endpoint"
|
objectStorageEndpoint: "Endpoint"
|
||||||
objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요. '<host>' 혹은 '<host>:<port>' 와 같이 지정합니다."
|
objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요.\
|
||||||
|
\ '<host>' 혹은 '<host>:<port>' 와 같이 지정합니다."
|
||||||
objectStorageRegion: "Region"
|
objectStorageRegion: "Region"
|
||||||
objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는 경우, 비워 두거나 'us-east-1'으로 설정해 주세요."
|
objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는\
|
||||||
|
\ 경우, 비워 두거나 'us-east-1'으로 설정해 주세요."
|
||||||
objectStorageUseSSL: "SSL 사용"
|
objectStorageUseSSL: "SSL 사용"
|
||||||
objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
||||||
objectStorageUseProxy: "연결에 프록시를 사용"
|
objectStorageUseProxy: "연결에 프록시를 사용"
|
||||||
objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
||||||
objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
|
objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
|
||||||
serverLogs: "서버 로그"
|
|
||||||
deleteAll: "모두 삭제"
|
|
||||||
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
||||||
newNoteRecived: "새 노트가 있습니다"
|
newNoteRecived: "새 노트가 있습니다"
|
||||||
sounds: "소리"
|
sounds: "소리"
|
||||||
|
@ -500,7 +449,6 @@ popout: "새 창으로 열기"
|
||||||
volume: "음량"
|
volume: "음량"
|
||||||
masterVolume: "마스터 볼륨"
|
masterVolume: "마스터 볼륨"
|
||||||
details: "자세히"
|
details: "자세히"
|
||||||
chooseEmoji: "이모지 선택"
|
|
||||||
unableToProcess: "작업을 완료할 수 없습니다"
|
unableToProcess: "작업을 완료할 수 없습니다"
|
||||||
recentUsed: "최근 사용"
|
recentUsed: "최근 사용"
|
||||||
install: "설치"
|
install: "설치"
|
||||||
|
@ -514,28 +462,27 @@ sort: "정렬"
|
||||||
ascendingOrder: "오름차순"
|
ascendingOrder: "오름차순"
|
||||||
descendingOrder: "내림차순"
|
descendingOrder: "내림차순"
|
||||||
scratchpad: "스크래치 패드"
|
scratchpad: "스크래치 패드"
|
||||||
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. FoundKey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다."
|
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. FoundKey 와 상호 작용하는 코드를\
|
||||||
|
\ 작성, 실행 및 결과를 확인할 수 있습니다."
|
||||||
output: "출력"
|
output: "출력"
|
||||||
script: "스크립트"
|
|
||||||
updateRemoteUser: "리모트 유저 정보 갱신"
|
updateRemoteUser: "리모트 유저 정보 갱신"
|
||||||
deleteAllFiles: "모든 파일 삭제"
|
deleteAllFiles: "모든 파일 삭제"
|
||||||
deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?"
|
deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?"
|
||||||
removeAllFollowing: "모든 팔로잉 해제"
|
removeAllFollowing: "모든 팔로잉 해제"
|
||||||
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요."
|
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게\
|
||||||
|
\ 된 경우 등에 실행해 주세요."
|
||||||
userSuspended: "이 계정은 정지된 상태입니다."
|
userSuspended: "이 계정은 정지된 상태입니다."
|
||||||
userSilenced: "이 계정은 사일런스된 상태입니다."
|
userSilenced: "이 계정은 사일런스된 상태입니다."
|
||||||
yourAccountSuspendedTitle: "계정이 정지되었습니다"
|
yourAccountSuspendedTitle: "계정이 정지되었습니다"
|
||||||
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
|
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한\
|
||||||
|
\ 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
|
||||||
menu: "메뉴"
|
menu: "메뉴"
|
||||||
divider: "구분선"
|
divider: "구분선"
|
||||||
addItem: "항목 추가"
|
addItem: "항목 추가"
|
||||||
relays: "릴레이"
|
relays: "릴레이"
|
||||||
addRelay: "릴레이 추가"
|
addRelay: "릴레이 추가"
|
||||||
inboxUrl: "Inbox 주소"
|
inboxUrl: "Inbox 주소"
|
||||||
addedRelays: "추가된 릴레이"
|
|
||||||
serviceworkerInfo: "푸시 알림을 수행하려면 활성화해야 합니다."
|
|
||||||
deletedNote: "삭제된 노트"
|
deletedNote: "삭제된 노트"
|
||||||
invisibleNote: "비공개 노트"
|
|
||||||
enableInfiniteScroll: "자동으로 좀 더 보기"
|
enableInfiniteScroll: "자동으로 좀 더 보기"
|
||||||
visibility: "공개 범위"
|
visibility: "공개 범위"
|
||||||
poll: "투표"
|
poll: "투표"
|
||||||
|
@ -545,15 +492,12 @@ disablePlayer: "플레이어 닫기"
|
||||||
themeEditor: "테마 에디터"
|
themeEditor: "테마 에디터"
|
||||||
description: "설명"
|
description: "설명"
|
||||||
describeFile: "캡션 추가"
|
describeFile: "캡션 추가"
|
||||||
enterFileDescription: "캡션 입력"
|
|
||||||
author: "작성자"
|
author: "작성자"
|
||||||
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
|
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
|
||||||
manage: "관리"
|
manage: "관리"
|
||||||
plugins: "플러그인"
|
plugins: "플러그인"
|
||||||
deck: "덱"
|
deck: "덱"
|
||||||
undeck: "덱 해제"
|
|
||||||
useBlurEffectForModal: "모달에 흐림 효과 사용"
|
useBlurEffectForModal: "모달에 흐림 효과 사용"
|
||||||
useFullReactionPicker: "모든 기능이 포함된 리액션 선택기 사용"
|
|
||||||
width: "폭"
|
width: "폭"
|
||||||
height: "높이"
|
height: "높이"
|
||||||
large: "크게"
|
large: "크게"
|
||||||
|
@ -565,7 +509,6 @@ enableAll: "전체 선택"
|
||||||
disableAll: "전체 해제"
|
disableAll: "전체 해제"
|
||||||
tokenRequested: "계정 접근 허용"
|
tokenRequested: "계정 접근 허용"
|
||||||
pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
|
pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
|
||||||
notificationType: "알림 유형"
|
|
||||||
edit: "편집"
|
edit: "편집"
|
||||||
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
|
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
|
||||||
emailServer: "메일 서버"
|
emailServer: "메일 서버"
|
||||||
|
@ -590,10 +533,7 @@ userSaysSomething: "{name}님이 무언가를 말했습니다"
|
||||||
makeActive: "활성화"
|
makeActive: "활성화"
|
||||||
display: "표시"
|
display: "표시"
|
||||||
copy: "복사"
|
copy: "복사"
|
||||||
metrics: "통계"
|
|
||||||
overview: "요약"
|
overview: "요약"
|
||||||
logs: "로그"
|
|
||||||
delayed: "지연"
|
|
||||||
database: "데이터베이스"
|
database: "데이터베이스"
|
||||||
channel: "채널"
|
channel: "채널"
|
||||||
create: "생성"
|
create: "생성"
|
||||||
|
@ -603,11 +543,11 @@ useGlobalSetting: "글로벌 설정을 사용하기"
|
||||||
useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다."
|
useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다."
|
||||||
other: "기타"
|
other: "기타"
|
||||||
regenerateLoginToken: "로그인 토큰을 재생성"
|
regenerateLoginToken: "로그인 토큰을 재생성"
|
||||||
regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
|
regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다.\
|
||||||
|
\ 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
|
||||||
setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
||||||
fileIdOrUrl: "파일 ID 또는 URL"
|
fileIdOrUrl: "파일 ID 또는 URL"
|
||||||
behavior: "동작"
|
behavior: "동작"
|
||||||
sample: "예시"
|
|
||||||
abuseReports: "신고"
|
abuseReports: "신고"
|
||||||
reportAbuse: "신고"
|
reportAbuse: "신고"
|
||||||
reportAbuseOf: "{name}을 신고하기"
|
reportAbuseOf: "{name}을 신고하기"
|
||||||
|
@ -621,12 +561,8 @@ forwardReportIsAnonymous: "리모트 인스턴스에서는 나의 정보를 볼
|
||||||
send: "전송"
|
send: "전송"
|
||||||
abuseMarkAsResolved: "해결됨으로 표시"
|
abuseMarkAsResolved: "해결됨으로 표시"
|
||||||
openInNewTab: "새 탭에서 열기"
|
openInNewTab: "새 탭에서 열기"
|
||||||
openInSideView: "사이드뷰로 열기"
|
|
||||||
defaultNavigationBehaviour: "기본 탐색 동작"
|
defaultNavigationBehaviour: "기본 탐색 동작"
|
||||||
editTheseSettingsMayBreakAccount: "이 설정을 변경하면 계정이 손상될 수 있습니다."
|
|
||||||
instanceTicker: "노트의 인스턴스 정보"
|
instanceTicker: "노트의 인스턴스 정보"
|
||||||
waitingFor: "{x}을(를) 기다리고 있습니다"
|
|
||||||
random: "랜덤"
|
|
||||||
system: "시스템"
|
system: "시스템"
|
||||||
switchUi: "UI 전환"
|
switchUi: "UI 전환"
|
||||||
desktop: "데스크탑"
|
desktop: "데스크탑"
|
||||||
|
@ -660,16 +596,12 @@ alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
|
||||||
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
|
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
|
||||||
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
|
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
|
||||||
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
|
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
|
||||||
notSet: "설정되지 않음"
|
|
||||||
emailVerified: "메일 주소가 확인되었습니다."
|
emailVerified: "메일 주소가 확인되었습니다."
|
||||||
noteFavoritesCount: "즐겨찾기한 노트 수"
|
|
||||||
pageLikesCount: "좋아요 한 Page 수"
|
pageLikesCount: "좋아요 한 Page 수"
|
||||||
pageLikedCount: "Page에 받은 좋아요 수"
|
pageLikedCount: "Page에 받은 좋아요 수"
|
||||||
contact: "연락처"
|
contact: "연락처"
|
||||||
useSystemFont: "시스템 기본 글꼴을 사용"
|
useSystemFont: "시스템 기본 글꼴을 사용"
|
||||||
clips: "클립"
|
clips: "클립"
|
||||||
experimentalFeatures: "실험실"
|
|
||||||
developer: "개발자"
|
|
||||||
makeExplorable: "\"발견하기\"에 내 계정 보이기"
|
makeExplorable: "\"발견하기\"에 내 계정 보이기"
|
||||||
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
|
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
|
||||||
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시"
|
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시"
|
||||||
|
@ -680,28 +612,16 @@ wide: "넓게"
|
||||||
narrow: "좁게"
|
narrow: "좁게"
|
||||||
reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
|
reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
|
||||||
needReloadToApply: "변경 사항은 새로고침하면 적용됩니다."
|
needReloadToApply: "변경 사항은 새로고침하면 적용됩니다."
|
||||||
showTitlebar: "타이틀 바를 표시하기"
|
|
||||||
clearCache: "캐시 비우기"
|
clearCache: "캐시 비우기"
|
||||||
onlineUsersCount: "{n}명이 접속 중"
|
onlineUsersCount: "{n}명이 접속 중"
|
||||||
nUsers: "{n} 유저"
|
|
||||||
nNotes: "{n} 노트"
|
|
||||||
myTheme: "내 테마"
|
|
||||||
backgroundColor: "배경 색"
|
backgroundColor: "배경 색"
|
||||||
accentColor: "강조 색상"
|
accentColor: "강조 색상"
|
||||||
textColor: "문자 색"
|
textColor: "문자 색"
|
||||||
saveAs: "다른 이름으로 저장"
|
saveAs: "다른 이름으로 저장"
|
||||||
advanced: "고급"
|
|
||||||
value: "값"
|
|
||||||
createdAt: "생성된 날짜"
|
createdAt: "생성된 날짜"
|
||||||
updatedAt: "수정한 날짜"
|
updatedAt: "수정한 날짜"
|
||||||
saveConfirm: "저장하시겠습니까?"
|
|
||||||
deleteConfirm: "삭제하시겠습니까?"
|
deleteConfirm: "삭제하시겠습니까?"
|
||||||
invalidValue: "올바른 값이 아닙니다."
|
|
||||||
registry: "레지스트리"
|
|
||||||
closeAccount: "계정 폐쇄"
|
closeAccount: "계정 폐쇄"
|
||||||
currentVersion: "현재 버전"
|
|
||||||
latestVersion: "최신 버전"
|
|
||||||
youAreRunningUpToDateClient: "사용 중인 클라이언트는 최신입니다."
|
|
||||||
newVersionOfClientAvailable: "새로운 버전의 클라이언트를 이용할 수 있습니다."
|
newVersionOfClientAvailable: "새로운 버전의 클라이언트를 이용할 수 있습니다."
|
||||||
usageAmount: "사용량"
|
usageAmount: "사용량"
|
||||||
capacity: "용량"
|
capacity: "용량"
|
||||||
|
@ -710,12 +630,9 @@ editCode: "코드 수정"
|
||||||
apply: "적용"
|
apply: "적용"
|
||||||
receiveAnnouncementFromInstance: "이 인스턴스의 알림을 이메일로 수신할게요"
|
receiveAnnouncementFromInstance: "이 인스턴스의 알림을 이메일로 수신할게요"
|
||||||
emailNotification: "메일 알림"
|
emailNotification: "메일 알림"
|
||||||
publish: "게시"
|
|
||||||
inChannelSearch: "채널에서 검색"
|
|
||||||
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
|
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
|
||||||
typingUsers: "{users} 님이 입력하고 있어요.."
|
typingUsers: "{users} 님이 입력하고 있어요.."
|
||||||
jumpToSpecifiedDate: "특정 날짜로 이동"
|
jumpToSpecifiedDate: "특정 날짜로 이동"
|
||||||
showingPastTimeline: "과거의 타임라인을 표시하고 있어요"
|
|
||||||
clear: "지우기"
|
clear: "지우기"
|
||||||
markAllAsRead: "모두 읽은 상태로 표시"
|
markAllAsRead: "모두 읽은 상태로 표시"
|
||||||
goBack: "뒤로"
|
goBack: "뒤로"
|
||||||
|
@ -728,7 +645,6 @@ notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어
|
||||||
info: "정보"
|
info: "정보"
|
||||||
userInfo: "유저 정보"
|
userInfo: "유저 정보"
|
||||||
unknown: "알 수 없음"
|
unknown: "알 수 없음"
|
||||||
onlineStatus: "온라인 상태"
|
|
||||||
hideOnlineStatus: "온라인 상태 숨기기"
|
hideOnlineStatus: "온라인 상태 숨기기"
|
||||||
hideOnlineStatusDescription: "온라인 상태를 숨기면, 검색과 같은 일부 기능에 영향을 미칠 수 있습니다."
|
hideOnlineStatusDescription: "온라인 상태를 숨기면, 검색과 같은 일부 기능에 영향을 미칠 수 있습니다."
|
||||||
online: "온라인"
|
online: "온라인"
|
||||||
|
@ -749,26 +665,15 @@ switch: "전환"
|
||||||
noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습니다."
|
noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습니다."
|
||||||
noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다."
|
noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다."
|
||||||
configure: "설정하기"
|
configure: "설정하기"
|
||||||
postToGallery: "갤러리에 업로드"
|
|
||||||
gallery: "갤러리"
|
|
||||||
recentPosts: "최근 포스트"
|
recentPosts: "최근 포스트"
|
||||||
popularPosts: "인기 포스트"
|
|
||||||
shareWithNote: "노트로 공유"
|
shareWithNote: "노트로 공유"
|
||||||
expiration: "기한"
|
|
||||||
memo: "메모"
|
|
||||||
priority: "우선순위"
|
|
||||||
high: "높음"
|
|
||||||
middle: "보통"
|
|
||||||
low: "낮음"
|
|
||||||
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
|
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
|
||||||
ratio: "비율"
|
ratio: "비율"
|
||||||
previewNoteText: "본문 미리보기"
|
previewNoteText: "본문 미리보기"
|
||||||
customCss: "CSS 사용자화"
|
customCss: "CSS 사용자화"
|
||||||
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다."
|
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수\
|
||||||
global: "글로벌"
|
\ 있습니다."
|
||||||
squareAvatars: "프로필 아이콘을 사각형으로 표시"
|
squareAvatars: "프로필 아이콘을 사각형으로 표시"
|
||||||
sent: "전송"
|
|
||||||
received: "수신"
|
|
||||||
searchResult: "검색 결과"
|
searchResult: "검색 결과"
|
||||||
hashtags: "해시태그"
|
hashtags: "해시태그"
|
||||||
troubleshooting: "문제 해결"
|
troubleshooting: "문제 해결"
|
||||||
|
@ -779,7 +684,8 @@ whatIsNew: "패치 정보 보기"
|
||||||
translate: "번역"
|
translate: "번역"
|
||||||
translatedFrom: "{x}에서 번역"
|
translatedFrom: "{x}에서 번역"
|
||||||
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
|
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
|
||||||
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다."
|
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다.\
|
||||||
|
\ 사용자명은 나중에 변경할 수 없습니다."
|
||||||
keepCw: "CW 유지하기"
|
keepCw: "CW 유지하기"
|
||||||
pubSub: "Pub/Sub 계정"
|
pubSub: "Pub/Sub 계정"
|
||||||
lastCommunication: "마지막 통신"
|
lastCommunication: "마지막 통신"
|
||||||
|
@ -843,7 +749,8 @@ _ffVisibility:
|
||||||
_signup:
|
_signup:
|
||||||
almostThere: "거의 다 끝났습니다"
|
almostThere: "거의 다 끝났습니다"
|
||||||
emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
|
emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
|
||||||
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
|
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해\
|
||||||
|
\ 주세요."
|
||||||
_accountDelete:
|
_accountDelete:
|
||||||
accountDelete: "계정 삭제"
|
accountDelete: "계정 삭제"
|
||||||
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
|
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
|
||||||
|
@ -851,18 +758,10 @@ _accountDelete:
|
||||||
requestAccountDelete: "계정 삭제 요청"
|
requestAccountDelete: "계정 삭제 요청"
|
||||||
started: "삭제 작업이 시작되었습니다."
|
started: "삭제 작업이 시작되었습니다."
|
||||||
inProgress: "삭제 진행 중"
|
inProgress: "삭제 진행 중"
|
||||||
_ad:
|
|
||||||
back: "뒤로"
|
|
||||||
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
|
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
|
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
|
||||||
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
|
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
|
||||||
contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오."
|
contactAdmin: "이 인스턴스에서는 메일 기능이 지원되지 않습니다. 비밀번호를 재설정하려면 관리자에게 문의해 주십시오."
|
||||||
_gallery:
|
|
||||||
my: "내 갤러리"
|
|
||||||
liked: "좋아요 한 갤러리"
|
|
||||||
like: "좋아요!"
|
|
||||||
unlike: "좋아요 취소"
|
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "새로운 팔로워가 있습니다"
|
title: "새로운 팔로워가 있습니다"
|
||||||
|
@ -871,7 +770,6 @@ _email:
|
||||||
_plugin:
|
_plugin:
|
||||||
install: "플러그인 설치"
|
install: "플러그인 설치"
|
||||||
installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다."
|
installWarn: "신뢰할 수 없는 플러그인은 설치하지 않는 것이 좋습니다."
|
||||||
manage: "플러그인 관리"
|
|
||||||
_registry:
|
_registry:
|
||||||
scope: "범위"
|
scope: "범위"
|
||||||
key: "키"
|
key: "키"
|
||||||
|
@ -880,17 +778,16 @@ _registry:
|
||||||
createKey: "키 생성"
|
createKey: "키 생성"
|
||||||
_aboutMisskey:
|
_aboutMisskey:
|
||||||
about: "FoundKey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
|
about: "FoundKey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
|
||||||
contributors: "주요 기여자"
|
|
||||||
allContributors: "모든 기여자"
|
allContributors: "모든 기여자"
|
||||||
source: "소스 코드"
|
source: "소스 코드"
|
||||||
translation: "FoundKey를 번역하기"
|
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "열람주의로 설정된 미디어 숨기기"
|
respect: "열람주의로 설정된 미디어 숨기기"
|
||||||
ignore: "열람 주의 미디어 항상 표시"
|
ignore: "열람 주의 미디어 항상 표시"
|
||||||
force: "미디어 항상 숨기기"
|
force: "미디어 항상 숨기기"
|
||||||
_mfm:
|
_mfm:
|
||||||
cheatSheet: "MFM 도움말"
|
cheatSheet: "MFM 도움말"
|
||||||
intro: "MFM는 FoundKey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할 수 있습니다."
|
intro: "MFM는 FoundKey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할\
|
||||||
|
\ 수 있습니다."
|
||||||
dummy: "FoundKey로 연합우주의 세계가 펼쳐집니다"
|
dummy: "FoundKey로 연합우주의 세계가 펼쳐집니다"
|
||||||
mention: "멘션"
|
mention: "멘션"
|
||||||
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
|
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
|
||||||
|
@ -1001,68 +898,6 @@ _theme:
|
||||||
alreadyInstalled: "이미 설치된 테마입니다"
|
alreadyInstalled: "이미 설치된 테마입니다"
|
||||||
invalid: "테마 형식이 올바르지 않습니다"
|
invalid: "테마 형식이 올바르지 않습니다"
|
||||||
make: "테마 만들기"
|
make: "테마 만들기"
|
||||||
base: "베이스"
|
|
||||||
addConstant: "상수 추가"
|
|
||||||
constant: "상수"
|
|
||||||
defaultValue: "기본값"
|
|
||||||
color: "색"
|
|
||||||
refProp: "프로퍼티를 참조"
|
|
||||||
refConst: "상수를 참조"
|
|
||||||
key: "키"
|
|
||||||
func: "함수"
|
|
||||||
funcKind: "함수 종류"
|
|
||||||
argument: "매개변수"
|
|
||||||
basedProp: "기준으로 할 속성 이름"
|
|
||||||
alpha: "불투명도"
|
|
||||||
darken: "어두움"
|
|
||||||
lighten: "밝음"
|
|
||||||
inputConstantName: "상수 이름을 입력하세요"
|
|
||||||
importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다."
|
|
||||||
deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?"
|
|
||||||
keys:
|
|
||||||
accent: "강조 색상"
|
|
||||||
bg: "배경"
|
|
||||||
fg: "텍스트"
|
|
||||||
focus: "포커스"
|
|
||||||
indicator: "인디케이터"
|
|
||||||
panel: "패널"
|
|
||||||
shadow: "그림자"
|
|
||||||
header: "헤더"
|
|
||||||
navBg: "사이드바 배경"
|
|
||||||
navFg: "사이드바 텍스트"
|
|
||||||
navHoverFg: "사이드바 텍스트 (호버)"
|
|
||||||
navActive: "사이드바 텍스트 (활성)"
|
|
||||||
navIndicator: "사이드바 인디케이터"
|
|
||||||
link: "링크"
|
|
||||||
hashtag: "해시태그"
|
|
||||||
mention: "멘션"
|
|
||||||
mentionMe: "나에게 보낸 멘션"
|
|
||||||
renote: "Renote"
|
|
||||||
modalBg: "모달 배경"
|
|
||||||
divider: "구분선"
|
|
||||||
scrollbarHandle: "스크롤바 핸들"
|
|
||||||
scrollbarHandleHover: "스크롤바 핸들 (호버)"
|
|
||||||
dateLabelFg: "날짜 레이블 텍스트"
|
|
||||||
infoBg: "정보창 배경"
|
|
||||||
infoFg: "정보창 텍스트"
|
|
||||||
infoWarnBg: "경고창 배경"
|
|
||||||
infoWarnFg: "경고창 텍스트"
|
|
||||||
cwBg: "CW 버튼 배경"
|
|
||||||
cwFg: "CW 버튼 텍스트"
|
|
||||||
cwHoverBg: "CW 버튼 배경 (호버)"
|
|
||||||
toastBg: "알림창 배경"
|
|
||||||
toastFg: "알림창 텍스트"
|
|
||||||
buttonBg: "버튼 배경"
|
|
||||||
buttonHoverBg: "버튼 배경 (호버)"
|
|
||||||
inputBorder: "입력 필드 테두리"
|
|
||||||
listItemHoverBg: "리스트 항목 배경 (호버)"
|
|
||||||
driveFolderBg: "드라이브 폴더 배경"
|
|
||||||
wallpaperOverlay: "배경화면 오버레이"
|
|
||||||
badge: "배지"
|
|
||||||
messageBg: "채팅 배경"
|
|
||||||
accentDarken: "강조 색상 (어두움)"
|
|
||||||
accentLighten: "강조 색상 (밝음)"
|
|
||||||
fgHighlighted: "강조된 텍스트"
|
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "새 노트"
|
note: "새 노트"
|
||||||
noteMy: "내 노트"
|
noteMy: "내 노트"
|
||||||
|
@ -1100,7 +935,8 @@ _tutorial:
|
||||||
step4_1: "노트 작성을 끝내셨나요?"
|
step4_1: "노트 작성을 끝내셨나요?"
|
||||||
step4_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
|
step4_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
|
||||||
step5_1: "이제, 다른 사람을 팔로우하여 타임라인을 활기차게 만들어보도록 합시다."
|
step5_1: "이제, 다른 사람을 팔로우하여 타임라인을 활기차게 만들어보도록 합시다."
|
||||||
step5_2: "{featured}에서 이 인스턴스의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요. 마음에 드는 사람을 골라 팔로우해 보세요!"
|
step5_2: "{featured}에서 이 인스턴스의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요.\
|
||||||
|
\ 마음에 드는 사람을 골라 팔로우해 보세요!"
|
||||||
step5_3: "다른 유저를 팔로우하려면 해당 유저의 아이콘을 클릭하여 프로필 페이지를 띄운 후, 팔로우 버튼을 눌러 주세요."
|
step5_3: "다른 유저를 팔로우하려면 해당 유저의 아이콘을 클릭하여 프로필 페이지를 띄운 후, 팔로우 버튼을 눌러 주세요."
|
||||||
step5_4: "사용자에 따라 팔로우가 승인될 때까지 시간이 걸릴 수 있습니다."
|
step5_4: "사용자에 따라 팔로우가 승인될 때까지 시간이 걸릴 수 있습니다."
|
||||||
step6_1: "타임라인에 다른 사용자의 노트가 나타난다면 성공입니다."
|
step6_1: "타임라인에 다른 사용자의 노트가 나타난다면 성공입니다."
|
||||||
|
@ -1108,7 +944,7 @@ _tutorial:
|
||||||
step6_3: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
|
step6_3: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
|
||||||
step7_1: "이것으로 FoundKey의 기본 튜토리얼을 마치겠습니다. 수고하셨습니다!"
|
step7_1: "이것으로 FoundKey의 기본 튜토리얼을 마치겠습니다. 수고하셨습니다!"
|
||||||
step7_2: "FoundKey에 대해 더 알고 싶으시다면 {help}를 참고해 주세요."
|
step7_2: "FoundKey에 대해 더 알고 싶으시다면 {help}를 참고해 주세요."
|
||||||
step7_3: "그럼 FoundKey를 즐기세요! 🚀"
|
step7_3: "그럼 FoundKey를 즐기세요! \U0001F680"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "이미 설정이 완료되었습니다."
|
alreadyRegistered: "이미 설정이 완료되었습니다."
|
||||||
registerDevice: "디바이스 등록"
|
registerDevice: "디바이스 등록"
|
||||||
|
@ -1118,7 +954,8 @@ _2fa:
|
||||||
step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:"
|
step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:"
|
||||||
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
|
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
|
||||||
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
|
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
|
||||||
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
|
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할\
|
||||||
|
\ 수 있습니다."
|
||||||
_permissions:
|
_permissions:
|
||||||
"read:account": "계정의 정보를 봅니다"
|
"read:account": "계정의 정보를 봅니다"
|
||||||
"write:account": "계정의 정보를 변경합니다"
|
"write:account": "계정의 정보를 변경합니다"
|
||||||
|
@ -1148,10 +985,6 @@ _permissions:
|
||||||
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
||||||
"read:channels": "채널을 보기"
|
"read:channels": "채널을 보기"
|
||||||
"write:channels": "채널을 추가하거나 삭제합니다"
|
"write:channels": "채널을 추가하거나 삭제합니다"
|
||||||
"read:gallery": "갤러리를 봅니다"
|
|
||||||
"write:gallery": "갤러리를 추가하거나 삭제합니다"
|
|
||||||
"read:gallery-likes": "갤러리의 좋아요를 확인합니다"
|
|
||||||
"write:gallery-likes": "갤러리에 좋아요를 추가하거나 취소합니다"
|
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
|
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
|
@ -1328,7 +1161,6 @@ _relayStatus:
|
||||||
accepted: "승인됨"
|
accepted: "승인됨"
|
||||||
rejected: "거절됨"
|
rejected: "거절됨"
|
||||||
_notification:
|
_notification:
|
||||||
fileUploaded: "파일이 업로드되었습니다"
|
|
||||||
youGotMention: "{name}님이 멘션함"
|
youGotMention: "{name}님이 멘션함"
|
||||||
youGotReply: "{name}님이 답글함"
|
youGotReply: "{name}님이 답글함"
|
||||||
youGotQuote: "{name}님이 인용함"
|
youGotQuote: "{name}님이 인용함"
|
||||||
|
@ -1343,7 +1175,6 @@ _notification:
|
||||||
pollEnded: "투표 결과가 발표되었습니다"
|
pollEnded: "투표 결과가 발표되었습니다"
|
||||||
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
|
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
|
||||||
_types:
|
_types:
|
||||||
all: "전부"
|
|
||||||
follow: "팔로잉"
|
follow: "팔로잉"
|
||||||
mention: "멘션"
|
mention: "멘션"
|
||||||
reply: "답글"
|
reply: "답글"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Registreren"
|
||||||
save: "Opslaan"
|
save: "Opslaan"
|
||||||
users: "Gebruikers"
|
users: "Gebruikers"
|
||||||
addUser: "Toevoegen gebruiker"
|
addUser: "Toevoegen gebruiker"
|
||||||
favorite: "Favorieten"
|
|
||||||
favorites: "Toevoegen aan favorieten"
|
|
||||||
unfavorite: "Verwijderen uit favorieten"
|
|
||||||
pin: "Vastmaken aan profielpagina"
|
pin: "Vastmaken aan profielpagina"
|
||||||
unpin: "Losmaken van profielpagina"
|
unpin: "Losmaken van profielpagina"
|
||||||
copyContent: "Kopiëren inhoud"
|
copyContent: "Kopiëren inhoud"
|
||||||
|
|
|
@ -33,9 +33,6 @@ signup: "Zarejestruj się"
|
||||||
save: "Zapisz"
|
save: "Zapisz"
|
||||||
users: "Użytkownicy"
|
users: "Użytkownicy"
|
||||||
addUser: "Dodaj użytkownika"
|
addUser: "Dodaj użytkownika"
|
||||||
favorite: "Dodaj do ulubionych"
|
|
||||||
favorites: "Ulubione"
|
|
||||||
unfavorite: "Usuń z ulubionych"
|
|
||||||
pin: "Przypnij do profilu"
|
pin: "Przypnij do profilu"
|
||||||
unpin: "Odepnij z profilu"
|
unpin: "Odepnij z profilu"
|
||||||
copyContent: "Skopiuj zawartość"
|
copyContent: "Skopiuj zawartość"
|
||||||
|
@ -604,7 +601,6 @@ disableShowingAnimatedImages: "Nie odtwarzaj animowanych obrazów"
|
||||||
verificationEmailSent: "Wiadomość weryfikacyjna została wysłana. Odwiedź uwzględniony\
|
verificationEmailSent: "Wiadomość weryfikacyjna została wysłana. Odwiedź uwzględniony\
|
||||||
\ odnośnik, aby ukończyć weryfikację."
|
\ odnośnik, aby ukończyć weryfikację."
|
||||||
emailVerified: "Adres e-mail został potwierdzony"
|
emailVerified: "Adres e-mail został potwierdzony"
|
||||||
noteFavoritesCount: "Liczba polubionych wpisów"
|
|
||||||
pageLikesCount: "Liczba otrzymanych polubień stron"
|
pageLikesCount: "Liczba otrzymanych polubień stron"
|
||||||
pageLikedCount: "Liczba polubionych stron"
|
pageLikedCount: "Liczba polubionych stron"
|
||||||
contact: "Kontakt"
|
contact: "Kontakt"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Registrar-se"
|
||||||
save: "Guardar"
|
save: "Guardar"
|
||||||
users: "Usuários"
|
users: "Usuários"
|
||||||
addUser: "Adicionar usuário"
|
addUser: "Adicionar usuário"
|
||||||
favorite: "Favoritar"
|
|
||||||
favorites: "Favoritar"
|
|
||||||
unfavorite: "Remover dos favoritos"
|
|
||||||
pin: "Afixar no perfil"
|
pin: "Afixar no perfil"
|
||||||
unpin: "Desafixar do perfil"
|
unpin: "Desafixar do perfil"
|
||||||
copyContent: "Copiar conteúdos"
|
copyContent: "Copiar conteúdos"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Înregistrează-te"
|
||||||
save: "Salvează"
|
save: "Salvează"
|
||||||
users: "Utilizatori"
|
users: "Utilizatori"
|
||||||
addUser: "Adăugă utilizator"
|
addUser: "Adăugă utilizator"
|
||||||
favorite: "Adaugă la favorite"
|
|
||||||
favorites: "Favorite"
|
|
||||||
unfavorite: "Elimină din favorite"
|
|
||||||
pin: "Fixează pe profil"
|
pin: "Fixează pe profil"
|
||||||
unpin: "Anulati fixare"
|
unpin: "Anulati fixare"
|
||||||
copyContent: "Copiază conținutul"
|
copyContent: "Copiază conținutul"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Регистрация"
|
||||||
save: "Сохранить"
|
save: "Сохранить"
|
||||||
users: "Пользователи"
|
users: "Пользователи"
|
||||||
addUser: "Добавить пользователя"
|
addUser: "Добавить пользователя"
|
||||||
favorite: "В избранное"
|
|
||||||
favorites: "Избранное"
|
|
||||||
unfavorite: "Убрать из избранного"
|
|
||||||
pin: "Закрепить в профиле"
|
pin: "Закрепить в профиле"
|
||||||
unpin: "Открепить от профиля"
|
unpin: "Открепить от профиля"
|
||||||
copyContent: "Скопировать содержимое"
|
copyContent: "Скопировать содержимое"
|
||||||
|
@ -631,7 +628,6 @@ disableShowingAnimatedImages: "Не проигрывать анимацию"
|
||||||
verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста,\
|
verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста,\
|
||||||
\ по ссылке из письма, чтобы завершить проверку."
|
\ по ссылке из письма, чтобы завершить проверку."
|
||||||
emailVerified: "Адрес электронной почты подтверждён."
|
emailVerified: "Адрес электронной почты подтверждён."
|
||||||
noteFavoritesCount: "Количество добавленного в избранное"
|
|
||||||
pageLikesCount: "Количество понравившихся страниц"
|
pageLikesCount: "Количество понравившихся страниц"
|
||||||
pageLikedCount: "Количество страниц, понравившихся другим"
|
pageLikedCount: "Количество страниц, понравившихся другим"
|
||||||
contact: "Как связаться"
|
contact: "Как связаться"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Registrovať"
|
||||||
save: "Uložiť"
|
save: "Uložiť"
|
||||||
users: "Používatelia"
|
users: "Používatelia"
|
||||||
addUser: "Pridať používateľa"
|
addUser: "Pridať používateľa"
|
||||||
favorite: "Páči sa mi"
|
|
||||||
favorites: "Obľúbené"
|
|
||||||
unfavorite: "Nepáči sa mi"
|
|
||||||
pin: "Pripnúť"
|
pin: "Pripnúť"
|
||||||
unpin: "Odopnúť"
|
unpin: "Odopnúť"
|
||||||
copyContent: "Kopírovať obsah"
|
copyContent: "Kopírovať obsah"
|
||||||
|
@ -623,7 +620,6 @@ disableShowingAnimatedImages: "Neprehrávať animované obrázky"
|
||||||
verificationEmailSent: "Odoslali sme overovací email. Overenie dokončíte kliknutím\
|
verificationEmailSent: "Odoslali sme overovací email. Overenie dokončíte kliknutím\
|
||||||
\ na odkaz v emaili."
|
\ na odkaz v emaili."
|
||||||
emailVerified: "Email overený"
|
emailVerified: "Email overený"
|
||||||
noteFavoritesCount: "Počet obľúbených poznámok"
|
|
||||||
pageLikesCount: "Počet obľúbených stránok"
|
pageLikesCount: "Počet obľúbených stránok"
|
||||||
pageLikedCount: "Počet prijatých \"páči sa mi\""
|
pageLikedCount: "Počet prijatých \"páči sa mi\""
|
||||||
contact: "Kontakt"
|
contact: "Kontakt"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Registrera"
|
||||||
save: "Spara"
|
save: "Spara"
|
||||||
users: "Användare"
|
users: "Användare"
|
||||||
addUser: "Lägg till användare"
|
addUser: "Lägg till användare"
|
||||||
favorite: "Lägg till i favoriter"
|
|
||||||
favorites: "Favoriter"
|
|
||||||
unfavorite: "Avfavorisera"
|
|
||||||
pin: "Fäst till profil"
|
pin: "Fäst till profil"
|
||||||
unpin: "Lossa från profil"
|
unpin: "Lossa från profil"
|
||||||
copyContent: "Kopiera innehåll"
|
copyContent: "Kopiera innehåll"
|
||||||
|
|
|
@ -28,9 +28,6 @@ logout: "Çıkış Yap"
|
||||||
signup: "Kayıt Ol"
|
signup: "Kayıt Ol"
|
||||||
users: "Kullanıcı"
|
users: "Kullanıcı"
|
||||||
addUser: "Kullanıcı Ekle"
|
addUser: "Kullanıcı Ekle"
|
||||||
favorite: "Favoriler"
|
|
||||||
favorites: "Favoriler"
|
|
||||||
unfavorite: "Favorilerden Kaldır"
|
|
||||||
pin: "Sabitlenmiş"
|
pin: "Sabitlenmiş"
|
||||||
unpin: "Sabitlemeyi kaldır"
|
unpin: "Sabitlemeyi kaldır"
|
||||||
copyContent: "İçeriği kopyala"
|
copyContent: "İçeriği kopyala"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Реєстрація"
|
||||||
save: "Зберегти"
|
save: "Зберегти"
|
||||||
users: "Користувачі"
|
users: "Користувачі"
|
||||||
addUser: "Додати користувача"
|
addUser: "Додати користувача"
|
||||||
favorite: "Обране"
|
|
||||||
favorites: "Обране"
|
|
||||||
unfavorite: "Видалити з обраного"
|
|
||||||
pin: "Закріпити"
|
pin: "Закріпити"
|
||||||
unpin: "Відкріпити"
|
unpin: "Відкріпити"
|
||||||
copyContent: "Скопіювати контент"
|
copyContent: "Скопіювати контент"
|
||||||
|
@ -631,7 +628,6 @@ disableShowingAnimatedImages: "Не програвати анімовані зо
|
||||||
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть\
|
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть\
|
||||||
\ по посиланню в листі для підтвердження."
|
\ по посиланню в листі для підтвердження."
|
||||||
emailVerified: "Електронну пошту підтверджено."
|
emailVerified: "Електронну пошту підтверджено."
|
||||||
noteFavoritesCount: "Кількість улюблених нотаток"
|
|
||||||
pageLikesCount: "Кількість отриманих вподобань сторінки"
|
pageLikesCount: "Кількість отриманих вподобань сторінки"
|
||||||
pageLikedCount: "Кількість вподобаних сторінок"
|
pageLikedCount: "Кількість вподобаних сторінок"
|
||||||
contact: "Контакт"
|
contact: "Контакт"
|
||||||
|
|
|
@ -32,9 +32,6 @@ signup: "Đăng ký"
|
||||||
save: "Lưu"
|
save: "Lưu"
|
||||||
users: "Người dùng"
|
users: "Người dùng"
|
||||||
addUser: "Thêm người dùng"
|
addUser: "Thêm người dùng"
|
||||||
favorite: "Thêm vào yêu thích"
|
|
||||||
favorites: "Lượt thích"
|
|
||||||
unfavorite: "Bỏ thích"
|
|
||||||
pin: "Ghim"
|
pin: "Ghim"
|
||||||
unpin: "Bỏ ghim"
|
unpin: "Bỏ ghim"
|
||||||
copyContent: "Chép nội dung"
|
copyContent: "Chép nội dung"
|
||||||
|
@ -628,7 +625,6 @@ disableShowingAnimatedImages: "Không phát ảnh động"
|
||||||
verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết\
|
verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết\
|
||||||
\ đính kèm để hoàn tất xác minh."
|
\ đính kèm để hoàn tất xác minh."
|
||||||
emailVerified: "Email đã được xác minh"
|
emailVerified: "Email đã được xác minh"
|
||||||
noteFavoritesCount: "Số lượng tút yêu thích"
|
|
||||||
pageLikesCount: "Số lượng trang đã thích"
|
pageLikesCount: "Số lượng trang đã thích"
|
||||||
pageLikedCount: "Số lượng thích trang đã nhận"
|
pageLikedCount: "Số lượng thích trang đã nhận"
|
||||||
contact: "Liên hệ"
|
contact: "Liên hệ"
|
||||||
|
|
|
@ -30,9 +30,6 @@ signup: "新用户注册"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
users: "用户"
|
users: "用户"
|
||||||
addUser: "添加用户"
|
addUser: "添加用户"
|
||||||
favorite: "收藏"
|
|
||||||
favorites: "收藏"
|
|
||||||
unfavorite: "取消收藏"
|
|
||||||
pin: "置顶"
|
pin: "置顶"
|
||||||
unpin: "取消置顶"
|
unpin: "取消置顶"
|
||||||
copyContent: "复制内容"
|
copyContent: "复制内容"
|
||||||
|
@ -583,7 +580,6 @@ loadRawImages: "添加附件图像的缩略图时使用原始图像质量"
|
||||||
disableShowingAnimatedImages: "不播放动画"
|
disableShowingAnimatedImages: "不播放动画"
|
||||||
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
|
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
|
||||||
emailVerified: "电子邮件地址已验证"
|
emailVerified: "电子邮件地址已验证"
|
||||||
noteFavoritesCount: "收藏的帖子数"
|
|
||||||
pageLikesCount: "页面点赞次数"
|
pageLikesCount: "页面点赞次数"
|
||||||
pageLikedCount: "页面被点赞次数"
|
pageLikedCount: "页面被点赞次数"
|
||||||
contact: "联系人"
|
contact: "联系人"
|
||||||
|
|
|
@ -30,9 +30,6 @@ signup: "註冊"
|
||||||
save: "儲存"
|
save: "儲存"
|
||||||
users: "使用者"
|
users: "使用者"
|
||||||
addUser: "新增使用者"
|
addUser: "新增使用者"
|
||||||
favorite: "我的最愛"
|
|
||||||
favorites: "我的最愛"
|
|
||||||
unfavorite: "從我的最愛中移除"
|
|
||||||
pin: "置頂"
|
pin: "置頂"
|
||||||
unpin: "取消置頂"
|
unpin: "取消置頂"
|
||||||
copyContent: "複製內容"
|
copyContent: "複製內容"
|
||||||
|
@ -582,7 +579,6 @@ loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
|
||||||
disableShowingAnimatedImages: "不播放動態圖檔"
|
disableShowingAnimatedImages: "不播放動態圖檔"
|
||||||
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
|
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
|
||||||
emailVerified: "已成功驗證您的電郵"
|
emailVerified: "已成功驗證您的電郵"
|
||||||
noteFavoritesCount: "我的最愛貼文的數目"
|
|
||||||
pageLikesCount: "頁面被按讚次數"
|
pageLikesCount: "頁面被按讚次數"
|
||||||
pageLikedCount: "頁面被按讚次數"
|
pageLikedCount: "頁面被按讚次數"
|
||||||
contact: "聯絡人"
|
contact: "聯絡人"
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
"extension": ["ts","js","cjs","mjs"],
|
|
||||||
"node-option": [
|
"node-option": [
|
||||||
"experimental-specifier-resolution=node",
|
"experimental-specifier-resolution=node"
|
||||||
"loader=./test/loader.js"
|
|
||||||
],
|
],
|
||||||
"slow": 1000,
|
"slow": 1000,
|
||||||
"timeout": 30000,
|
"timeout": 30000,
|
||||||
|
|
|
@ -6,7 +6,7 @@ export class removeFavourites1685126322423 {
|
||||||
WITH "new_clips" AS (
|
WITH "new_clips" AS (
|
||||||
INSERT INTO "clip" ("id", "createdAt", "userId", "name")
|
INSERT INTO "clip" ("id", "createdAt", "userId", "name")
|
||||||
SELECT
|
SELECT
|
||||||
RIGHT(GEN_RANDOM_UUID()::text, 10),
|
LEFT(MD5(RANDOM()::text), 10),
|
||||||
NOW(),
|
NOW(),
|
||||||
"userId",
|
"userId",
|
||||||
'⭐'
|
'⭐'
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||||
"watch": "node watch.mjs",
|
"watch": "node watch.mjs",
|
||||||
"lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts",
|
"lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts",
|
||||||
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
|
"mocha": "NODE_ENV=test mocha",
|
||||||
"migrate": "npx typeorm migration:run -d ormconfig.js",
|
"migrate": "npx typeorm migration:run -d ormconfig.js",
|
||||||
"start": "node --experimental-json-modules ./built/index.js",
|
"start": "node --experimental-json-modules ./built/index.js",
|
||||||
"start:test": "cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js",
|
"start:test": "NODE_ENV=test node --experimental-json-modules ./built/index.js",
|
||||||
"test": "npm run mocha"
|
"test": "npm run mocha"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -164,7 +164,6 @@
|
||||||
"@types/ws": "8.5.3",
|
"@types/ws": "8.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||||
"@typescript-eslint/parser": "^5.46.1",
|
"@typescript-eslint/parser": "^5.46.1",
|
||||||
"cross-env": "7.0.3",
|
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.29.0",
|
||||||
"eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules",
|
"eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
|
|
@ -20,10 +20,11 @@ export async function checkWordMute(note: NoteLike, me: UserLike | null | undefi
|
||||||
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
||||||
|
|
||||||
if (text === '') return false;
|
if (text === '') return false;
|
||||||
|
const textLower = text.toLowerCase();
|
||||||
|
|
||||||
const matched = mutedWords.some(filter => {
|
const matched = mutedWords.some(filter => {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
return filter.every(keyword => text.includes(keyword));
|
return filter.every(keyword => textLower.includes(keyword.toLowerCase()));
|
||||||
} else {
|
} else {
|
||||||
// represents RegExp
|
// represents RegExp
|
||||||
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
||||||
|
|
|
@ -302,10 +302,10 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
|
|
||||||
const ffVisible = await this.areFollowersVisibleTo(user, me);
|
const ffVisible = await this.areFollowersVisibleTo(user, me);
|
||||||
|
|
||||||
const followingCount = opts.detail ? null :
|
const followingCount = !opts.detail ? null :
|
||||||
ffVisible ? user.followingCount : null;
|
ffVisible ? user.followingCount : null;
|
||||||
|
|
||||||
const followersCount = opts.detail ? null :
|
const followersCount = !opts.detail ? null :
|
||||||
ffVisible ? user.followersCount : null;
|
ffVisible ? user.followersCount : null;
|
||||||
|
|
||||||
const packed = {
|
const packed = {
|
||||||
|
|
|
@ -32,11 +32,6 @@ export const packedUserLiteSchema = {
|
||||||
type: 'any',
|
type: 'any',
|
||||||
nullable: true, optional: false,
|
nullable: true, optional: false,
|
||||||
},
|
},
|
||||||
avatarColor: {
|
|
||||||
type: 'any',
|
|
||||||
nullable: true, optional: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
isAdmin: {
|
isAdmin: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: true,
|
nullable: false, optional: true,
|
||||||
|
@ -122,11 +117,6 @@ export const packedUserDetailedNotMeOnlySchema = {
|
||||||
type: 'any',
|
type: 'any',
|
||||||
nullable: true, optional: false,
|
nullable: true, optional: false,
|
||||||
},
|
},
|
||||||
bannerColor: {
|
|
||||||
type: 'any',
|
|
||||||
nullable: true, optional: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
isLocked: {
|
isLocked: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
|
|
|
@ -10,12 +10,17 @@ const logger = queueLogger.createSubLogger('check-expired');
|
||||||
export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
|
export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
|
||||||
logger.info('Checking expired data...');
|
logger.info('Checking expired data...');
|
||||||
|
|
||||||
const expiredMutings = await Mutings.createQueryBuilder('muting')
|
const OlderThan = (millis: number) => {
|
||||||
|
return LessThan(new Date(new Date().getTime() - millis));
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
Mutings.createQueryBuilder('muting')
|
||||||
.where('muting.expiresAt IS NOT NULL')
|
.where('muting.expiresAt IS NOT NULL')
|
||||||
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
||||||
.innerJoinAndSelect('muting.mutee', 'mutee')
|
.innerJoinAndSelect('muting.mutee', 'mutee')
|
||||||
.getMany();
|
.getMany()
|
||||||
|
.then(async (expiredMutings) => {
|
||||||
if (expiredMutings.length > 0) {
|
if (expiredMutings.length > 0) {
|
||||||
await Mutings.delete({
|
await Mutings.delete({
|
||||||
id: In(expiredMutings.map(m => m.id)),
|
id: In(expiredMutings.map(m => m.id)),
|
||||||
|
@ -25,37 +30,29 @@ export async function checkExpired(job: Bull.Job<Record<string, unknown>>, done:
|
||||||
publishUserEvent(m.muterId, 'unmute', m.mutee!);
|
publishUserEvent(m.muterId, 'unmute', m.mutee!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
const OlderThan = (millis: number) => {
|
Signins.delete({
|
||||||
return LessThan(new Date(new Date().getTime() - millis));
|
|
||||||
};
|
|
||||||
|
|
||||||
await Signins.delete({
|
|
||||||
createdAt: OlderThan(2 * MONTH),
|
createdAt: OlderThan(2 * MONTH),
|
||||||
});
|
}),
|
||||||
|
AttestationChallenges.delete({
|
||||||
await AttestationChallenges.delete({
|
|
||||||
createdAt: OlderThan(5 * MINUTE),
|
createdAt: OlderThan(5 * MINUTE),
|
||||||
});
|
}),
|
||||||
|
PasswordResetRequests.delete({
|
||||||
await PasswordResetRequests.delete({
|
|
||||||
// this timing should be the same as in @/server/api/endpoints/reset-password.ts
|
// this timing should be the same as in @/server/api/endpoints/reset-password.ts
|
||||||
createdAt: OlderThan(30 * MINUTE),
|
createdAt: OlderThan(30 * MINUTE),
|
||||||
});
|
}),
|
||||||
|
AuthSessions.delete({
|
||||||
await AuthSessions.delete({
|
|
||||||
createdAt: OlderThan(15 * MINUTE),
|
createdAt: OlderThan(15 * MINUTE),
|
||||||
});
|
}),
|
||||||
|
Notifications.delete({
|
||||||
await Notifications.delete({
|
|
||||||
isRead: true,
|
isRead: true,
|
||||||
createdAt: OlderThan(3 * MONTH),
|
createdAt: OlderThan(3 * MONTH),
|
||||||
});
|
}),
|
||||||
|
Users.delete({
|
||||||
await Users.delete({
|
|
||||||
// delete users where the deletion status reference count has come down to zero
|
// delete users where the deletion status reference count has come down to zero
|
||||||
isDeleted: 0,
|
isDeleted: 0,
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
logger.succ('Deleted expired data.');
|
logger.succ('Deleted expired data.');
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,6 @@ import { shouldBlockInstance } from '@/misc/should-block-instance.js';
|
||||||
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
||||||
const uri = getApId(activity);
|
const uri = getApId(activity);
|
||||||
|
|
||||||
if (actor.isSuspended) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel if the announced from host is blocked.
|
// Cancel if the announced from host is blocked.
|
||||||
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { IRemoteUser } from '@/models/entities/user.js';
|
import { IRemoteUser } from '@/models/entities/user.js';
|
||||||
import { createReaction } from '@/services/note/reaction/create.js';
|
import { createReaction } from '@/services/note/reaction/create.js';
|
||||||
import { ILike, getApId } from '../type.js';
|
import { ILike, getApId } from '../type.js';
|
||||||
import { fetchNote, extractEmojis } from '../models/note.js';
|
import { fetchNote } from '../models/note.js';
|
||||||
|
import { extractEmojis } from '../models/tag.js';
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||||
const targetUri = getApId(activity.object);
|
const targetUri = getApId(activity.object);
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { unique, toArray, toSingle } from '@/prelude/array.js';
|
||||||
import { vote } from '@/services/note/polls/vote.js';
|
import { vote } from '@/services/note/polls/vote.js';
|
||||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||||
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
|
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
|
||||||
import { extractDbHost, toPuny } from '@/misc/convert-host.js';
|
import { extractDbHost } from '@/misc/convert-host.js';
|
||||||
import { Emojis, Polls, MessagingMessages } from '@/models/index.js';
|
import { Polls, MessagingMessages } from '@/models/index.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { Emoji } from '@/models/entities/emoji.js';
|
import { Emoji } from '@/models/entities/emoji.js';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
|
@ -19,12 +19,12 @@ import { fromHtml } from '@/mfm/from-html.js';
|
||||||
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
|
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
|
||||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||||
import { parseAudience } from '../audience.js';
|
import { parseAudience } from '../audience.js';
|
||||||
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
|
import { IObject, getOneApId, getApId, getOneApHrefNullable, isPost, IPost, getApType } from '../type.js';
|
||||||
import { DbResolver } from '../db-resolver.js';
|
import { DbResolver } from '../db-resolver.js';
|
||||||
import { apLogger } from '../logger.js';
|
import { apLogger } from '../logger.js';
|
||||||
import { resolvePerson } from './person.js';
|
import { resolvePerson } from './person.js';
|
||||||
import { resolveImage } from './image.js';
|
import { resolveImage } from './image.js';
|
||||||
import { extractApHashtags, extractQuoteUrl } from './tag.js';
|
import { extractApHashtags, extractQuoteUrl, extractEmojis } from './tag.js';
|
||||||
import { extractPollFromQuestion } from './question.js';
|
import { extractPollFromQuestion } from './question.js';
|
||||||
import { extractApMentions } from './mention.js';
|
import { extractApMentions } from './mention.js';
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export function validateNote(object: IObject): Error | null {
|
||||||
return new Error('invalid Note: object is null');
|
return new Error('invalid Note: object is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validPost.includes(getApType(object))) {
|
if (!isPost(object)) {
|
||||||
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
|
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,10 +52,63 @@ export function validateNote(object: IObject): Error | null {
|
||||||
if (attributedToHost !== expectHost) {
|
if (attributedToHost !== expectHost) {
|
||||||
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${attributedToHost}`);
|
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${attributedToHost}`);
|
||||||
}
|
}
|
||||||
|
if (attributedToHost === config.hostname) {
|
||||||
|
return new Error('invalid Note: by local author');
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to process the content of a note, reusable in createNote and updateNote.
|
||||||
|
*/
|
||||||
|
async function processContent(actor: IRemoteUser, note: IPost, quoteUri: string | null, resolver: Resolver):
|
||||||
|
Promise<{
|
||||||
|
cw: string | null,
|
||||||
|
files: DriveFile[],
|
||||||
|
text: string | null,
|
||||||
|
apEmoji: Emoji[],
|
||||||
|
apHashtags: string[],
|
||||||
|
url: string,
|
||||||
|
name: string
|
||||||
|
}>
|
||||||
|
{
|
||||||
|
// Attachments handling
|
||||||
|
// TODO: attachments are not necessarily images
|
||||||
|
const limit = promiseLimit(2);
|
||||||
|
|
||||||
|
const attachments = toArray(note.attachment);
|
||||||
|
const files = attachments
|
||||||
|
// If the note is marked as sensitive, the images should be marked sensitive too.
|
||||||
|
.map(attach => attach.sensitive |= note.sensitive)
|
||||||
|
? (await Promise.all(attachments.map(x => limit(() => resolveImage(actor, x, resolver)) as Promise<DriveFile>)))
|
||||||
|
.filter(image => image != null)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// text parsing
|
||||||
|
let text: string | null = null;
|
||||||
|
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
|
||||||
|
text = note.source.content;
|
||||||
|
} else if (typeof note.content === 'string') {
|
||||||
|
text = fromHtml(note.content, quoteUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis = await extractEmojis(note.tag || [], extractDbHost(getApId(note))).catch(e => {
|
||||||
|
apLogger.info(`extractEmojis: ${e}`);
|
||||||
|
return [] as Emoji[];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
cw: note.summary === '' ? null : note.summary,
|
||||||
|
files,
|
||||||
|
text,
|
||||||
|
apEmojis: emojis.map(emoji => emoji.name),
|
||||||
|
apHashtags: await extractApHashtags(note.tag),
|
||||||
|
url: getOneApHrefNullable(note.url),
|
||||||
|
name: note.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch Note.
|
* Fetch Note.
|
||||||
*
|
*
|
||||||
|
@ -96,33 +149,17 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
||||||
let visibility = noteAudience.visibility;
|
let visibility = noteAudience.visibility;
|
||||||
const visibleUsers = noteAudience.visibleUsers;
|
const visibleUsers = noteAudience.visibleUsers;
|
||||||
|
|
||||||
// Audience (to, cc) が指定されてなかった場合
|
// If audience(to,cc) was not specified
|
||||||
if (visibility === 'specified' && visibleUsers.length === 0) {
|
if (visibility === 'specified' && visibleUsers.length === 0) {
|
||||||
if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
|
// TODO derive audience from context (e.g. whose inbox this was in?)
|
||||||
// こちらから匿名GET出来たものならばpublic
|
throw new Error('audience not understood');
|
||||||
visibility = 'public';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let isTalk = note._misskey_talk && visibility === 'specified';
|
let isTalk = note._misskey_talk && visibility === 'specified';
|
||||||
|
|
||||||
const apMentions = await extractApMentions(note.tag, resolver);
|
const apMentions = await extractApMentions(note.tag, resolver);
|
||||||
const apHashtags = await extractApHashtags(note.tag);
|
|
||||||
|
|
||||||
// 添付ファイル
|
// Reply handling
|
||||||
// TODO: attachmentは必ずしもImageではない
|
|
||||||
// TODO: attachmentは必ずしも配列ではない
|
|
||||||
// Noteがsensitiveなら添付もsensitiveにする
|
|
||||||
const limit = promiseLimit(2);
|
|
||||||
|
|
||||||
note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : [];
|
|
||||||
const files = note.attachment
|
|
||||||
.map(attach => attach.sensitive = note.sensitive)
|
|
||||||
? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x, resolver)) as Promise<DriveFile>)))
|
|
||||||
.filter(image => image != null)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// リプライ
|
|
||||||
const reply: Note | null = note.inReplyTo
|
const reply: Note | null = note.inReplyTo
|
||||||
? await resolveNote(note.inReplyTo, resolver).then(x => {
|
? await resolveNote(note.inReplyTo, resolver).then(x => {
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
|
@ -132,7 +169,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}).catch(async e => {
|
}).catch(async e => {
|
||||||
// トークだったらinReplyToのエラーは無視
|
// ignore inReplyTo if it is a messaging message
|
||||||
const uri = getApId(note.inReplyTo);
|
const uri = getApId(note.inReplyTo);
|
||||||
if (uri.startsWith(config.url + '/')) {
|
if (uri.startsWith(config.url + '/')) {
|
||||||
const id = uri.split('/').pop();
|
const id = uri.split('/').pop();
|
||||||
|
@ -197,16 +234,6 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cw = note.summary === '' ? null : note.summary;
|
|
||||||
|
|
||||||
// text parsing
|
|
||||||
let text: string | null = null;
|
|
||||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
|
|
||||||
text = note.source.content;
|
|
||||||
} else if (typeof note.content === 'string') {
|
|
||||||
text = fromHtml(note.content, quote?.uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// vote
|
// vote
|
||||||
if (reply && reply.hasPoll) {
|
if (reply && reply.hasPoll) {
|
||||||
const poll = await Polls.findOneByOrFail({ noteId: reply.id });
|
const poll = await Polls.findOneByOrFail({ noteId: reply.id });
|
||||||
|
@ -218,7 +245,7 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
||||||
apLogger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
apLogger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
||||||
await vote(actor, reply, index);
|
await vote(actor, reply, index);
|
||||||
|
|
||||||
// リモートフォロワーにUpdate配信
|
// Federate an Update to other servers
|
||||||
deliverQuestionUpdate(reply.id);
|
deliverQuestionUpdate(reply.id);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -229,40 +256,36 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = await extractEmojis(note.tag || [], actor.host).catch(e => {
|
|
||||||
apLogger.info(`extractEmojis: ${e}`);
|
|
||||||
return [] as Emoji[];
|
|
||||||
});
|
|
||||||
|
|
||||||
const apEmojis = emojis.map(emoji => emoji.name);
|
|
||||||
|
|
||||||
const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined);
|
const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined);
|
||||||
|
|
||||||
|
const processedContent = await processContent(actor, note, quote?.uri, resolver);
|
||||||
|
|
||||||
if (isTalk) {
|
if (isTalk) {
|
||||||
for (const recipient of visibleUsers) {
|
for (const recipient of visibleUsers) {
|
||||||
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
|
await createMessage(
|
||||||
|
actor,
|
||||||
|
recipient,
|
||||||
|
undefined,
|
||||||
|
processedContent.text,
|
||||||
|
processedContent.files[0] ?? null,
|
||||||
|
object.id
|
||||||
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
return await post(actor, {
|
return await post(actor, {
|
||||||
|
...processedContent,
|
||||||
createdAt: note.published ? new Date(note.published) : null,
|
createdAt: note.published ? new Date(note.published) : null,
|
||||||
files,
|
|
||||||
reply,
|
reply,
|
||||||
renote: quote,
|
renote: quote,
|
||||||
name: note.name,
|
|
||||||
cw,
|
|
||||||
text,
|
|
||||||
localOnly: false,
|
localOnly: false,
|
||||||
visibility,
|
visibility,
|
||||||
visibleUsers,
|
visibleUsers,
|
||||||
apMentions,
|
apMentions,
|
||||||
apHashtags,
|
|
||||||
apEmojis,
|
|
||||||
poll,
|
poll,
|
||||||
uri: note.id,
|
uri: note.id,
|
||||||
url: getOneApHrefNullable(note.url),
|
|
||||||
}, silent);
|
}, silent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -301,59 +324,3 @@ export async function resolveNote(value: string | IObject, resolver: Resolver):
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractEmojis(tags: IObject | IObject[], idnHost: string): Promise<Emoji[]> {
|
|
||||||
const host = toPuny(idnHost);
|
|
||||||
|
|
||||||
if (!tags) return [];
|
|
||||||
|
|
||||||
const eomjiTags = toArray(tags).filter(isEmoji);
|
|
||||||
|
|
||||||
return await Promise.all(eomjiTags.map(async tag => {
|
|
||||||
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
|
|
||||||
tag.icon = toSingle(tag.icon);
|
|
||||||
|
|
||||||
const exists = await Emojis.findOneBy({
|
|
||||||
host,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
if ((tag.updated != null && exists.updatedAt == null)
|
|
||||||
|| (tag.id != null && exists.uri == null)
|
|
||||||
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|
|
||||||
|| (tag.icon!.url !== exists.originalUrl)
|
|
||||||
) {
|
|
||||||
await Emojis.update({
|
|
||||||
host,
|
|
||||||
name,
|
|
||||||
}, {
|
|
||||||
uri: tag.id,
|
|
||||||
originalUrl: tag.icon!.url,
|
|
||||||
publicUrl: tag.icon!.url,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return await Emojis.findOneBy({
|
|
||||||
host,
|
|
||||||
name,
|
|
||||||
}) as Emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
apLogger.info(`register emoji host=${host}, name=${name}`);
|
|
||||||
|
|
||||||
return await Emojis.insert({
|
|
||||||
id: genId(),
|
|
||||||
host,
|
|
||||||
name,
|
|
||||||
uri: tag.id,
|
|
||||||
originalUrl: tag.icon!.url,
|
|
||||||
publicUrl: tag.icon!.url,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
aliases: [],
|
|
||||||
} as Partial<Emoji>).then(x => Emojis.findOneByOrFail(x.identifiers[0]));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ import { fromHtml } from '@/mfm/from-html.js';
|
||||||
import { Resolver } from '@/remote/activitypub/resolver.js';
|
import { Resolver } from '@/remote/activitypub/resolver.js';
|
||||||
import { apLogger } from '../logger.js';
|
import { apLogger } from '../logger.js';
|
||||||
import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, getApType, isActor } from '../type.js';
|
import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, getApType, isActor } from '../type.js';
|
||||||
import { extractApHashtags } from './tag.js';
|
import { extractApHashtags, extractEmojis } from './tag.js';
|
||||||
import { resolveNote, extractEmojis } from './note.js';
|
import { resolveNote } from './note.js';
|
||||||
import { resolveImage } from './image.js';
|
import { resolveImage } from './image.js';
|
||||||
|
|
||||||
const nameLength = 128;
|
const nameLength = 128;
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { toArray } from '@/prelude/array.js';
|
import { toArray, toSingle } from '@/prelude/array.js';
|
||||||
import { IObject, isHashtag, IApHashtag, isLink, ILink } from '../type.js';
|
import { IObject, isHashtag, IApHashtag, isLink, ILink, isEmoji } from '../type.js';
|
||||||
|
import { toPuny } from '@/misc/convert-host.js';
|
||||||
|
import { Emojis } from '@/models/index.js';
|
||||||
|
import { Emoji } from '@/models/entities/emoji.js';
|
||||||
|
import { apLogger } from '@/remote/activitypub/logger.js';
|
||||||
|
import { genId } from '@/misc/gen-id.js';
|
||||||
|
|
||||||
export function extractApHashtags(tags: IObject | IObject[] | null | undefined) {
|
export function extractApHashtags(tags: IObject | IObject[] | null | undefined): string[] {
|
||||||
if (tags == null) return [];
|
if (tags == null) return [];
|
||||||
|
|
||||||
const hashtags = extractApHashtagObjects(tags);
|
const hashtags = extractApHashtagObjects(tags);
|
||||||
|
@ -47,3 +52,59 @@ export function extractQuoteUrl(tags: IObject | IObject[] | null | undefined): s
|
||||||
// If there is more than one quote, we just pick the first/a random one.
|
// If there is more than one quote, we just pick the first/a random one.
|
||||||
else return quotes[0].href;
|
else return quotes[0].href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function extractEmojis(tags: IObject | IObject[], idnHost: string): Promise<Emoji[]> {
|
||||||
|
const host = toPuny(idnHost);
|
||||||
|
|
||||||
|
if (!tags) return [];
|
||||||
|
|
||||||
|
const eomjiTags = toArray(tags).filter(isEmoji);
|
||||||
|
|
||||||
|
return await Promise.all(eomjiTags.map(async tag => {
|
||||||
|
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
|
||||||
|
tag.icon = toSingle(tag.icon);
|
||||||
|
|
||||||
|
const exists = await Emojis.findOneBy({
|
||||||
|
host,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
if ((tag.updated != null && exists.updatedAt == null)
|
||||||
|
|| (tag.id != null && exists.uri == null)
|
||||||
|
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|
||||||
|
|| (tag.icon!.url !== exists.originalUrl)
|
||||||
|
) {
|
||||||
|
await Emojis.update({
|
||||||
|
host,
|
||||||
|
name,
|
||||||
|
}, {
|
||||||
|
uri: tag.id,
|
||||||
|
originalUrl: tag.icon!.url,
|
||||||
|
publicUrl: tag.icon!.url,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Emojis.findOneBy({
|
||||||
|
host,
|
||||||
|
name,
|
||||||
|
}) as Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
apLogger.info(`register emoji host=${host}, name=${name}`);
|
||||||
|
|
||||||
|
return await Emojis.insert({
|
||||||
|
id: genId(),
|
||||||
|
host,
|
||||||
|
name,
|
||||||
|
uri: tag.id,
|
||||||
|
originalUrl: tag.icon!.url,
|
||||||
|
publicUrl: tag.icon!.url,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
aliases: [],
|
||||||
|
} as Partial<Emoji>).then(x => Emojis.findOneByOrFail(x.identifiers[0]));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
|
@ -17,27 +17,56 @@ export const renderActivity = (x: any): IActivity | null => {
|
||||||
'https://www.w3.org/ns/activitystreams',
|
'https://www.w3.org/ns/activitystreams',
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
{
|
{
|
||||||
|
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
||||||
// as non-standards
|
// as non-standards
|
||||||
manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
|
manuallyApprovesFollowers: {
|
||||||
sensitive: 'as:sensitive',
|
'@id': 'as:manuallyApprovesFollowers',
|
||||||
|
'@type': 'xsd:boolean',
|
||||||
|
},
|
||||||
|
sensitive: {
|
||||||
|
'@id': 'as:sensitive',
|
||||||
|
'@type': 'xsd:boolean',
|
||||||
|
},
|
||||||
Hashtag: 'as:Hashtag',
|
Hashtag: 'as:Hashtag',
|
||||||
// Mastodon
|
// Mastodon
|
||||||
toot: 'http://joinmastodon.org/ns#',
|
toot: 'http://joinmastodon.org/ns#',
|
||||||
Emoji: 'toot:Emoji',
|
Emoji: 'toot:Emoji',
|
||||||
featured: 'toot:featured',
|
featured: {
|
||||||
discoverable: 'toot:discoverable',
|
'@id': 'toot:featured',
|
||||||
|
'@type': '@id',
|
||||||
|
},
|
||||||
|
discoverable: {
|
||||||
|
'@id': 'toot:discoverable',
|
||||||
|
'@type': 'xsd:boolean',
|
||||||
|
},
|
||||||
// Fedibird
|
// Fedibird
|
||||||
fedibird: 'http://fedibird.com/ns#',
|
quoteUri: {
|
||||||
quoteUri: 'fedibird:quoteUri',
|
'@id': 'http://fedibird.com/ns#quoteUri',
|
||||||
|
'@type': '@id',
|
||||||
|
},
|
||||||
// schema
|
// schema
|
||||||
schema: 'http://schema.org#',
|
schema: 'http://schema.org/',
|
||||||
PropertyValue: 'schema:PropertyValue',
|
PropertyValue: {
|
||||||
value: 'schema:value',
|
'@id': 'schema:PropertyValue',
|
||||||
|
'@context': {
|
||||||
|
'value': 'schema:value',
|
||||||
|
'name': 'schema:name',
|
||||||
|
},
|
||||||
|
},
|
||||||
// Misskey
|
// Misskey
|
||||||
misskey: 'https://misskey-hub.net/ns#',
|
misskey: 'https://misskey-hub.net/ns#',
|
||||||
'_misskey_quote': 'misskey:_misskey_quote',
|
'_misskey_quote': {
|
||||||
'_misskey_talk': 'misskey:_misskey_talk',
|
'@id': 'misskey:_misskey_quote',
|
||||||
'isCat': 'misskey:isCat',
|
'@type': '@id',
|
||||||
|
},
|
||||||
|
'_misskey_talk': {
|
||||||
|
'@id': 'misskey:_misskey_talk',
|
||||||
|
'@type': 'xsd:boolean',
|
||||||
|
},
|
||||||
|
'isCat': {
|
||||||
|
'@id': 'misskey:isCat',
|
||||||
|
'@type': 'xsd:boolean',
|
||||||
|
},
|
||||||
// vcard
|
// vcard
|
||||||
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
||||||
},
|
},
|
||||||
|
|
|
@ -182,6 +182,13 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
|
result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
|
||||||
result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
|
result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
|
||||||
|
|
||||||
|
// store the updated counts in the user table to potentially fix the cache
|
||||||
|
Users.update(user.id, {
|
||||||
|
followersCount: result.followersCount,
|
||||||
|
followingCount: result.followingCount,
|
||||||
|
notesCount: result.notesCount,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Router from '@koa/router';
|
||||||
import { IsNull, MoreThan } from 'typeorm';
|
import { IsNull, MoreThan } from 'typeorm';
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { Users, Notes } from '@/models/index.js';
|
import { Users, Notes, Webhooks } from '@/models/index.js';
|
||||||
import { MONTH, DAY } from '@/const.js';
|
import { MONTH, DAY } from '@/const.js';
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
@ -52,12 +52,14 @@ const nodeinfo2 = async (): Promise<NodeInfo2Base> => {
|
||||||
activeHalfyear,
|
activeHalfyear,
|
||||||
activeMonth,
|
activeMonth,
|
||||||
localPosts,
|
localPosts,
|
||||||
|
activeWebhooks,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
fetchMeta(true),
|
fetchMeta(true),
|
||||||
Users.count({ where: { host: IsNull() } }),
|
Users.count({ where: { host: IsNull() } }),
|
||||||
Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 180 * DAY)) } }),
|
Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 180 * DAY)) } }),
|
||||||
Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - MONTH)) } }),
|
Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - MONTH)) } }),
|
||||||
Notes.count({ where: { userHost: IsNull() } }),
|
Notes.count({ where: { userHost: IsNull() } }),
|
||||||
|
Webhooks.countBy({ active: true }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null;
|
const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null;
|
||||||
|
@ -100,6 +102,7 @@ const nodeinfo2 = async (): Promise<NodeInfo2Base> => {
|
||||||
enableEmail: meta.enableEmail,
|
enableEmail: meta.enableEmail,
|
||||||
proxyAccountName: proxyAccount?.username ?? null,
|
proxyAccountName: proxyAccount?.username ?? null,
|
||||||
themeColor: meta.themeColor || '#86b300',
|
themeColor: meta.themeColor || '#86b300',
|
||||||
|
activeWebhooks,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,13 +31,13 @@ export default class Logger {
|
||||||
* @param color Log message color
|
* @param color Log message color
|
||||||
* @param store Whether to store messages
|
* @param store Whether to store messages
|
||||||
*/
|
*/
|
||||||
constructor(domain: string, color?: KEYWORD, store = true, minLevel: Level = LOG_LEVELS.info) {
|
constructor(domain: string, color?: KEYWORD, store = true, minLevel?: Level) {
|
||||||
this.domain = {
|
this.domain = {
|
||||||
name: domain,
|
name: domain,
|
||||||
color,
|
color,
|
||||||
};
|
};
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.minLevel = minLevel;
|
this.minLevel = minLevel ?? envOption.logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +47,7 @@ export default class Logger {
|
||||||
* @param store Whether to store messages
|
* @param store Whether to store messages
|
||||||
* @returns A Logger instance whose parent logger is this instance.
|
* @returns A Logger instance whose parent logger is this instance.
|
||||||
*/
|
*/
|
||||||
public createSubLogger(domain: string, color?: KEYWORD, store = true, minLevel: Level = LOG_LEVELS.info): Logger {
|
public createSubLogger(domain: string, color?: KEYWORD, store = true, minLevel?: Level): Logger {
|
||||||
const logger = new Logger(domain, color, store, minLevel);
|
const logger = new Logger(domain, color, store, minLevel);
|
||||||
logger.parentLogger = this;
|
logger.parentLogger = this;
|
||||||
return logger;
|
return logger;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { ArrayOverlap, Not, In } from 'typeorm';
|
import { ArrayOverlap, Not, In } from 'typeorm';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import { db } from '@/db/postgre.js';
|
import { db } from '@/db/postgre.js';
|
||||||
import es from '@/db/elasticsearch.js';
|
|
||||||
import { publishMainStream, publishNotesStream } from '@/services/stream.js';
|
import { publishMainStream, publishNotesStream } from '@/services/stream.js';
|
||||||
import { DeliverManager } from '@/remote/activitypub/deliver-manager.js';
|
import { DeliverManager } from '@/remote/activitypub/deliver-manager.js';
|
||||||
import renderNote from '@/remote/activitypub/renderer/note.js';
|
import renderNote from '@/remote/activitypub/renderer/note.js';
|
||||||
|
@ -9,14 +8,13 @@ import renderCreate from '@/remote/activitypub/renderer/create.js';
|
||||||
import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
|
import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
|
||||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||||
import { resolveUser } from '@/remote/resolve-user.js';
|
import { resolveUser } from '@/remote/resolve-user.js';
|
||||||
import config from '@/config/index.js';
|
|
||||||
import { concat } from '@/prelude/array.js';
|
import { concat } from '@/prelude/array.js';
|
||||||
import { insertNoteUnread } from '@/services/note/unread.js';
|
import { insertNoteUnread } from '@/services/note/unread.js';
|
||||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, MutedNotes, Channels, ChannelFollowings, NoteThreadMutings } from '@/models/index.js';
|
import { Mutings, Users, NoteWatchings, Notes, Instances, MutedNotes, Channels, ChannelFollowings, NoteThreadMutings } from '@/models/index.js';
|
||||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||||
import { App } from '@/models/entities/app.js';
|
import { App } from '@/models/entities/app.js';
|
||||||
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
|
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
|
||||||
|
@ -32,27 +30,16 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import { getAntennas } from '@/misc/antenna-cache.js';
|
import { getAntennas } from '@/misc/antenna-cache.js';
|
||||||
import { endedPollNotificationQueue } from '@/queue/queues.js';
|
import { endedPollNotificationQueue } from '@/queue/queues.js';
|
||||||
import { webhookDeliver } from '@/queue/index.js';
|
import { webhookDeliver } from '@/queue/index.js';
|
||||||
import { Cache } from '@/misc/cache.js';
|
|
||||||
import { UserProfile } from '@/models/entities/user-profile.js';
|
import { UserProfile } from '@/models/entities/user-profile.js';
|
||||||
import { getActiveWebhooks } from '@/misc/webhook-cache.js';
|
import { getActiveWebhooks } from '@/misc/webhook-cache.js';
|
||||||
import { IActivity } from '@/remote/activitypub/type.js';
|
import { IActivity } from '@/remote/activitypub/type.js';
|
||||||
import { renderNoteOrRenoteActivity } from '@/remote/activitypub/renderer/note-or-renote.js';
|
import { renderNoteOrRenoteActivity } from '@/remote/activitypub/renderer/note-or-renote.js';
|
||||||
import { MINUTE } from '@/const.js';
|
|
||||||
import { updateHashtags } from '../update-hashtag.js';
|
import { updateHashtags } from '../update-hashtag.js';
|
||||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
|
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
|
||||||
import { createNotification } from '../create-notification.js';
|
import { createNotification } from '../create-notification.js';
|
||||||
import { addNoteToAntenna } from '../add-note-to-antenna.js';
|
import { addNoteToAntenna } from '../add-note-to-antenna.js';
|
||||||
import { deliverToRelays } from '../relay.js';
|
import { deliverToRelays } from '../relay.js';
|
||||||
|
import { mutedWordsCache, index } from './index.js';
|
||||||
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(
|
|
||||||
5 * MINUTE,
|
|
||||||
() => UserProfiles.find({
|
|
||||||
where: {
|
|
||||||
enableWordMute: true,
|
|
||||||
},
|
|
||||||
select: ['userId', 'mutedWords'],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||||
|
|
||||||
|
@ -576,20 +563,6 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function index(note: Note): void {
|
|
||||||
if (note.text == null || config.elasticsearch == null) return;
|
|
||||||
|
|
||||||
es.index({
|
|
||||||
index: config.elasticsearch.index || 'misskey_note',
|
|
||||||
id: note.id.toString(),
|
|
||||||
body: {
|
|
||||||
text: normalizeForSearch(note.text),
|
|
||||||
userId: note.userId,
|
|
||||||
userHost: note.userHost,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType): Promise<void> {
|
async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType): Promise<void> {
|
||||||
const watchers = await NoteWatchings.findBy({
|
const watchers = await NoteWatchings.findBy({
|
||||||
noteId: renote.id,
|
noteId: renote.id,
|
||||||
|
|
32
packages/backend/src/services/note/index.ts
Normal file
32
packages/backend/src/services/note/index.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import es from '@/db/elasticsearch.js';
|
||||||
|
import config from '@/config/index.js';
|
||||||
|
import { Note } from '@/models/entities/note.js';
|
||||||
|
import { UserProfiles } from '@/models/index.js';
|
||||||
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
|
import { Cache } from '@/misc/cache.js';
|
||||||
|
import { UserProfile } from '@/models/entities/user-profile.js';
|
||||||
|
import { MINUTE } from '@/const.js';
|
||||||
|
|
||||||
|
export const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(
|
||||||
|
5 * MINUTE,
|
||||||
|
() => UserProfiles.find({
|
||||||
|
where: {
|
||||||
|
enableWordMute: true,
|
||||||
|
},
|
||||||
|
select: ['userId', 'mutedWords'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function index(note: Note): void {
|
||||||
|
if (note.text == null || config.elasticsearch == null) return;
|
||||||
|
|
||||||
|
es.index({
|
||||||
|
index: config.elasticsearch.index || 'misskey_note',
|
||||||
|
id: note.id.toString(),
|
||||||
|
body: {
|
||||||
|
text: normalizeForSearch(note.text),
|
||||||
|
userId: note.userId,
|
||||||
|
userHost: note.userHost,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { initDb } from '../src/db/postgre.js';
|
import { initDb } from '../built/db/postgre.js';
|
||||||
import { initTestDb } from './utils.js';
|
import { initTestDb } from './utils.mjs';
|
||||||
|
|
||||||
|
|
||||||
function rndstr(length): string {
|
function rndstr(length) {
|
||||||
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
const chars_len = 62;
|
const chars_len = 62;
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ describe('ActivityPub', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('Minimum Actor', async () => {
|
it('Minimum Actor', async () => {
|
||||||
const { MockResolver } = await import('./misc/mock-resolver.js');
|
const { MockResolver } = await import('./misc/mock-resolver.mjs');
|
||||||
const { createPerson } = await import('../src/remote/activitypub/models/person.js');
|
const { createPerson } = await import('../built/remote/activitypub/models/person.js');
|
||||||
|
|
||||||
const resolver = new MockResolver();
|
const resolver = new MockResolver();
|
||||||
resolver._register(actor.id, actor);
|
resolver._register(actor.id, actor);
|
||||||
|
@ -66,8 +66,8 @@ describe('ActivityPub', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Minimum Note', async () => {
|
it('Minimum Note', async () => {
|
||||||
const { MockResolver } = await import('./misc/mock-resolver.js');
|
const { MockResolver } = await import('./misc/mock-resolver.mjs');
|
||||||
const { createNote } = await import('../src/remote/activitypub/models/note.js');
|
const { createNote } = await import('../built/remote/activitypub/models/note.js');
|
||||||
|
|
||||||
const resolver = new MockResolver();
|
const resolver = new MockResolver();
|
||||||
resolver._register(actor.id, actor);
|
resolver._register(actor.id, actor);
|
||||||
|
@ -99,8 +99,8 @@ describe('ActivityPub', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('Actor', async () => {
|
it('Actor', async () => {
|
||||||
const { MockResolver } = await import('./misc/mock-resolver.js');
|
const { MockResolver } = await import('./misc/mock-resolver.mjs');
|
||||||
const { createPerson } = await import('../src/remote/activitypub/models/person.js');
|
const { createPerson } = await import('../built/remote/activitypub/models/person.js');
|
||||||
|
|
||||||
const resolver = new MockResolver();
|
const resolver = new MockResolver();
|
||||||
resolver._register(actor.id, actor);
|
resolver._register(actor.id, actor);
|
|
@ -1,9 +1,9 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import httpSignature from '@peertube/http-signature';
|
import httpSignature from '@peertube/http-signature';
|
||||||
import { genRsaKeyPair } from '../src/misc/gen-key-pair.js';
|
import { genRsaKeyPair } from '../built/misc/gen-key-pair.js';
|
||||||
import { createSignedPost, createSignedGet } from '../src/remote/activitypub/ap-request.js';
|
import { createSignedPost, createSignedGet } from '../built/remote/activitypub/ap-request.js';
|
||||||
|
|
||||||
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
export const buildParsedSignature = (signingString, signature, algorithm) => {
|
||||||
return {
|
return {
|
||||||
scheme: 'Signature',
|
scheme: 'Signature',
|
||||||
params: {
|
params: {
|
|
@ -2,13 +2,14 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, startServer, shutdownServer } from './utils.js';
|
import { async, signup, request, post, startServer, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
describe('API visibility', () => {
|
describe('API visibility', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
|
let p;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,48 +20,48 @@ describe('API visibility', () => {
|
||||||
describe('Note visibility', async () => {
|
describe('Note visibility', async () => {
|
||||||
//#region vars
|
//#region vars
|
||||||
/** protagonist */
|
/** protagonist */
|
||||||
let alice: any;
|
let alice;
|
||||||
/** follower */
|
/** follower */
|
||||||
let follower: any;
|
let follower;
|
||||||
/** non-follower */
|
/** non-follower */
|
||||||
let other: any;
|
let other;
|
||||||
/** non-follower who has been replied to or mentioned */
|
/** non-follower who has been replied to or mentioned */
|
||||||
let target: any;
|
let target;
|
||||||
/** actor for which a specified visibility was set */
|
/** actor for which a specified visibility was set */
|
||||||
let target2: any;
|
let target2;
|
||||||
|
|
||||||
/** public-post */
|
/** public-post */
|
||||||
let pub: any;
|
let pub;
|
||||||
/** home-post */
|
/** home-post */
|
||||||
let home: any;
|
let home;
|
||||||
/** followers-post */
|
/** followers-post */
|
||||||
let fol: any;
|
let fol;
|
||||||
/** specified-post */
|
/** specified-post */
|
||||||
let spe: any;
|
let spe;
|
||||||
|
|
||||||
/** public-reply to target's post */
|
/** public-reply to target's post */
|
||||||
let pubR: any;
|
let pubR;
|
||||||
/** home-reply to target's post */
|
/** home-reply to target's post */
|
||||||
let homeR: any;
|
let homeR;
|
||||||
/** followers-reply to target's post */
|
/** followers-reply to target's post */
|
||||||
let folR: any;
|
let folR;
|
||||||
/** specified-reply to target's post */
|
/** specified-reply to target's post */
|
||||||
let speR: any;
|
let speR;
|
||||||
|
|
||||||
/** public-mention to target */
|
/** public-mention to target */
|
||||||
let pubM: any;
|
let pubM;
|
||||||
/** home-mention to target */
|
/** home-mention to target */
|
||||||
let homeM: any;
|
let homeM;
|
||||||
/** followers-mention to target */
|
/** followers-mention to target */
|
||||||
let folM: any;
|
let folM;
|
||||||
/** specified-mention to target */
|
/** specified-mention to target */
|
||||||
let speM: any;
|
let speM;
|
||||||
|
|
||||||
/** reply target post */
|
/** reply target post */
|
||||||
let tgt: any;
|
let tgt;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const show = async (noteId: any, by: any) => {
|
const show = async (noteId, by) => {
|
||||||
return await request('/notes/show', {
|
return await request('/notes/show', {
|
||||||
noteId,
|
noteId,
|
||||||
}, by);
|
}, by);
|
||||||
|
@ -411,21 +412,21 @@ describe('API visibility', () => {
|
||||||
it('[TL] public post on author home TL', async(async () => {
|
it('[TL] public post on author home TL', async(async () => {
|
||||||
const res = await request('/notes/timeline', { limit: 100 }, alice);
|
const res = await request('/notes/timeline', { limit: 100 }, alice);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
const notes = res.body.filter((n) => n.id == pub.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('[TL] public post absent from non-follower home TL', async(async () => {
|
it('[TL] public post absent from non-follower home TL', async(async () => {
|
||||||
const res = await request('/notes/timeline', { limit: 100 }, other);
|
const res = await request('/notes/timeline', { limit: 100 }, other);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
const notes = res.body.filter((n) => n.id == pub.id);
|
||||||
assert.strictEqual(notes.length, 0);
|
assert.strictEqual(notes.length, 0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('[TL] followers post on follower home TL', async(async () => {
|
it('[TL] followers post on follower home TL', async(async () => {
|
||||||
const res = await request('/notes/timeline', { limit: 100 }, follower);
|
const res = await request('/notes/timeline', { limit: 100 }, follower);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == fol.id);
|
const notes = res.body.filter((n) => n.id == fol.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
}));
|
}));
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -434,21 +435,21 @@ describe('API visibility', () => {
|
||||||
it('[TL] followers reply on follower reply TL', async(async () => {
|
it('[TL] followers reply on follower reply TL', async(async () => {
|
||||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower);
|
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
const notes = res.body.filter((n) => n.id == folR.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('[TL] followers reply absent from not replied to non-follower reply TL', async(async () => {
|
it('[TL] followers reply absent from not replied to non-follower reply TL', async(async () => {
|
||||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other);
|
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
const notes = res.body.filter((n) => n.id == folR.id);
|
||||||
assert.strictEqual(notes.length, 0);
|
assert.strictEqual(notes.length, 0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('[TL] followers reply on replied to actor reply TL', async(async () => {
|
it('[TL] followers reply on replied to actor reply TL', async(async () => {
|
||||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target);
|
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
const notes = res.body.filter((n) => n.id == folR.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
}));
|
}));
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -457,14 +458,14 @@ describe('API visibility', () => {
|
||||||
it('[TL] followers reply on replied to non-follower mention TL', async(async () => {
|
it('[TL] followers reply on replied to non-follower mention TL', async(async () => {
|
||||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
const notes = res.body.filter((n) => n.id == folR.id);
|
||||||
assert.strictEqual(notes[0].text, 'x');
|
assert.strictEqual(notes[0].text, 'x');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('[TL] followers mention on mentioned non-follower mention TL', async(async () => {
|
it('[TL] followers mention on mentioned non-follower mention TL', async(async () => {
|
||||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
const notes = res.body.filter((n: any) => n.id == folM.id);
|
const notes = res.body.filter((n) => n.id == folM.id);
|
||||||
assert.strictEqual(notes[0].text, '@target x');
|
assert.strictEqual(notes[0].text, '@target x');
|
||||||
}));
|
}));
|
||||||
//#endregion
|
//#endregion
|
|
@ -2,16 +2,15 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.js';
|
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
describe('API', () => {
|
describe('API', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async function() {
|
let p;
|
||||||
this.timeout(0);
|
let alice, bob, carol;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
|
@ -2,19 +2,17 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, startServer, shutdownServer } from './utils.js';
|
import { async, signup, request, post, startServer, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
describe('Block', () => {
|
describe('Block', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
|
let p;
|
||||||
|
|
||||||
// alice blocks bob
|
// alice blocks bob
|
||||||
let alice: any;
|
let alice, bob, carol;
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
@ -80,8 +78,8 @@ describe('Block', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), true);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
|
@ -2,18 +2,14 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as lolex from '@sinonjs/fake-timers';
|
import * as lolex from '@sinonjs/fake-timers';
|
||||||
import TestChart from '../src/services/chart/charts/test.js';
|
import TestChart from '../built/services/chart/charts/test.js';
|
||||||
import TestGroupedChart from '../src/services/chart/charts/test-grouped.js';
|
import TestGroupedChart from '../built/services/chart/charts/test-grouped.js';
|
||||||
import TestUniqueChart from '../src/services/chart/charts/test-unique.js';
|
import TestUniqueChart from '../built/services/chart/charts/test-unique.js';
|
||||||
import TestIntersectionChart from '../src/services/chart/charts/test-intersection.js';
|
import TestIntersectionChart from '../built/services/chart/charts/test-intersection.js';
|
||||||
import { initDb } from '../src/db/postgre.js';
|
import { initDb } from '../built/db/postgre.js';
|
||||||
|
|
||||||
describe('Chart', () => {
|
describe('Chart', () => {
|
||||||
let testChart: TestChart;
|
let testChart, testGroupedChart, testUniqueChart, testIntersectionChart, clock;
|
||||||
let testGroupedChart: TestGroupedChart;
|
|
||||||
let testUniqueChart: TestUniqueChart;
|
|
||||||
let testIntersectionChart: TestIntersectionChart;
|
|
||||||
let clock: lolex.InstalledClock;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await initDb(true);
|
await initDb(true);
|
|
@ -1,15 +0,0 @@
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
redistest:
|
|
||||||
image: redis:6
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:56312:6379"
|
|
||||||
|
|
||||||
dbtest:
|
|
||||||
image: postgres:13
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:54312:5432"
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: "test-misskey"
|
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
|
|
@ -3,13 +3,12 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.js';
|
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
describe('API: Endpoints', () => {
|
describe('API: Endpoints', () => {
|
||||||
let p: childProcess.ChildProcess;
|
let p;
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
let alice, bob, carol;
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
p = await startServer();
|
p = await startServer();
|
|
@ -1,11 +1,11 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
|
||||||
import { parse } from 'mfm-js';
|
import { parse } from 'mfm-js';
|
||||||
import { extractMentions } from '../src/misc/extract-mentions.js';
|
import { extractMentions } from '../built/misc/extract-mentions.js';
|
||||||
|
|
||||||
describe('Extract mentions', () => {
|
describe('Extract mentions', () => {
|
||||||
it('simple', () => {
|
it('simple', () => {
|
||||||
const ast = parse('@foo @bar @baz')!;
|
const ast = parse('@foo @bar @baz');
|
||||||
const mentions = extractMentions(ast);
|
const mentions = extractMentions(ast);
|
||||||
assert.deepStrictEqual(mentions, [{
|
assert.deepStrictEqual(mentions, [{
|
||||||
username: 'foo',
|
username: 'foo',
|
||||||
|
@ -23,7 +23,7 @@ describe('Extract mentions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested', () => {
|
it('nested', () => {
|
||||||
const ast = parse('@foo **@bar** @baz')!;
|
const ast = parse('@foo **@bar** @baz');
|
||||||
const mentions = extractMentions(ast);
|
const mentions = extractMentions(ast);
|
||||||
assert.deepStrictEqual(mentions, [{
|
assert.deepStrictEqual(mentions, [{
|
||||||
username: 'foo',
|
username: 'foo',
|
|
@ -3,7 +3,7 @@ process.env.NODE_ENV = 'test';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import * as openapi from '@redocly/openapi-core';
|
import * as openapi from '@redocly/openapi-core';
|
||||||
import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js';
|
import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
// Request Accept
|
// Request Accept
|
||||||
const ONLY_AP = 'application/activity+json';
|
const ONLY_AP = 'application/activity+json';
|
||||||
|
@ -16,14 +16,14 @@ const AP = 'application/activity+json; charset=utf-8';
|
||||||
const JSON = 'application/json; charset=utf-8';
|
const JSON = 'application/json; charset=utf-8';
|
||||||
const HTML = 'text/html; charset=utf-8';
|
const HTML = 'text/html; charset=utf-8';
|
||||||
|
|
||||||
describe('Fetch resource', () => {
|
describe('Fetch resource', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
let alice: any;
|
let p;
|
||||||
let alicesPost: any;
|
|
||||||
|
let alice, alicesPost;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
alicesPost = await post(alice, {
|
alicesPost = await post(alice, {
|
|
@ -2,18 +2,16 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, react, connectStream, startServer, shutdownServer, simpleGet } from './utils.js';
|
import { async, signup, request, post, react, connectStream, startServer, shutdownServer, simpleGet } from './utils.mjs';
|
||||||
|
|
||||||
describe('FF visibility', () => {
|
describe('FF visibility', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
let alice: any;
|
let p;
|
||||||
let bob: any;
|
|
||||||
let follower: any;
|
let alice, bob, follower;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
|
@ -1,8 +1,8 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { getFileInfo } from '../src/misc/get-file-info.js';
|
import { getFileInfo } from '../built/misc/get-file-info.js';
|
||||||
import { async } from './utils.js';
|
import { async } from './utils.mjs';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
@ -10,7 +10,7 @@ const _dirname = dirname(_filename);
|
||||||
describe('Get file info', () => {
|
describe('Get file info', () => {
|
||||||
it('Empty file', async (async () => {
|
it('Empty file', async (async () => {
|
||||||
const path = `${_dirname}/resources/emptyfile`;
|
const path = `${_dirname}/resources/emptyfile`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -28,7 +28,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('Generic JPEG', async (async () => {
|
it('Generic JPEG', async (async () => {
|
||||||
const path = `${_dirname}/resources/Lenna.jpg`;
|
const path = `${_dirname}/resources/Lenna.jpg`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -46,7 +46,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('Generic APNG', async (async () => {
|
it('Generic APNG', async (async () => {
|
||||||
const path = `${_dirname}/resources/anime.png`;
|
const path = `${_dirname}/resources/anime.png`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -64,7 +64,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('Generic AGIF', async (async () => {
|
it('Generic AGIF', async (async () => {
|
||||||
const path = `${_dirname}/resources/anime.gif`;
|
const path = `${_dirname}/resources/anime.gif`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -82,7 +82,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('PNG with alpha', async (async () => {
|
it('PNG with alpha', async (async () => {
|
||||||
const path = `${_dirname}/resources/with-alpha.png`;
|
const path = `${_dirname}/resources/with-alpha.png`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -100,7 +100,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('Generic SVG', async (async () => {
|
it('Generic SVG', async (async () => {
|
||||||
const path = `${_dirname}/resources/image.svg`;
|
const path = `${_dirname}/resources/image.svg`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -119,7 +119,7 @@ describe('Get file info', () => {
|
||||||
it('SVG with XML definition', async (async () => {
|
it('SVG with XML definition', async (async () => {
|
||||||
// https://github.com/misskey-dev/misskey/issues/4413
|
// https://github.com/misskey-dev/misskey/issues/4413
|
||||||
const path = `${_dirname}/resources/with-xml-def.svg`;
|
const path = `${_dirname}/resources/with-xml-def.svg`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -137,7 +137,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('Dimension limit', async (async () => {
|
it('Dimension limit', async (async () => {
|
||||||
const path = `${_dirname}/resources/25000x25000.png`;
|
const path = `${_dirname}/resources/25000x25000.png`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
||||||
|
@ -155,7 +155,7 @@ describe('Get file info', () => {
|
||||||
|
|
||||||
it('Rotate JPEG', async (async () => {
|
it('Rotate JPEG', async (async () => {
|
||||||
const path = `${_dirname}/resources/rotate.jpg`;
|
const path = `${_dirname}/resources/rotate.jpg`;
|
||||||
const info = await getFileInfo(path) as any;
|
const info = await getFileInfo(path);
|
||||||
delete info.warnings;
|
delete info.warnings;
|
||||||
delete info.blurhash;
|
delete info.blurhash;
|
||||||
assert.deepStrictEqual(info, {
|
assert.deepStrictEqual(info, {
|
|
@ -1,34 +0,0 @@
|
||||||
/**
|
|
||||||
* ts-node/esmローダーに投げる前にpath mappingを解決する
|
|
||||||
* 参考
|
|
||||||
* - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115
|
|
||||||
* - https://nodejs.org/api/esm.html#loaders
|
|
||||||
* ※ https://github.com/TypeStrong/ts-node/pull/1585 が取り込まれたらこのカスタムローダーは必要なくなる
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { resolve as resolveTs, load } from 'ts-node/esm';
|
|
||||||
import { loadConfig, createMatchPath } from 'tsconfig-paths';
|
|
||||||
import { pathToFileURL } from 'url';
|
|
||||||
|
|
||||||
const tsconfig = loadConfig();
|
|
||||||
const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
|
|
||||||
|
|
||||||
export function resolve(specifier, ctx, defaultResolve) {
|
|
||||||
let resolvedSpecifier;
|
|
||||||
if (specifier.endsWith('.js')) {
|
|
||||||
// maybe transpiled
|
|
||||||
const specifierWithoutExtension = specifier.substring(0, specifier.length - '.js'.length);
|
|
||||||
const matchedSpecifier = matchPath(specifierWithoutExtension);
|
|
||||||
if (matchedSpecifier) {
|
|
||||||
resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const matchedSpecifier = matchPath(specifier);
|
|
||||||
if (matchedSpecifier) {
|
|
||||||
resolvedSpecifier = pathToFileURL(matchedSpecifier).href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { load };
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
|
||||||
import { toHtml } from '../src/mfm/to-html.js';
|
import { toHtml } from '../built/mfm/to-html.js';
|
||||||
import { fromHtml } from '../src/mfm/from-html.js';
|
import { fromHtml } from '../built/mfm/from-html.js';
|
||||||
|
|
||||||
describe('toHtml', () => {
|
describe('toHtml', () => {
|
||||||
it('br', async () => {
|
it('br', async () => {
|
|
@ -1,21 +1,16 @@
|
||||||
import { Resolver } from '../../src/remote/activitypub/resolver.js';
|
import { Resolver } from '../../built/remote/activitypub/resolver.js';
|
||||||
import { IObject } from '../../src/remote/activitypub/type.js';
|
|
||||||
|
|
||||||
type MockResponse = {
|
|
||||||
type: string;
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MockResolver extends Resolver {
|
export class MockResolver extends Resolver {
|
||||||
private _rs = new Map<string, MockResponse>();
|
_rs = new Map();
|
||||||
public async _register(uri: string, content: string | Record<string, any>, type = 'application/activity+json') {
|
|
||||||
|
async _register(uri, content, type = 'application/activity+json') {
|
||||||
this._rs.set(uri, {
|
this._rs.set(uri, {
|
||||||
type,
|
type,
|
||||||
content: typeof content === 'string' ? content : JSON.stringify(content),
|
content: typeof content === 'string' ? content : JSON.stringify(content),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resolve(value: string | IObject): Promise<IObject> {
|
async resolve(value) {
|
||||||
if (typeof value !== 'string') return value;
|
if (typeof value !== 'string') return value;
|
||||||
|
|
||||||
const r = this._rs.get(value);
|
const r = this._rs.get(value);
|
|
@ -2,19 +2,17 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, react, startServer, shutdownServer, waitFire } from './utils.js';
|
import { async, signup, request, post, react, startServer, shutdownServer, waitFire } from './utils.mjs';
|
||||||
|
|
||||||
describe('Mute', () => {
|
describe('Mute', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
|
let p;
|
||||||
|
|
||||||
// alice mutes carol
|
// alice mutes carol
|
||||||
let alice: any;
|
let alice, bob, carol;
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
@ -41,8 +39,8 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async(async () => {
|
it('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async(async () => {
|
||||||
|
@ -86,9 +84,9 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async(async () => {
|
it('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async(async () => {
|
||||||
|
@ -102,9 +100,9 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,8 +116,8 @@ describe('Mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
|
assert.strictEqual(res.body.some((notification) => notification.userId === bob.id), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
|
assert.strictEqual(res.body.some((notification) => notification.userId === carol.id), false);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -2,19 +2,18 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { Note } from '../src/models/entities/note.js';
|
import { Note } from '../built/models/entities/note.js';
|
||||||
import { async, signup, request, post, uploadUrl, startServer, shutdownServer, initTestDb, api } from './utils.js';
|
import { async, signup, request, post, uploadUrl, startServer, shutdownServer, initTestDb, api } from './utils.mjs';
|
||||||
|
|
||||||
describe('Note', () => {
|
describe('Note', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
let Notes: any;
|
|
||||||
|
|
||||||
let alice: any;
|
let p;
|
||||||
let bob: any;
|
let Notes;
|
||||||
|
|
||||||
|
let alice, bob;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
const connection = await initTestDb(true);
|
const connection = await initTestDb(true);
|
||||||
Notes = connection.getRepository(Note);
|
Notes = connection.getRepository(Note);
|
|
@ -1,5 +1,5 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { query } from '../../src/prelude/url.js';
|
import { query } from '../../built/prelude/url.js';
|
||||||
|
|
||||||
describe('url', () => {
|
describe('url', () => {
|
||||||
it('query', () => {
|
it('query', () => {
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
import * as assert from 'assert';
|
|
||||||
|
|
||||||
import { toDbReaction } from '../src/misc/reaction-lib.js';
|
|
||||||
|
|
||||||
describe('toDbReaction', async () => {
|
|
||||||
it('既存の文字列リアクションはそのまま', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('like'), 'like');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Unicodeプリンは寿司化不能とするため文字列化しない', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('🍮'), '🍮');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する like', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('👍'), 'like');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する love', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('❤️'), 'love');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する love 異体字セレクタなし', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('❤'), 'love');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する laugh', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('😆'), 'laugh');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する hmm', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('🤔'), 'hmm');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する surprise', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('😮'), 'surprise');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する congrats', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('🎉'), 'congrats');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する angry', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('💢'), 'angry');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する confused', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('😥'), 'confused');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('プリン以外の既存のリアクションは文字列化する rip', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('😇'), 'rip');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('それ以外はUnicodeのまま', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('🍅'), '🍅');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('異体字セレクタ除去', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('㊗️'), '㊗');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('異体字セレクタ除去 必要なし', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('㊗'), '㊗');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fallback - undefined', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction(undefined), 'like');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fallback - null', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction(null), 'like');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fallback - empty', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction(''), 'like');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fallback - unknown', async () => {
|
|
||||||
assert.strictEqual(await toDbReaction('unknown'), 'like');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
*/
|
|
|
@ -3,19 +3,17 @@ process.env.NODE_ENV = 'test';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.js';
|
import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.mjs';
|
||||||
|
|
||||||
describe('Creating a block activity', () => {
|
describe('Creating a block activity', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
|
let p;
|
||||||
|
|
||||||
// alice blocks bob
|
// alice blocks bob
|
||||||
let alice: any;
|
let alice, bob, carol;
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
await initTestDb();
|
await initTestDb();
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
|
@ -34,10 +32,10 @@ describe('Creating a block activity', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should federate blocks normally', async(async () => {
|
it('Should federate blocks normally', async(async () => {
|
||||||
const createBlock = (await import('../../src/services/blocking/create')).default;
|
const createBlock = (await import('../../built/services/blocking/create')).default;
|
||||||
const deleteBlock = (await import('../../src/services/blocking/delete')).default;
|
const deleteBlock = (await import('../../built/services/blocking/delete')).default;
|
||||||
|
|
||||||
const queues = await import('../../src/queue/index');
|
const queues = await import('../../built/queue/index');
|
||||||
const spy = sinon.spy(queues, 'deliver');
|
const spy = sinon.spy(queues, 'deliver');
|
||||||
await createBlock(alice, bob);
|
await createBlock(alice, bob);
|
||||||
assert(spy.calledOnce);
|
assert(spy.calledOnce);
|
||||||
|
@ -46,12 +44,12 @@ describe('Creating a block activity', () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('Should not federate blocks if federateBlocks is false', async () => {
|
it('Should not federate blocks if federateBlocks is false', async () => {
|
||||||
const createBlock = (await import('../../src/services/blocking/create')).default;
|
const createBlock = (await import('../../built/services/blocking/create')).default;
|
||||||
const deleteBlock = (await import('../../src/services/blocking/delete')).default;
|
const deleteBlock = (await import('../../built/services/blocking/delete')).default;
|
||||||
|
|
||||||
alice.federateBlocks = true;
|
alice.federateBlocks = true;
|
||||||
|
|
||||||
const queues = await import('../../src/queue/index');
|
const queues = await import('../../built/queue/index');
|
||||||
const spy = sinon.spy(queues, 'deliver');
|
const spy = sinon.spy(queues, 'deliver');
|
||||||
await createBlock(alice, carol);
|
await createBlock(alice, carol);
|
||||||
await deleteBlock(alice, carol);
|
await deleteBlock(alice, carol);
|
|
@ -2,14 +2,14 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { Following } from '../src/models/entities/following.js';
|
import { Following } from '../built/models/entities/following.js';
|
||||||
import { connectStream, signup, api, post, startServer, shutdownServer, initTestDb, waitFire } from './utils.js';
|
import { connectStream, signup, api, post, startServer, shutdownServer, initTestDb, waitFire } from './utils.mjs';
|
||||||
|
|
||||||
describe('Streaming', () => {
|
describe('Streaming', () => {
|
||||||
let p: childProcess.ChildProcess;
|
let p;
|
||||||
let Followings: any;
|
let Followings;
|
||||||
|
|
||||||
const follow = async (follower: any, followee: any) => {
|
const follow = async (follower, followee) => {
|
||||||
await Followings.save({
|
await Followings.save({
|
||||||
id: 'a',
|
id: 'a',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
@ -24,22 +24,18 @@ describe('Streaming', () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Streaming', () => {
|
describe('Streaming', function() {
|
||||||
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
// Local users
|
// Local users
|
||||||
let ayano: any;
|
let ayano, kyoko, chitose;
|
||||||
let kyoko: any;
|
|
||||||
let chitose: any;
|
|
||||||
|
|
||||||
// Remote users
|
// Remote users
|
||||||
let akari: any;
|
let akari, chinatsu;
|
||||||
let chinatsu: any;
|
|
||||||
|
|
||||||
let kyokoNote: any;
|
let kyokoNote, list;
|
||||||
let list: any;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
const connection = await initTestDb(true);
|
const connection = await initTestDb(true);
|
||||||
Followings = connection.getRepository(Following);
|
Followings = connection.getRepository(Following);
|
||||||
|
@ -388,7 +384,7 @@ describe('Streaming', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Hashtag Timeline', () => {
|
describe('Hashtag Timeline', () => {
|
||||||
it('指定したハッシュタグの投稿が流れる', () => new Promise<void>(async done => {
|
it('指定したハッシュタグの投稿が流れる', () => new Promise(async done => {
|
||||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||||
if (type == 'note') {
|
if (type == 'note') {
|
||||||
assert.deepStrictEqual(body.text, '#foo');
|
assert.deepStrictEqual(body.text, '#foo');
|
||||||
|
@ -406,7 +402,7 @@ describe('Streaming', () => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('指定したハッシュタグの投稿が流れる (AND)', () => new Promise<void>(async done => {
|
it('指定したハッシュタグの投稿が流れる (AND)', () => new Promise(async done => {
|
||||||
let fooCount = 0;
|
let fooCount = 0;
|
||||||
let barCount = 0;
|
let barCount = 0;
|
||||||
let fooBarCount = 0;
|
let fooBarCount = 0;
|
||||||
|
@ -444,7 +440,7 @@ describe('Streaming', () => {
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('指定したハッシュタグの投稿が流れる (OR)', () => new Promise<void>(async done => {
|
it('指定したハッシュタグの投稿が流れる (OR)', () => new Promise(async done => {
|
||||||
let fooCount = 0;
|
let fooCount = 0;
|
||||||
let barCount = 0;
|
let barCount = 0;
|
||||||
let fooBarCount = 0;
|
let fooBarCount = 0;
|
||||||
|
@ -490,7 +486,7 @@ describe('Streaming', () => {
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('指定したハッシュタグの投稿が流れる (AND + OR)', () => new Promise<void>(async done => {
|
it('指定したハッシュタグの投稿が流れる (AND + OR)', () => new Promise(async done => {
|
||||||
let fooCount = 0;
|
let fooCount = 0;
|
||||||
let barCount = 0;
|
let barCount = 0;
|
||||||
let fooBarCount = 0;
|
let fooBarCount = 0;
|
|
@ -2,18 +2,16 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils.js';
|
import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
describe('Note thread mute', () => {
|
describe('Note thread mute', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
let alice: any;
|
let p;
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
let alice, bob, carol;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
@ -37,9 +35,9 @@ describe('Note thread mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolReply.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === carolReply.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolReplyWithoutMention.id), false);
|
assert.strictEqual(res.body.some((note) => note.id === carolReplyWithoutMention.id), false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async(async () => {
|
it('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async(async () => {
|
||||||
|
@ -97,8 +95,8 @@ describe('Note thread mute', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReply.id), false);
|
assert.strictEqual(res.body.some((notification) => notification.note.id === carolReply.id), false);
|
||||||
assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReplyWithoutMention.id), false);
|
assert.strictEqual(res.body.some((notification) => notification.note.id === carolReplyWithoutMention.id), false);
|
||||||
|
|
||||||
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
||||||
}));
|
}));
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"noEmitOnError": false,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"declaration": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"target": "es2017",
|
|
||||||
"module": "es2020",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"removeComments": false,
|
|
||||||
"noLib": false,
|
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictPropertyInitialization": false,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["../src/*"]
|
|
||||||
},
|
|
||||||
"typeRoots": [
|
|
||||||
"../node_modules/@types",
|
|
||||||
"../src/@types"
|
|
||||||
],
|
|
||||||
"lib": [
|
|
||||||
"esnext"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"compileOnSave": false,
|
|
||||||
"include": [
|
|
||||||
"./**/*.ts"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -2,19 +2,16 @@ process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import { async, signup, request, post, uploadUrl, startServer, shutdownServer } from './utils.js';
|
import { async, signup, request, post, uploadUrl, startServer, shutdownServer } from './utils.mjs';
|
||||||
|
|
||||||
describe('users/notes', () => {
|
describe('users/notes', function() {
|
||||||
let p: childProcess.ChildProcess;
|
this.timeout(20*60*1000);
|
||||||
|
|
||||||
let alice: any;
|
let p;
|
||||||
let jpgNote: any;
|
|
||||||
let pngNote: any;
|
let alice, jpgNote, pngNote, jpgPngNote;
|
||||||
let jpgPngNote: any;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.timeout(0);
|
|
||||||
|
|
||||||
p = await startServer();
|
p = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||||
|
@ -43,8 +40,8 @@ describe('users/notes', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.length, 2);
|
assert.strictEqual(res.body.length, 2);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === jpgNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === jpgNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === jpgPngNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === jpgPngNote.id), true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('ファイルタイプ指定 (jpg or png)', async(async () => {
|
it('ファイルタイプ指定 (jpg or png)', async(async () => {
|
||||||
|
@ -56,8 +53,8 @@ describe('users/notes', () => {
|
||||||
assert.strictEqual(res.status, 200);
|
assert.strictEqual(res.status, 200);
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
assert.strictEqual(Array.isArray(res.body), true);
|
||||||
assert.strictEqual(res.body.length, 3);
|
assert.strictEqual(res.body.length, 3);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === jpgNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === jpgNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === pngNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === pngNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === jpgPngNote.id), true);
|
assert.strictEqual(res.body.some((note) => note.id === jpgPngNote.id), true);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
|
@ -10,8 +10,8 @@ import * as foundkey from 'foundkey-js';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import loadConfig from '../src/config/load.js';
|
import { loadConfig } from '../built/config/load.js';
|
||||||
import { entities } from '../src/db/postgre.js';
|
import { entities } from '../built/db/postgre.js';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
|
@ -20,20 +20,20 @@ const _dirname = dirname(_filename);
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
export const port = config.port;
|
export const port = config.port;
|
||||||
|
|
||||||
export const async = (fn: Function) => (done: Function) => {
|
export const async = (fn) => (done) => {
|
||||||
fn().then(() => {
|
fn().then(() => {
|
||||||
done();
|
done();
|
||||||
}, (err: Error) => {
|
}, (err) => {
|
||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const api = async (endpoint: string, params: any, me?: any) => {
|
export const api = async (endpoint, params, me) => {
|
||||||
endpoint = endpoint.replace(/^\//, '');
|
endpoint = endpoint.replace(/^\//, '');
|
||||||
|
|
||||||
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
||||||
|
|
||||||
const res = await got<string>(`http://localhost:${port}/api/${endpoint}`, {
|
const res = await got(`http://localhost:${port}/api/${endpoint}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -63,7 +63,7 @@ export const api = async (endpoint: string, params: any, me?: any) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => {
|
export const request = async (endpoint, params, me) => {
|
||||||
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
||||||
|
|
||||||
const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
|
const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
|
||||||
|
@ -83,7 +83,7 @@ export const request = async (endpoint: string, params: any, me?: any): Promise<
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signup = async (params?: any): Promise<any> => {
|
export const signup = async (params) => {
|
||||||
const q = Object.assign({
|
const q = Object.assign({
|
||||||
username: 'test',
|
username: 'test',
|
||||||
password: 'test',
|
password: 'test',
|
||||||
|
@ -94,7 +94,7 @@ export const signup = async (params?: any): Promise<any> => {
|
||||||
return res.body;
|
return res.body;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const post = async (user: any, params?: foundkey.Endpoints['notes/create']['req']): Promise<foundkey.entities.Note> => {
|
export const post = async (user, params) => {
|
||||||
const q = Object.assign({
|
const q = Object.assign({
|
||||||
text: 'test',
|
text: 'test',
|
||||||
}, params);
|
}, params);
|
||||||
|
@ -104,7 +104,7 @@ export const post = async (user: any, params?: foundkey.Endpoints['notes/create'
|
||||||
return res.body ? res.body.createdNote : null;
|
return res.body ? res.body.createdNote : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const react = async (user: any, note: any, reaction: string): Promise<any> => {
|
export const react = async (user, note, reaction) => {
|
||||||
await api('notes/reactions/create', {
|
await api('notes/reactions/create', {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
|
@ -116,10 +116,10 @@ export const react = async (user: any, note: any, reaction: string): Promise<any
|
||||||
* @param user User
|
* @param user User
|
||||||
* @param _path Optional, absolute path or relative from ./resources/
|
* @param _path Optional, absolute path or relative from ./resources/
|
||||||
*/
|
*/
|
||||||
export const uploadFile = async (user: any, _path?: string): Promise<any> => {
|
export const uploadFile = async (user, _path) => {
|
||||||
const absPath = _path == null ? `${_dirname}/resources/Lenna.jpg` : path.isAbsolute(_path) ? _path : `${_dirname}/resources/${_path}`;
|
const absPath = _path == null ? `${_dirname}/resources/Lenna.jpg` : path.isAbsolute(_path) ? _path : `${_dirname}/resources/${_path}`;
|
||||||
|
|
||||||
const formData = new FormData() as any;
|
const formData = new FormData();
|
||||||
formData.append('i', user.token);
|
formData.append('i', user.token);
|
||||||
formData.append('file', fs.createReadStream(absPath));
|
formData.append('file', fs.createReadStream(absPath));
|
||||||
formData.append('force', 'true');
|
formData.append('force', 'true');
|
||||||
|
@ -137,8 +137,8 @@ export const uploadFile = async (user: any, _path?: string): Promise<any> => {
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadUrl = async (user: any, url: string) => {
|
export const uploadUrl = async (user, url) => {
|
||||||
let file: any;
|
let file;
|
||||||
|
|
||||||
const ws = await connectStream(user, 'main', (msg) => {
|
const ws = await connectStream(user, 'main', (msg) => {
|
||||||
if (msg.type === 'driveFileCreated') {
|
if (msg.type === 'driveFileCreated') {
|
||||||
|
@ -157,7 +157,7 @@ export const uploadUrl = async (user: any, url: string) => {
|
||||||
return file;
|
return file;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
|
export function connectStream(user, channel, listener, params) {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const ws = new WebSocket(`ws://localhost:${port}/streaming?i=${user.token}`);
|
const ws = new WebSocket(`ws://localhost:${port}/streaming?i=${user.token}`);
|
||||||
|
|
||||||
|
@ -184,11 +184,11 @@ export function connectStream(user: any, channel: string, listener: (message: Re
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const waitFire = async (user: any, channel: string, trgr: () => any, cond: (msg: Record<string, any>) => boolean, params?: any) => {
|
export const waitFire = async (user, channel, trgr, cond, params) => {
|
||||||
return new Promise<boolean>(async (res, rej) => {
|
return new Promise(async (res, rej) => {
|
||||||
let timer: NodeJS.Timeout;
|
let timer;
|
||||||
|
|
||||||
let ws: WebSocket;
|
let ws;
|
||||||
try {
|
try {
|
||||||
ws = await connectStream(user, channel, msg => {
|
ws = await connectStream(user, channel, msg => {
|
||||||
if (cond(msg)) {
|
if (cond(msg)) {
|
||||||
|
@ -201,7 +201,7 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond
|
||||||
rej(e);
|
rej(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ws!) return;
|
if (!ws) return;
|
||||||
|
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
ws.close();
|
ws.close();
|
||||||
|
@ -218,7 +218,7 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?: number, type?: string, location?: string }> => {
|
export const simpleGet = async (path, accept = '*/*') => {
|
||||||
// node-fetchだと3xxを取れない
|
// node-fetchだと3xxを取れない
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const req = http.request(`http://localhost:${port}${path}`, {
|
const req = http.request(`http://localhost:${port}${path}`, {
|
||||||
|
@ -226,7 +226,7 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?
|
||||||
Accept: accept,
|
Accept: accept,
|
||||||
},
|
},
|
||||||
}, res => {
|
}, res => {
|
||||||
if (res.statusCode! >= 400) {
|
if (res.statusCode >= 400) {
|
||||||
reject(res);
|
reject(res);
|
||||||
} else {
|
} else {
|
||||||
resolve({
|
resolve({
|
||||||
|
@ -241,8 +241,8 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProcess) => void, moreProcess: () => Promise<void> = async () => {}) {
|
export function launchServer(callbackSpawnedProcess, moreProcess = async () => {}) {
|
||||||
return (done: (err?: Error) => any) => {
|
return (done) => {
|
||||||
const p = childProcess.spawn('node', [_dirname + '/../index.js'], {
|
const p = childProcess.spawn('node', [_dirname + '/../index.js'], {
|
||||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||||
env: { NODE_ENV: 'test', PATH: process.env.PATH },
|
env: { NODE_ENV: 'test', PATH: process.env.PATH },
|
||||||
|
@ -254,7 +254,7 @@ export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProce
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initTestDb(justBorrow = false, initEntities?: any[]) {
|
export async function initTestDb(justBorrow = false, initEntities) {
|
||||||
if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
|
if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
|
||||||
|
|
||||||
const db = new DataSource({
|
const db = new DataSource({
|
||||||
|
@ -274,7 +274,7 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startServer(timeout = 60 * 1000): Promise<childProcess.ChildProcess> {
|
export function startServer(timeout = 60 * 1000) {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
p.kill(SIGKILL);
|
p.kill(SIGKILL);
|
||||||
|
@ -297,7 +297,7 @@ export function startServer(timeout = 60 * 1000): Promise<childProcess.ChildProc
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shutdownServer(p: childProcess.ChildProcess, timeout = 20 * 1000) {
|
export function shutdownServer(p, timeout = 20 * 1000) {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
p.kill(SIGKILL);
|
p.kill(SIGKILL);
|
||||||
|
@ -313,8 +313,8 @@ export function shutdownServer(p: childProcess.ChildProcess, timeout = 20 * 1000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sleep(msec: number) {
|
export function sleep(msec) {
|
||||||
return new Promise<void>(res => {
|
return new Promise(res => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
res();
|
res();
|
||||||
}, msec);
|
}, msec);
|
|
@ -36,10 +36,6 @@ export default defineComponent({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
rootScale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -53,7 +49,7 @@ export default defineComponent({
|
||||||
return t.match(/^[0-9.]+s$/) ? t : null;
|
return t.match(/^[0-9.]+s$/) ? t : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const genEl = (ast: mfm.MfmNode[], scale: Number) => ast.map((token): VNode | VNode[] => {
|
const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | VNode[] => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
||||||
|
@ -72,17 +68,17 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'bold': {
|
case 'bold': {
|
||||||
return h('b', genEl(token.children, scale));
|
return h('b', genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'strike': {
|
case 'strike': {
|
||||||
return h('del', genEl(token.children, scale));
|
return h('del', genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'italic': {
|
case 'italic': {
|
||||||
return h('i', {
|
return h('i', {
|
||||||
style: 'font-style: oblique;',
|
style: 'font-style: oblique;',
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'fn': {
|
case 'fn': {
|
||||||
|
@ -143,17 +139,17 @@ export default defineComponent({
|
||||||
case 'x2': {
|
case 'x2': {
|
||||||
return h('span', {
|
return h('span', {
|
||||||
class: 'mfm-x2'
|
class: 'mfm-x2'
|
||||||
}, genEl(token.children, scale * 2));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
case 'x3': {
|
case 'x3': {
|
||||||
return h('span', {
|
return h('span', {
|
||||||
class: 'mfm-x3'
|
class: 'mfm-x3'
|
||||||
}, genEl(token.children, scale * 3));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
case 'x4': {
|
case 'x4': {
|
||||||
return h('span', {
|
return h('span', {
|
||||||
class: 'mfm-x4'
|
class: 'mfm-x4'
|
||||||
}, genEl(token.children, scale * 4));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
case 'font': {
|
case 'font': {
|
||||||
const family =
|
const family =
|
||||||
|
@ -170,7 +166,7 @@ export default defineComponent({
|
||||||
case 'blur': {
|
case 'blur': {
|
||||||
return h('span', {
|
return h('span', {
|
||||||
class: '_mfm_blur_',
|
class: '_mfm_blur_',
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
case 'rainbow': {
|
case 'rainbow': {
|
||||||
const speed = validTime(token.props.args.speed) || '1s';
|
const speed = validTime(token.props.args.speed) || '1s';
|
||||||
|
@ -179,9 +175,9 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
case 'sparkle': {
|
case 'sparkle': {
|
||||||
if (!this.$store.state.animatedMfm) {
|
if (!this.$store.state.animatedMfm) {
|
||||||
return genEl(token.children, scale);
|
return genEl(token.children);
|
||||||
}
|
}
|
||||||
return h(MkSparkle, {}, genEl(token.children, scale));
|
return h(MkSparkle, {}, genEl(token.children));
|
||||||
}
|
}
|
||||||
case 'rotate': {
|
case 'rotate': {
|
||||||
const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90';
|
const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90';
|
||||||
|
@ -191,48 +187,47 @@ export default defineComponent({
|
||||||
case 'position': {
|
case 'position': {
|
||||||
const x = parseFloat(token.props.args.x ?? '0');
|
const x = parseFloat(token.props.args.x ?? '0');
|
||||||
const y = parseFloat(token.props.args.y ?? '0');
|
const y = parseFloat(token.props.args.y ?? '0');
|
||||||
style = `transform: translateX(${x}em) translateY(${y}em);`;
|
style = `transform: translate(${x}em, ${y}em);`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'scale': {
|
case 'scale': {
|
||||||
const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
|
const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
|
||||||
const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
|
const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
|
||||||
style = `transform: scale(${x}, ${y});`;
|
style = `transform: scale(${x}, ${y});`;
|
||||||
scale = scale * Math.max(x, y);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'fg': {
|
case 'fg': {
|
||||||
let color = token.props.args.color;
|
let color = token.props.args.color ?? 'f00';
|
||||||
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
|
if (!/^([0-9a-f]{3}){1,2}$/i.test(color)) color = 'f00';
|
||||||
style = `color: #${color};`;
|
style = `color: #${color};`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'bg': {
|
case 'bg': {
|
||||||
let color = token.props.args.color;
|
let color = token.props.args.color ?? '0f0';
|
||||||
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
|
if (!/^([0-9a-f]{3}){1,2}$/i.test(color)) color = '0f0';
|
||||||
style = `background-color: #${color};`;
|
style = `background-color: #${color};`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (style == null) {
|
if (style == null) {
|
||||||
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);
|
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']);
|
||||||
} else {
|
} else {
|
||||||
return h('span', {
|
return h('span', {
|
||||||
style: 'display: inline-block;' + style,
|
style: 'display: inline-block;' + style,
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'small': {
|
case 'small': {
|
||||||
return h('small', {
|
return h('small', {
|
||||||
class: '_mfm_small_'
|
class: '_mfm_small_'
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'center': {
|
case 'center': {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
style: 'text-align:center;',
|
style: 'text-align:center;',
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'url': {
|
case 'url': {
|
||||||
|
@ -248,7 +243,7 @@ export default defineComponent({
|
||||||
key: Math.random(),
|
key: Math.random(),
|
||||||
url: token.props.url,
|
url: token.props.url,
|
||||||
rel: 'nofollow noopener',
|
rel: 'nofollow noopener',
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'mention': {
|
case 'mention': {
|
||||||
|
@ -286,7 +281,7 @@ export default defineComponent({
|
||||||
case 'quote': {
|
case 'quote': {
|
||||||
return h(this.nowrap ? 'span' : 'div', {
|
return h(this.nowrap ? 'span' : 'div', {
|
||||||
class: 'quote',
|
class: 'quote',
|
||||||
}, genEl(token.children, scale));
|
}, genEl(token.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'emojiCode': {
|
case 'emojiCode': {
|
||||||
|
@ -339,6 +334,6 @@ export default defineComponent({
|
||||||
}).flat();
|
}).flat();
|
||||||
|
|
||||||
// Parse ast to DOM
|
// Parse ast to DOM
|
||||||
return h('span', genEl(ast, this.rootScale ?? 1));
|
return h('span', genEl(ast));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility">
|
<button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility">
|
||||||
<span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span>
|
<span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span>
|
||||||
<span v-else-if="visibility === 'home'"><i class="fas fa-home"></i></span>
|
<span v-else-if="visibility === 'home'"><i class="fas fa-home"></i></span>
|
||||||
<span v-else-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span>
|
<span v-else-if="visibility === 'followers'"><i class="fas fa-lock"></i></span>
|
||||||
<span v-else-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span>
|
<span v-else-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span>
|
||||||
</button>
|
</button>
|
||||||
<button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button>
|
<button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button key="followers" :disabled="disabled['followers']" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
|
<button key="followers" :disabled="disabled['followers']" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
|
||||||
<div><i class="fas fa-unlock"></i></div>
|
<div><i class="fas fa-lock"></i></div>
|
||||||
<div>
|
<div>
|
||||||
<span>{{ i18n.ts._visibility.followers }}</span>
|
<span>{{ i18n.ts._visibility.followers }}</span>
|
||||||
<span>{{ i18n.ts._visibility.followersDescription }}</span>
|
<span>{{ i18n.ts._visibility.followersDescription }}</span>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<span v-if="note.visibility !== 'public'" :class="$style.visibility" :title="i18n.ts._visibility[note.visibility]">
|
<span v-if="note.visibility !== 'public'" :class="$style.visibility" :title="i18n.ts._visibility[note.visibility]">
|
||||||
<i v-if="note.visibility === 'home'" class="fas fa-home"></i>
|
<i v-if="note.visibility === 'home'" class="fas fa-home"></i>
|
||||||
<i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i>
|
<i v-else-if="note.visibility === 'followers'" class="fas fa-lock"></i>
|
||||||
<i v-else-if="note.visibility === 'specified'" ref="specified" class="fas fa-envelope"></i>
|
<i v-else-if="note.visibility === 'specified'" ref="specified" class="fas fa-envelope"></i>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="note.localOnly" :class="$style.localOnly" :title="i18n.ts._visibility['localOnly']">
|
<span v-if="note.localOnly" :class="$style.localOnly" :title="i18n.ts._visibility['localOnly']">
|
||||||
|
|
|
@ -50,10 +50,6 @@
|
||||||
<template #key>{{ i18n.ts.receivedReactionsCount }}</template>
|
<template #key>{{ i18n.ts.receivedReactionsCount }}</template>
|
||||||
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
|
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
|
||||||
<template #key>{{ i18n.ts.noteFavoritesCount }}</template>
|
|
||||||
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue oneline style="margin: 1em 0;">
|
<MkKeyValue oneline style="margin: 1em 0;">
|
||||||
<template #key>{{ i18n.ts.followingCount }}</template>
|
<template #key>{{ i18n.ts.followingCount }}</template>
|
||||||
<template #value>{{ number(stats.followingCount) }}</template>
|
<template #value>{{ number(stats.followingCount) }}</template>
|
||||||
|
|
|
@ -159,12 +159,14 @@ definePageMetadata(computed(() => ({
|
||||||
top: calc(var(--stickyTop, 0px) + 16px);
|
top: calc(var(--stickyTop, 0px) + 16px);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
display: block;
|
display: block;
|
||||||
margin: var(--margin) auto 0 auto;
|
margin: var(--margin) auto 0 auto;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
|
pointer-events: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
|
||||||
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
|
||||||
|
|
||||||
if (text === '') return false;
|
if (text === '') return false;
|
||||||
|
const textLower = text.toLowerCase();
|
||||||
|
|
||||||
const matched = mutedWords.some(filter => {
|
const matched = mutedWords.some(filter => {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
|
@ -13,7 +14,7 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
|
||||||
const filteredFilter = filter.filter(keyword => keyword !== '');
|
const filteredFilter = filter.filter(keyword => keyword !== '');
|
||||||
if (filteredFilter.length === 0) return false;
|
if (filteredFilter.length === 0) return false;
|
||||||
|
|
||||||
return filteredFilter.every(keyword => text.includes(keyword));
|
return filteredFilter.every(keyword => textLower.includes(keyword.toLowerCase()));
|
||||||
} else {
|
} else {
|
||||||
// represents RegExp
|
// represents RegExp
|
||||||
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
||||||
|
|
|
@ -243,7 +243,7 @@ export class ColdDeviceStorage {
|
||||||
plugins: [] as Plugin[],
|
plugins: [] as Plugin[],
|
||||||
mediaVolume: 0.5,
|
mediaVolume: 0.5,
|
||||||
sound_masterVolume: 0.3,
|
sound_masterVolume: 0.3,
|
||||||
sound_note: { type: 'syuilo/down', volume: 1 },
|
sound_note: { type: 'syuilo/down', volume: 0 },
|
||||||
sound_noteMy: { type: 'syuilo/up', volume: 1 },
|
sound_noteMy: { type: 'syuilo/up', volume: 1 },
|
||||||
sound_notification: { type: 'syuilo/pope2', volume: 1 },
|
sound_notification: { type: 'syuilo/pope2', volume: 1 },
|
||||||
sound_chat: { type: 'syuilo/pope1', volume: 1 },
|
sound_chat: { type: 'syuilo/pope1', volume: 1 },
|
||||||
|
|
|
@ -33,7 +33,6 @@ export type UserLite = {
|
||||||
|
|
||||||
export type UserDetailed = UserLite & {
|
export type UserDetailed = UserLite & {
|
||||||
bannerBlurhash: string | null;
|
bannerBlurhash: string | null;
|
||||||
bannerColor: string | null;
|
|
||||||
bannerUrl: string | null;
|
bannerUrl: string | null;
|
||||||
birthday: string | null;
|
birthday: string | null;
|
||||||
createdAt: DateString;
|
createdAt: DateString;
|
||||||
|
|
|
@ -3717,7 +3717,6 @@ __metadata:
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
content-disposition: 0.5.4
|
content-disposition: 0.5.4
|
||||||
cross-env: 7.0.3
|
|
||||||
date-fns: 2.28.0
|
date-fns: 2.28.0
|
||||||
deep-email-validator: 0.1.21
|
deep-email-validator: 0.1.21
|
||||||
escape-regexp: 0.0.1
|
escape-regexp: 0.0.1
|
||||||
|
|
Loading…
Reference in a new issue