diff --git a/.gitignore b/.gitignore index 6bf2ea1b1..ce9e4f8a1 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 bb28e5bbf..e561c3a5a 100644 --- a/CALCKEY.md +++ b/CALCKEY.md @@ -16,7 +16,6 @@ ## Work in progress -- Link verification - Better Messaging UI - Better API Documentation - Remote follow button @@ -118,6 +117,7 @@ - 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 7e97a99eb..59dda2bcf 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 e3fbf8cb9..2f37a02a2 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 8fb57e879..b8263bf6e 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 9e3a07654..c2c89f15c 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 76bc5c24a..eeafa6ffd 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 faf9ba282..f5c002505 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 f81ef4520..660be0347 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 17bebe99c..6aaa726db 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 bdf7cab54..93dd5fcf3 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 1043e3524..755afa8d3 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 2c8e548bd..459274166 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 571f6af95..58624303c 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 bfef68d10..643a84eef 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 dce23d755..046e67aec 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 a3021221d..f73d693a8 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 78aaf2bbd..6bd72b75b 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 ddd79084f..40b3909ce 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 36453551d..a0353e0ec 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 d1bad4ad1..8b59b63a7 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/packages/backend/assets/LICENSE b/packages/backend/assets/LICENSE new file mode 100644 index 000000000..342509dec --- /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/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index 119eecdc7..002247d3a 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 d7580a4f6..93aed7cb8 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 53321de5f..697d24d06 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 000000000..3ddda9baf --- /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 0637251a6..6d3bde2b8 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/services/fetch-rel-me.ts b/packages/backend/src/services/fetch-rel-me.ts new file mode 100644 index 000000000..7b450c229 --- /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 5a581a54c..346830e0f 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/src/components/MkAutocomplete.vue b/packages/client/src/components/MkAutocomplete.vue index 3e234ddbc..5fcee6e5a 100644 --- a/packages/client/src/components/MkAutocomplete.vue +++ b/packages/client/src/components/MkAutocomplete.vue @@ -436,10 +436,7 @@ onMounted(() => { setPosition(); props.textarea.addEventListener("keydown", onKeydown); - - for (const el of Array.from(document.querySelectorAll("body *"))) { - el.addEventListener("mousedown", onMousedown); - } + document.body.addEventListener("mousedown", onMousedown); nextTick(() => { exec(); @@ -457,10 +454,7 @@ onMounted(() => { onBeforeUnmount(() => { props.textarea.removeEventListener("keydown", onKeydown); - - for (const el of Array.from(document.querySelectorAll("body *"))) { - el.removeEventListener("mousedown", onMousedown); - } + document.body.removeEventListener("mousedown", onMousedown); }); diff --git a/packages/client/src/components/MkContextMenu.vue b/packages/client/src/components/MkContextMenu.vue index a21547780..33506b96f 100644 --- a/packages/client/src/components/MkContextMenu.vue +++ b/packages/client/src/components/MkContextMenu.vue @@ -57,15 +57,11 @@ onMounted(() => { rootEl.style.top = `${top}px`; rootEl.style.left = `${left}px`; - for (const el of Array.from(document.querySelectorAll("body *"))) { - el.addEventListener("mousedown", onMousedown); - } + document.body.addEventListener("mousedown", onMousedown); }); onBeforeUnmount(() => { - for (const el of Array.from(document.querySelectorAll("body *"))) { - el.removeEventListener("mousedown", onMousedown); - } + document.body.removeEventListener("mousedown", onMousedown); }); function onMousedown(evt: Event) { diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 9b5a079f9..04051d8a1 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -126,7 +126,11 @@ @@ -173,6 +177,7 @@ import { i18n } from "@/i18n"; import { $i } from "@/account"; import { langmap } from "@/scripts/langmap"; import { definePageMetadata } from "@/scripts/page-metadata"; +import { host } from "@/config"; const profile = reactive({ name: $i?.name, diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue index 9d643c0b6..9192d1691 100644 --- a/packages/client/src/pages/user/home.vue +++ b/packages/client/src/pages/user/home.vue @@ -288,10 +288,17 @@
+ { margin-bottom: 8px; } + &.verified { + background-color: var(--hover); + border-radius: 10px; + color: var(--badge) !important; + } + > .name { width: 30%; overflow: hidden; diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 6fdcfb28d..9037ad156 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -384,6 +384,27 @@ async function deleteProfile() { } + + +