diff --git a/.gitignore b/.gitignore index 6bf2ea1b14..ce9e4f8a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ coverage !/.config/helm_values_example.yml !/.config/LICENSE -#docker dev config +# docker dev config /dev/docker-compose.yml # misskey @@ -46,6 +46,7 @@ files ormconfig.json packages/backend/assets/instance.css packages/backend/assets/sounds/None.mp3 +packages/backend/assets/LICENSE !packages/backend/src/db diff --git a/CALCKEY.md b/CALCKEY.md index 619155238c..e561c3a5a6 100644 --- a/CALCKEY.md +++ b/CALCKEY.md @@ -6,23 +6,16 @@ ## Planned - Stucture - - [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative - - Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes - Rewrite backend in Rust and [Rocket](https://rocket.rs/) - - Use [Magic RegExP](https://regexp.dev/) for RegEx 🦄 - Function - User "choices" (recommended users) and featured hashtags like Mastodon and Soapbox - Join Reason system like Mastodon/Pleroma - Option to publicize server blocks - More antenna options - Groups -- Form - - Lookup/details for post/file/server - - [Rat mode?](https://stop.voring.me/notes/933fx97bmd) ## Work in progress -- Link verification - Better Messaging UI - Better API Documentation - Remote follow button @@ -30,6 +23,7 @@ - Timeline filters - Events - Fully revamp non-logged-in screen +- Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes ## Implemented @@ -122,6 +116,8 @@ - Let moderators see moderation nodes - Non-mangled unicode emojis - Skin tone selection support +- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative +- Link verification ## Implemented (remote) diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 7e97a99ebc..59dda2bcfa 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1179,7 +1179,6 @@ _profile: youCanIncludeHashtags: "يمكنك أيضًا إضافة وسوم إلى سيرتك التعريفية." metadata: "معلومات إضافية" metadataEdit: "عدّل المعلومات الإضافية" - metadataDescription: "يُمكنك عرض 4 حقول معلومات في ملفك الشخصي" metadataLabel: "التسمية" metadataContent: "المحتوى" changeAvatar: "غيّر الصورة الرمزية" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index e3fbf8cb9b..2f37a02a2e 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -1268,7 +1268,7 @@ _profile: youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।" metadata: "অতিরিক্ত তথ্য" metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন" - metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।" + metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।. আপনি আপনার প্রোফাইলে লিঙ্কটি যাচাই করতে {rel} এর সাথে একটি {a} ট্যাগ বা {l} ট্যাগ যোগ করতে পারেন!" metadataLabel: "লেবেল" metadataContent: "বিষয়বস্তু" changeAvatar: "অ্যাভাটার পরিবর্তন করুন" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 8fb57e8797..b8263bf6ec 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -409,8 +409,9 @@ _profile: locationDescription: Si primer introduïu la vostra ciutat, es mostrarà l'hora local a altres usuaris. name: Nom - metadataDescription: Fent servir això, podràs mostrar camps d'informació addicionals - al vostre perfil. + metadataDescription: "Fent servir això, podràs mostrar camps d'informació addicionals + al vostre perfil. Podeu afegir una etiqueta {a} o una etiqueta {l} amb {rel} per + verificar l'enllaç al vostre perfil." _exportOrImport: followingList: "Usuaris que segueixes" muteList: "Silencia" @@ -2161,3 +2162,4 @@ remindMeLater: Potser després removeMember: Elimina el membre removeQuote: Elimina la cita removeRecipient: Elimina el destinatari +verifiedLink: Enllaç verificat diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 9e3a07654f..c2c89f15c0 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1551,7 +1551,7 @@ _profile: metadata: "Zusätzliche Informationen" metadataEdit: "Zusätzliche Informationen bearbeiten" metadataDescription: "Hierdurch kannst du auf deinem Profil zusätzliche Informationsblöcke - anzeigen lassen." + anzeigen lassen. Sie können ein {a}-Tag oder ein {l}-Tag mit {rel} hinzufügen, um den Link in Ihrem Profil zu überprüfen!" metadataLabel: "Beschriftung" metadataContent: "Inhalt" changeAvatar: "Profilbild ändern" diff --git a/locales/en-US.yml b/locales/en-US.yml index 76bc5c24a9..eeafa6ffdc 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1124,6 +1124,7 @@ remindMeLater: "Maybe later" removeQuote: "Remove quote" removeRecipient: "Remove recipient" removeMember: "Remove member" +verifiedLink: "Verified link" _sensitiveMediaDetection: description: "Reduces the effort of server moderation through automatically recognizing @@ -1676,8 +1677,10 @@ _profile: youCanIncludeHashtags: "You can also include hashtags in your bio." metadata: "Additional Information" metadataEdit: "Edit additional Information" - metadataDescription: "Using these, you can display additional information fields - in your profile." + metadataDescription: + "Using these, you can display additional information fields + in your profile. You can add an {a} tag or {l} tag with {rel} + to verify the link on your profile!" metadataLabel: "Label" metadataContent: "Content" changeAvatar: "Change avatar" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index faf9ba2820..f5c0025056 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1475,7 +1475,7 @@ _profile: youCanIncludeHashtags: "Puedes añadir hashtags" metadata: "información adicional" metadataEdit: "Editar información adicional" - metadataDescription: "Muestra la información adicional en el perfil" + metadataDescription: "Muestra la información adicional en el perfil. ¡Puede agregar una etiqueta {a} o una etiqueta {l} con {rel} para verificar el enlace en su perfil!" metadataLabel: "Etiqueta" metadataContent: "Contenido" changeAvatar: "Cambiar avatar" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index f81ef4520a..660be0347a 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1413,7 +1413,7 @@ _profile: metadata: "Informations supplémentaires" metadataEdit: "Éditer les informations supplémentaires" metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires - dans votre profil." + dans votre profil. Vous pouvez ajouter une balise {a} ou une balise {l} avec {rel} pour vérifier le lien sur votre profil!" metadataLabel: "Étiquette" metadataContent: "Contenu" changeAvatar: "Changer l'image de profil" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 17bebe99cf..6aaa726dba 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1399,7 +1399,7 @@ _profile: metadata: "Informasi tambahan" metadataEdit: "Sunting informasi tambahan" metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan\ - \ ke dalam profilmu." + \ ke dalam profilmu. Anda dapat menambahkan tag {a} atau tag {l} dengan {rel} untuk memverifikasi tautan di profil Anda!" metadataLabel: "Label" metadataContent: "Isi" changeAvatar: "Ubah avatar" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index bdf7cab541..93dd5fcf3c 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1266,7 +1266,7 @@ _profile: metadata: "Informazioni aggiuntive" metadataEdit: "Modifica informazioni aggiuntive" metadataDescription: "Puoi pubblicare fino a quattro informazioni aggiuntive sul - profilo." + profilo. Puoi aggiungere un tag {a} o {l} con {rel} per verificare il link sul tuo profilo!" metadataLabel: "Etichetta" metadataContent: "Contenuto" changeAvatar: "Modifica immagine profilo" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1043e35248..755afa8d3a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1491,7 +1491,7 @@ _profile: youCanIncludeHashtags: "ハッシュタグを含められます。" metadata: "追加情報" metadataEdit: "追加情報を編集" - metadataDescription: "プロフィールに表として追加情報を表示できます。" + metadataDescription: "プロフィールに表として追加情報を表示できます。{a}タグまたは{l}タグを{rel}とともに追加すると、プロフィールのリンクを確認できます。" metadataLabel: "ラベル" metadataContent: "内容" changeAvatar: "アバター画像を変更" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 2c8e548bde..459274166c 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1319,7 +1319,7 @@ _profile: youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다." metadata: "추가 정보" metadataEdit: "추가 정보 편집" - metadataDescription: "프로필에 추가 정보를 표시할 수 있어요" + metadataDescription: "프로필에 추가 정보를 표시할 수 있어요. {rel}과 함께 {a} 태그 또는 {l} 태그를 추가하여 프로필의 링크를 확인할 수 있습니다!" metadataLabel: "라벨" metadataContent: "내용" changeAvatar: "아바타 이미지 변경" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 571f6af951..58624303c7 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1404,7 +1404,7 @@ _profile: metadata: "Dodatkowe informacje" metadataEdit: "Edytuj dodatkowe informacje" metadataDescription: "Możesz wyświetlać do czterech sekcji dodatkowych informacji - na swoim profilu." + na swoim profilu. Możesz dodać tag {a} lub tag {l} z {rel}, aby zweryfikować link w swoim profilu!" metadataLabel: "Etykieta" metadataContent: "Treść" changeAvatar: "Zmień awatar" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index bfef68d106..643a84eef0 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1398,7 +1398,7 @@ _profile: youCanIncludeHashtags: "Можете использовать здесь хэштеги." metadata: "Дополнительные сведения" metadataEdit: "Редактировать дополнительные сведения" - metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль." + metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль. Вы можете добавить тег {a} или тег {l} с {rel}, чтобы подтвердить ссылку в своем профиле!" metadataLabel: "Метка" metadataContent: "Содержимое" changeAvatar: "Поменять аватар" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index dce23d7558..046e67aec9 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -1337,7 +1337,7 @@ _profile: youCanIncludeHashtags: "Vo svojom bio môžete mať aj hashtagy." metadata: "Dodatočné informácie" metadataEdit: "Upraviť dodatočné informácie" - metadataDescription: "Vo svojom profile môžete uviesť až štyri dodatočné informačné polia." + metadataDescription: "Vo svojom profile môžete uviesť až štyri dodatočné informačné polia. Dodate lahko oznako {a} ali oznako {l} z {rel}, da preverite povezavo v svojem profile!" metadataLabel: "Popisok" metadataContent: "Obsah" changeAvatar: "Zmeniť avatara" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index a3021221de..f73d693a80 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -182,7 +182,7 @@ _profile: gösterecektir. youCanIncludeHashtags: Hakkımdan'da etiket kullanabilirsin. description: Hakkımda - metadataDescription: Bunları kullanarak profilinizde ek bilgi alanları görüntüleyebilirsiniz. + metadataDescription: 'Bunları kullanarak profilinizde ek bilgi alanları görüntüleyebilirsiniz. Profilinizdeki bağlantıyı doğrulamak için {rel} ile bir {a} etiketi veya {l} etiketi ekleyebilirsiniz!' metadata: Ek Bilgi metadataContent: İçerik metadataLabel: Etiket diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 78aaf2bbd1..6bd72b75be 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -155,7 +155,7 @@ flagAsBotDescription: "Ввімкніть якщо цей обліковий з flagAsCat: "Акаунт кота" flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком, та отримати котячі вуха!" -flagShowTimelineReplies: "Показувати відповіді на нотатки на часовій шкалі" +flagShowTimelineReplies: "Показувати відповіді на записи в стрічці" flagShowTimelineRepliesDescription: "Показує відповіді користувачів на записи інших користувачів у стрічці." autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на @@ -1250,7 +1250,7 @@ _poll: _visibility: public: "Публічний" publicDescription: "Ваш запис буде видно в усіх публічних стрічках" - home: "Скритий" + home: "Домашній" homeDescription: "Лише на домашній стрічці" followers: "Підписники" followersDescription: "Зробити видимим тільки для ваших підписників і згаданих користувачів" @@ -1277,7 +1277,8 @@ _profile: metadata: "Додаткова інформація" metadataEdit: "Редагувати додаткову інформацію" metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації - у своєму профілі." + у своєму профілі. Ви можете додати тег {a} або {l} за допомогою {rel}, щоб підтвердити + посилання у своєму профілі!" metadataLabel: "Назва" metadataContent: "Вміст" changeAvatar: "Змінити аватар" @@ -2131,3 +2132,4 @@ customSplashIconsDescription: URL-адреси іконок для застав які будуть показуватися випадковим чином щоразу, коли користувач завантажує/перезавантажує сторінку. Будь ласка, переконайтеся, що зображення знаходяться на статичній URL-адресі, бажано, щоб вони були змінені до розміру 192x192. +verifiedLink: Перевірене посилання diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index ddd79084fc..40b3909cec 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1342,7 +1342,7 @@ _profile: youCanIncludeHashtags: "Bạn có thể dùng hashtag trong tiểu sử." metadata: "Thông tin bổ sung" metadataEdit: "Sửa thông tin bổ sung" - metadataDescription: "Sử dụng phần này, bạn có thể hiển thị các mục thông tin bổ sung trong hồ sơ của mình." + metadataDescription: "Sử dụng phần này, bạn có thể hiển thị các mục thông tin bổ sung trong hồ sơ của mình. Bạn có thể thêm thẻ {a} hoặc thẻ {l} với {rel} để xác minh liên kết trên tiểu sử của mình!" metadataLabel: "Nhãn" metadataContent: "Nội dung" changeAvatar: "Đổi ảnh đại diện" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 36453551da..a0353e0ec8 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1402,7 +1402,7 @@ _profile: youCanIncludeHashtags: "您可以包含一个话题标签。" metadata: "附加信息" metadataEdit: "附加信息编辑" - metadataDescription: "使用这些,您可以在您的个人资料中显示其它信息字段。" + metadataDescription: "使用这些,您可以在您的个人资料中显示其它信息字段。您可以添加带有 {rel} 的 {a} 标签或 {l} 标签来验证您个人资料上的链接!" metadataLabel: "标签" metadataContent: "内容" changeAvatar: "修改头像" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index d1bad4ad1d..8b59b63a74 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -984,6 +984,12 @@ _aboutMisskey: donate: "贊助Calckey" morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰" patrons: "贊助者" + patronsList: 按時間順序列出,而不是按贊助規模列出。使用上面的連結贊助,在這裡獲得顯示您名字的機會! + sponsors: Calckey 贊助者們 + donateTitle: 覺得 Calckey 棒嗎? + pleaseDonateToCalckey: 請考慮向 Calckey 贊助以支持其發展。 + pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。 + donateHost: 贊助給 {host} _nsfw: respect: "隱藏敏感內容" ignore: "不隱藏敏感內容" @@ -1060,6 +1066,8 @@ _mfm: position: 位置 alwaysPlay: 自動播放所有MFM動畫 positionDescription: 按指定數量移動內容。 + advancedDescription: 如果禁用,則僅允許基本標記,除非正在播放 MFM 動畫 + advanced: 高級MFM _instanceTicker: none: "隱藏" remote: "向遠端使用者顯示" @@ -1202,14 +1210,14 @@ _tutorial: step1_1: "歡迎!" step1_2: "讓我們把你安排好。你很快就會啟動並運行!" step2_1: "首先,請完成你的個人資料。" - step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的帖子或關注你。" + step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的貼文或關注你。" step3_1: "現在是時候追隨一些人了!" step3_2: "你的主頁和社交時間線是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號圈就可以關注它。" step4_1: "讓我們出去找你。" step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\"" step5_1: "時間線,到處都是時間線!" step5_2: "您的伺服器已啟用了{timelines}個時間線。" - step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的帖子。" + step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的貼文。" step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。" step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。" step5_6: "推薦 {icon} 時間線是顯示你的伺服器管理員推薦的貼文。" @@ -1361,7 +1369,7 @@ _profile: youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag。" metadata: "進階資訊" metadataEdit: "編輯進階資訊" - metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。" + metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。您可以添加帶有 {rel} 的 {a} 標籤或 {l} 標籤來驗證您個人資料上的鏈接!" metadataLabel: "標籤" metadataContent: "内容" changeAvatar: "更換大頭貼" @@ -1820,12 +1828,12 @@ _experiments: title: 試驗功能 findOtherInstance: 找找另一個伺服器 noGraze: 瀏覽器擴展 "Graze for Mastodon" 會與Calckey發生衝突,請停用該擴展。 -userSaysSomethingReasonRenote: '{name} 轉傳了包含 {reason} 的帖子' +userSaysSomethingReasonRenote: '{name} 轉傳了包含 {reason} 的貼文' pushNotificationNotSupported: 你的瀏覽器或伺服器不支援推送通知 accessibility: 輔助功能 -userSaysSomethingReasonReply: '{name} 回復了包含 {reason} 的帖子' +userSaysSomethingReasonReply: '{name} 回覆了包含 {reason} 的貼文' hiddenTags: 隱藏主題標籤 -indexPosts: 索引帖子 +indexPosts: 索引貼文 indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。 deleted: 已刪除 editNote: 編輯筆記 @@ -1861,9 +1869,33 @@ audio: 音訊 sendPushNotificationReadMessageCaption: 包含文本 “{emptyPushNotificationMessage}” 的通知將顯示一小段時間。 這可能會增加您設備的電池使用量(如果適用)。 channelFederationWarn: 頻道功能尚未與聯邦宇宙連動 -swipeOnMobile: 允許在頁面之間滑動 +swipeOnMobile: 允許以滑動在頁面之間切換 sendPushNotificationReadMessage: 閱讀相關通知或消息後刪除推送通知 image: 圖片 seperateRenoteQuote: 分別獨立的轉傳及引用按鈕 clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。 noteId: 貼文 ID +sendModMail: 發送審核通知 +enableIdenticonGeneration: 啟用碎片生成 +enableServerMachineStats: 啟用伺服器硬體統計資訊 +reactionPickerSkinTone: 首選表情符號膚色 +indexFromDescription: 留空以索引每個貼文 +preventAiLearning: 防止 AI 機器人抓取 +preventAiLearningDescription: 請求第三方 AI 語言模型不要研究您上傳的內容,例如貼文和圖像。 +indexFrom: 從貼文 ID 開始的索引 +isLocked: 該帳戶已獲得以下批准 +isModerator: 板主 +isAdmin: 管理員 +isPatron: Calckey 項目贊助者 +silencedWarning: 顯示此頁面是因為這些使用者來自您伺服器管理員已靜音的伺服器,因此他們可能是垃圾訊息。 +signupsDisabled: 該伺服器上的註冊當前已被禁用,但您隨時可以在另一台伺服器上註冊!或是您有該伺服器的邀請碼,請在下面輸入。 +showPopup: 通過彈出式視窗通知用戶 +showWithSparkles: 閃閃發光的顯示 +youHaveUnreadAnnouncements: 您有未讀的公告 +donationLink: 連結到贊助頁面 +neverShow: 不再顯示 +remindMeLater: 可能之後 +removeQuote: 删除引用 +removeRecipient: 刪除收件者 +removeMember: 刪除成員 +isBot: 此帳戶是機器人 diff --git a/package.json b/package.json index a30d85b3d3..d4460c8788 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "14.0.0-dev78", + "version": "14.0.0-dev79", "codename": "aqua", "repository": { "type": "git", @@ -57,7 +57,7 @@ "gulp-replace": "1.1.4", "gulp-terser": "2.1.0", "install-peers": "^1.0.4", - "rome": "^12.1.3", + "rome": "^v12.1.3-nightly.f65b0d9", "start-server-and-test": "1.15.2", "typescript": "5.1.6" } diff --git a/packages/backend/assets/LICENSE b/packages/backend/assets/LICENSE new file mode 100644 index 0000000000..342509dec1 --- /dev/null +++ b/packages/backend/assets/LICENSE @@ -0,0 +1,13 @@ +Copyright 2023 Calckey + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/backend/migration/1678426061773-tweak-varchar-length.js b/packages/backend/migration/1678426061773-tweak-varchar-length.js index 8833745991..00ddcaebea 100644 --- a/packages/backend/migration/1678426061773-tweak-varchar-length.js +++ b/packages/backend/migration/1678426061773-tweak-varchar-length.js @@ -1,10 +1,16 @@ export class tweakVarcharLength1678426061773 { - name = 'tweakVarcharLength1678426061773' + name = "tweakVarcharLength1678426061773"; - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`, undefined); - await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`, undefined); - } + async up(queryRunner) { + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`, + undefined, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`, + undefined, + ); + } - async down(queryRunner) {} + async down(queryRunner) {} } diff --git a/packages/backend/native-utils/package.json b/packages/backend/native-utils/package.json index 385330d776..962b4bc4c4 100644 --- a/packages/backend/native-utils/package.json +++ b/packages/backend/native-utils/package.json @@ -43,6 +43,7 @@ "universal": "napi universal", "version": "napi version", "format": "cargo fmt --all", + "lint": "cargo clippy --fix", "cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration", "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test", "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1" diff --git a/packages/backend/native-utils/src/model/repository/antenna.rs b/packages/backend/native-utils/src/model/repository/antenna.rs index 7c614b954b..2b761173ea 100644 --- a/packages/backend/native-utils/src/model/repository/antenna.rs +++ b/packages/backend/native-utils/src/model/repository/antenna.rs @@ -46,7 +46,7 @@ impl Repository for antenna::Model { src: self.src.try_into()?, user_list_id: self.user_list_id, user_group_id, - users: self.users.into(), + users: self.users, instances: self.instances.into(), case_sensitive: self.case_sensitive, notify: self.notify, diff --git a/packages/backend/native-utils/src/model/schema/antenna.rs b/packages/backend/native-utils/src/model/schema/antenna.rs index 4ec1e07946..da2c3061b4 100644 --- a/packages/backend/native-utils/src/model/schema/antenna.rs +++ b/packages/backend/native-utils/src/model/schema/antenna.rs @@ -58,7 +58,7 @@ impl TryFrom for super::AntennaSrc { // ---- TODO: could be macro impl Schema for super::Antenna {} -pub static VALIDATOR: Lazy = Lazy::new(|| super::Antenna::validator()); +pub static VALIDATOR: Lazy = Lazy::new(super::Antenna::validator); // ---- cfg_if! { diff --git a/packages/backend/native-utils/src/model/schema/app.rs b/packages/backend/native-utils/src/model/schema/app.rs index 682b82ec07..9b5691154d 100644 --- a/packages/backend/native-utils/src/model/schema/app.rs +++ b/packages/backend/native-utils/src/model/schema/app.rs @@ -91,7 +91,7 @@ pub enum AppPermission { impl Schema for App {} -pub static VALIDATOR: Lazy = Lazy::new(|| App::validator()); +pub static VALIDATOR: Lazy = Lazy::new(App::validator); #[cfg(test)] mod unit_test { diff --git a/packages/backend/native-utils/tests/common.rs b/packages/backend/native-utils/tests/common.rs index 186e862bd5..b134319ca0 100644 --- a/packages/backend/native-utils/tests/common.rs +++ b/packages/backend/native-utils/tests/common.rs @@ -148,8 +148,8 @@ async fn setup_model(db: &DbConn) { let user_model = entity::user::Model { id: user_id.to_owned(), created_at: Utc::now().into(), - username: name.to_lowercase().to_string(), - username_lower: name.to_lowercase().to_string(), + username: name.to_lowercase(), + username_lower: name.to_lowercase(), name: Some(name.to_string()), token: Some(gen_string(16)), is_admin: true, diff --git a/packages/backend/native-utils/tests/model/repository/antenna.rs b/packages/backend/native-utils/tests/model/repository/antenna.rs index 3bda2ca183..80eea67718 100644 --- a/packages/backend/native-utils/tests/model/repository/antenna.rs +++ b/packages/backend/native-utils/tests/model/repository/antenna.rs @@ -43,18 +43,16 @@ mod int_test { keywords: vec![ vec!["foo".to_string(), "bar".to_string()], vec!["foobar".to_string()], - ] - .into(), + ], exclude_keywords: vec![ vec!["abc".to_string()], vec!["def".to_string(), "ghi".to_string()], - ] - .into(), + ], src: schema::AntennaSrc::All, user_list_id: None, user_group_id: None, - users: vec![].into(), - instances: vec![].into(), + users: vec![], + instances: vec![], case_sensitive: true, notify: true, with_replies: false, diff --git a/packages/backend/package.json b/packages/backend/package.json index fe8c078a03..8564ca8327 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -71,6 +71,7 @@ "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "20.0.3", + "json5": "2.2.3", "jsonld": "8.2.0", "jsrsasign": "10.8.6", "koa": "2.14.2", @@ -185,7 +186,6 @@ "cross-env": "7.0.3", "eslint": "^8.44.0", "execa": "6.1.0", - "json5": "2.2.3", "json5-loader": "4.0.1", "mocha": "10.2.0", "pug": "3.0.2", diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 49f012c5ea..2a955ee521 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,5 +1,8 @@ import config from "@/config/index.js"; -import { DB_MAX_NOTE_TEXT_LENGTH, DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; +import { + DB_MAX_NOTE_TEXT_LENGTH, + DB_MAX_IMAGE_COMMENT_LENGTH, +} from "@/misc/hard-limits.js"; export const MAX_NOTE_TEXT_LENGTH = Math.min( config.maxNoteLength ?? 3000, diff --git a/packages/backend/src/misc/download-url.ts b/packages/backend/src/misc/download-url.ts index 7fafb635ba..e9975f3486 100644 --- a/packages/backend/src/misc/download-url.ts +++ b/packages/backend/src/misc/download-url.ts @@ -24,6 +24,7 @@ export async function downloadUrl(url: string, path: string): Promise { .stream(url, { headers: { "User-Agent": config.userAgent, + Host: new URL(url).hostname, }, timeout: { lookup: timeout, diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index 119eecdc73..002247d3a3 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -51,6 +51,7 @@ export class UserProfile { public fields: { name: string; value: string; + verified?: boolean; }[]; @Column("varchar", { diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index d7580a4f62..93aed7cb8b 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -576,6 +576,16 @@ export default function () { { removeOnComplete: true, removeOnFail: true }, ); + systemQueue.add( + "verifyLinks", + {}, + { + repeat: { cron: "0 0 * * 0" }, + removeOnComplete: true, + removeOnFail: true, + }, + ); + processSystemQueue(systemQueue); } diff --git a/packages/backend/src/queue/processors/system/index.ts b/packages/backend/src/queue/processors/system/index.ts index 53321de5f9..697d24d067 100644 --- a/packages/backend/src/queue/processors/system/index.ts +++ b/packages/backend/src/queue/processors/system/index.ts @@ -5,6 +5,7 @@ import { cleanCharts } from "./clean-charts.js"; import { checkExpiredMutings } from "./check-expired-mutings.js"; import { clean } from "./clean.js"; import { setLocalEmojiSizes } from "./local-emoji-size.js"; +import { verifyLinks } from "./verify-links.js"; const jobs = { tickCharts, @@ -13,6 +14,7 @@ const jobs = { checkExpiredMutings, clean, setLocalEmojiSizes, + verifyLinks, } as Record< string, | Bull.ProcessCallbackFunction> diff --git a/packages/backend/src/queue/processors/system/verify-links.ts b/packages/backend/src/queue/processors/system/verify-links.ts new file mode 100644 index 0000000000..3ddda9baf4 --- /dev/null +++ b/packages/backend/src/queue/processors/system/verify-links.ts @@ -0,0 +1,44 @@ +import type Bull from "bull"; + +import { UserProfiles } from "@/models/index.js"; +import { Not } from "typeorm"; +import { queueLogger } from "../../logger.js"; +import { verifyLink } from "@/services/fetch-rel-me.js"; +import config from "@/config/index.js"; + +const logger = queueLogger.createSubLogger("verify-links"); + +export async function verifyLinks( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Verifying links..."); + + const usersToVerify = await UserProfiles.findBy({ + fields: Not(null), + userHost: "", + }); + for (const user of usersToVerify) { + for (const field of user.fields) { + if (!field || field.name === "" || field.value === "") { + continue; + } + if (field.value.startsWith("http") && user.user?.username) { + field.verified = await verifyLink(field.value, user.user.username); + } + } + if (user.fields.length > 0) { + try { + await UserProfiles.update(user.userId, { + fields: user.fields, + }); + } catch (e) { + logger.error(`Failed to update user ${user.userId} ${e}`); + done(e); + } + } + } + + logger.succ("All links successfully verified."); + done(); +} diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 0637251a6b..6d3bde2b8d 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -12,7 +12,9 @@ import type { UserProfile } from "@/models/entities/user-profile.js"; import { notificationTypes } from "@/types.js"; import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { langmap } from "@/misc/langmap.js"; +import { verifyLink } from "@/services/fetch-rel-me.js"; import { ApiError } from "../../error.js"; +import config from "@/config/index.js"; import define from "../../define.js"; export const meta = { @@ -58,6 +60,18 @@ export const meta = { code: "INVALID_REGEXP", id: "0d786918-10df-41cd-8f33-8dec7d9a89a5", }, + + invalidFieldName: { + message: "Invalid field name.", + code: "INVALID_FIELD_NAME", + id: "8f81972e-8b53-4d30-b0d2-efb026dda673", + }, + + invalidFieldValue: { + message: "Invalid field value.", + code: "INVALID_FIELD_VALUE", + id: "aede7444-244b-11ee-be56-0242ac120002", + }, }, res: { @@ -234,16 +248,29 @@ export default define(meta, paramDef, async (ps, _user, token) => { } if (ps.fields) { + for (const field of ps.fields) { + if (!field || field.name === "" || field.value === "") { + continue; + } + if (typeof field.name !== "string" || field.name === "") { + throw new ApiError(meta.errors.invalidFieldName); + } + if (typeof field.value !== "string" || field.value === "") { + throw new ApiError(meta.errors.invalidFieldValue); + } + if (field.value.startsWith("http")) { + field.verified = await verifyLink(field.value, user.username); + } + } + profileUpdates.fields = ps.fields - .filter( - (x) => - typeof x.name === "string" && - x.name !== "" && - typeof x.value === "string" && - x.value !== "", - ) + .filter((x) => Object.keys(x).length !== 0) .map((x) => { - return { name: x.name, value: x.value }; + return { + name: x.name, + value: x.value, + verified: x.verified, + }; }); } diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index a9c257bfeb..b3bb031244 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -1,4 +1,6 @@ import * as fs from "node:fs"; +import net from "node:net"; +import { promises } from "node:dns"; import type Koa from "koa"; import sharp from "sharp"; import type { IImage } from "@/services/drive/image-processor.js"; @@ -19,6 +21,40 @@ export async function proxyMedia(ctx: Koa.Context) { return; } + const { hostname } = new URL(url); + let resolvedIps; + try { + resolvedIps = await promises.resolve(hostname); + } catch (error) { + ctx.status = 400; + ctx.body = { message: "Invalid URL" }; + return; + } + + const isSSRF = resolvedIps.some((ip) => { + if (net.isIPv4(ip)) { + const parts = ip.split(".").map(Number); + return ( + parts[0] === 10 || + (parts[0] === 172 && parts[1] >= 16 && parts[1] < 32) || + (parts[0] === 192 && parts[1] === 168) || + parts[0] === 127 || + parts[0] === 0 + ); + } else if (net.isIPv6(ip)) { + return ( + ip.startsWith("::") || ip.startsWith("fc00:") || ip.startsWith("fe80:") + ); + } + return false; + }); + + if (isSSRF) { + ctx.status = 400; + ctx.body = { message: "Access to this URL is not allowed" }; + return; + } + // Create temp file const [path, cleanup] = await createTemp(); diff --git a/packages/backend/src/services/fetch-rel-me.ts b/packages/backend/src/services/fetch-rel-me.ts new file mode 100644 index 0000000000..7b450c229c --- /dev/null +++ b/packages/backend/src/services/fetch-rel-me.ts @@ -0,0 +1,33 @@ +import { getHtml } from "@/misc/fetch.js"; +import { JSDOM } from "jsdom"; +import config from "@/config/index.js"; + +async function getRelMeLinks(url: string): Promise { + try { + const html = await getHtml(url); + const dom = new JSDOM(html); + const relMeLinks = [ + ...dom.window.document.querySelectorAll("a[rel='me']"), + ...dom.window.document.querySelectorAll("link[rel='me']"), + ].map((a) => (a as HTMLAnchorElement | HTMLLinkElement).href); + return relMeLinks; + } catch { + return []; + } +} + +export async function verifyLink(link: string, username: string): Promise { + let verified = false; + if (link.startsWith("http")) { + const relMeLinks = await getRelMeLinks(link); + verified = relMeLinks.some((href) => + new RegExp( + `^https?:\/\/${config.host.replace( + /[.*+\-?^${}()|[\]\\]/g, + "\\$&", + )}\/@${username.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&")}$`, + ).test(href), + ); + } + return verified; +} diff --git a/packages/calckey-js/src/entities.ts b/packages/calckey-js/src/entities.ts index 5a581a54cd..346830e0f7 100644 --- a/packages/calckey-js/src/entities.ts +++ b/packages/calckey-js/src/entities.ts @@ -38,7 +38,11 @@ export type UserDetailed = UserLite & { createdAt: DateString; description: string | null; ffVisibility: "public" | "followers" | "private"; - fields: { name: string; value: string }[]; + fields: { + name: string; + value: string; + verified?: boolean; + }[]; followersCount: number; followingCount: number; hasPendingFollowRequestFromYou: boolean; diff --git a/packages/client/.eslintrc.json b/packages/client/.eslintrc.json new file mode 100644 index 0000000000..fd4718003d --- /dev/null +++ b/packages/client/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"], + "plugins": ["file-progress", "prettier"], + "rules": { + "file-progress/activate": 1 + } +} diff --git a/packages/client/package.json b/packages/client/package.json index 95fb9f9b2a..3fe101b811 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -4,11 +4,14 @@ "scripts": { "watch": "pnpm vite build --watch --mode development", "build": "pnpm vite build", - "lint": "pnpm rome check \"src/**/*.{ts,vue}\"", - "format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}'" + "lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue", + "lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache", + "format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata" }, "devDependencies": { "@discordapp/twemoji": "14.1.2", + "@eslint-sets/eslint-config-vue3": "^5.6.1", + "@eslint-sets/eslint-config-vue3-ts": "^3.3.0", "@phosphor-icons/web": "^2.0.3", "@rollup/plugin-alias": "3.1.9", "@rollup/plugin-json": "4.1.0", @@ -46,6 +49,8 @@ "date-fns": "2.30.0", "emojilib": "github:thatonecalculator/emojilib", "escape-regexp": "0.0.1", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-file-progress": "^1.3.0", "eventemitter3": "5.0.1", "fast-blurhash": "^1.1.2", "focus-trap": "^7.5.2", @@ -57,6 +62,7 @@ "katex": "0.16.8", "matter-js": "0.18.0", "mfm-js": "0.23.3", + "paralint": "^1.2.1", "photoswipe": "5.3.8", "prettier": "3.0.0", "prettier-plugin-vue": "1.1.6", diff --git a/packages/client/src/components/MkAbuseReport.vue b/packages/client/src/components/MkAbuseReport.vue index ccb85d7222..f92d10ba77 100644 --- a/packages/client/src/components/MkAbuseReport.vue +++ b/packages/client/src/components/MkAbuseReport.vue @@ -80,11 +80,11 @@ const emit = defineEmits<{ (ev: "resolved", reportId: string): void; }>(); -let forward = $ref(props.report.forwarded); +const forward = $ref(props.report.forwarded); function resolve() { os.apiWithDialog("admin/resolve-abuse-user-report", { - forward: forward, + forward, reportId: props.report.id, }).then(() => { emit("resolved", props.report.id); diff --git a/packages/client/src/components/MkAbuseReportWindow.vue b/packages/client/src/components/MkAbuseReportWindow.vue index 6fdf3b9e0b..fc80cd66f5 100644 --- a/packages/client/src/components/MkAbuseReportWindow.vue +++ b/packages/client/src/components/MkAbuseReportWindow.vue @@ -41,7 +41,7 @@ diff --git a/packages/client/src/components/MkButton.vue b/packages/client/src/components/MkButton.vue index ab02819086..3a6b8f321a 100644 --- a/packages/client/src/components/MkButton.vue +++ b/packages/client/src/components/MkButton.vue @@ -49,8 +49,8 @@ const emit = defineEmits<{ (ev: "click", payload: MouseEvent): void; }>(); -let el = $ref(null); -let ripples = $ref(null); +const el = $ref(null); +const ripples = $ref(null); onMounted(() => { if (props.autofocus) { diff --git a/packages/client/src/components/MkCaptcha.vue b/packages/client/src/components/MkCaptcha.vue index 71d8c29964..146c512fb8 100644 --- a/packages/client/src/components/MkCaptcha.vue +++ b/packages/client/src/components/MkCaptcha.vue @@ -6,11 +6,11 @@ + + +