diff --git a/CHANGELOG.md b/CHANGELOG.md index c20718032c..5714d2025f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,19 @@ --> +## 202x.x.x (Unreleased) + +### Client +- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように + +## 2023.12.2 + +### General +- v2023.12.1でDockerを利用してサーバーを起動できない問題を修正 + +### Client +- Enhance: 検索画面においてEnterキー押下で検索できるように + ## 2023.12.1 ### Note diff --git a/COPYING b/COPYING index c218443d42..905d3e1236 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Unless otherwise stated this repository is -Copyright © 2014-2023 syuilo and contributers +Copyright © 2014-2023 syuilo and contributors And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. diff --git a/Dockerfile b/Dockerfile index 38aa5bc7b3..922ce4dca3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,6 +51,7 @@ WORKDIR /misskey COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["scripts", "./scripts"] COPY --link ["packages/backend/package.json", "./packages/backend/"] +COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ pnpm i --frozen-lockfile --aggregate-output @@ -77,7 +78,9 @@ WORKDIR /misskey COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules +COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built +COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/built ./packages/misskey-js/built COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis COPY --chown=misskey:misskey . ./ diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index c659e13250..77ba3f0306 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -2,6 +2,7 @@ _lang_: "বাংলা" headlineMisskey: "নোট ব্যাবহার করে সংযুক্ত নেটওয়ার্ক" introMisskey: "স্বাগতম! মিসকি একটি ওপেন সোর্স, ডিসেন্ট্রালাইজড মাইক্রোব্লগিং পরিষেবা। \n\"নোট\" তৈরির মাধ্যমে যা ঘটছে তা সবার সাথে শেয়ার করুন 📡\n\"রিঅ্যাকশন\" গুলির মাধ্যমে যেকোনো নোট সম্পর্কে আপনার অনুভূতি ব্যাক্ত করতে পারেন 👍\nএকটি নতুন দুনিয়া ঘুরে দেখুন 🚀\n" +poweredByMisskeyDescription: "{name} হল ওপেন সোর্স প্ল্যাটফর্ম Misskey-এর সার্ভারগুলির একটি৷" monthAndDay: "{day}/{month}" search: "খুঁজুন" notifications: "বিজ্ঞপ্তি" @@ -12,12 +13,14 @@ fetchingAsApObject: "ফেডিভার্স থেকে খবর আন ok: "ঠিক" gotIt: "বুঝেছি" cancel: "বাতিল" +noThankYou: "না, ধন্যবাদ" enterUsername: "ইউজারনেম লিখুন" renotedBy: "{user} রিনোট করেছেন" noNotes: "কোন নোট নেই" noNotifications: "কোনো বিজ্ঞপ্তি নেই" instance: "ইন্সট্যান্স" settings: "সেটিংস" +notificationSettings: "বিজ্ঞপ্তির সেটিংস" basicSettings: "সাধারণ সেটিংস" otherSettings: "অন্যান্য সেটিংস" openInWindow: "নতুন উইন্ডোতে খুলা" @@ -42,12 +45,20 @@ pin: "পিন করা" unpin: "পিন সরান" copyContent: "বিষয়বস্তু কপি করুন" copyLink: "লিঙ্ক কপি করুন" +copyLinkRenote: "রিনোট লিঙ্ক কপি করুন" delete: "মুছুন" deleteAndEdit: "মুছুন এবং সম্পাদনা করুন" deleteAndEditConfirm: "আপনি কি এই নোটটি মুছে এটি সম্পাদনা করার বিষয়ে নিশ্চিত? আপনি এটির সমস্ত রিঅ্যাকশন, রিনোট এবং জবাব হারাবেন।" addToList: "লিস্ট এ যোগ করুন" +addToAntenna: "অ্যান্টেনা এ যোগ করুন" sendMessage: "একটি বার্তা পাঠান" +copyRSS: "RSS কপি করুন" copyUsername: "ব্যবহারকারীর নাম কপি করুন" +copyUserId: "ব্যবহারকারীর ID কপি করুন" +copyNoteId: "নোটের ID কপি করুন" +copyFileId: "ফাইল ID কপি করুন" +copyFolderId: "ফোল্ডার ID কপি করুন" +copyProfileUrl: "প্রোফাইল URL কপি করুন" searchUser: "ব্যবহারকারী খুঁজুন..." reply: "জবাব" loadMore: "আরও দেখুন" @@ -100,6 +111,8 @@ renoted: "রিনোট করা হয়েছে" cantRenote: "এই নোটটি রিনোট করা যাবে না।" cantReRenote: "রিনোটকে রিনোট করা যাবে না।" quote: "উদ্ধৃতি" +inChannelRenote: "চ্যানেলে রিনোট" +inChannelQuote: "চ্যানেলে উদ্ধৃতি" pinnedNote: "পিন করা নোট" pinned: "পিন করা" you: "আপনি" @@ -108,6 +121,10 @@ sensitive: "সংবেদনশীল বিষয়বস্তু" add: "যুক্ত করুন" reaction: "প্রতিক্রিয়া" reactions: "প্রতিক্রিয়া" +emojiPicker: "ইমোজি পিকার" +pinnedEmojisForReactionSettingDescription: "রিঅ্যাকশন দেয়ার সময় আপনি ইমোজিটিকে পিন করা এবং প্রদর্শিত হওয়ার জন্য সেট করতে পারেন।" +pinnedEmojisSettingDescription: "ইমোজি ইনপুট দেয়ার সময় আপনি ইমোজিটিকে পিন করা এবং প্রদর্শিত হওয়ার জন্য সেট করতে পারেন।" +emojiPickerDisplay: "পিকার ডিসপ্লে" reactionSettingDescription2: "পুনরায় সাজাতে টেনে আনুন, মুছতে ক্লিক করুন, যোগ করতে + টিপুন।" rememberNoteVisibility: "নোটের দৃশ্যমান্যতার সেটিংস মনে রাখুন" attachCancel: "অ্যাটাচমেন্ট সরান " @@ -1034,6 +1051,7 @@ _2fa: step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।" step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।" securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷" + renewTOTPCancel: "না, ধন্যবাদ" _permissions: "read:account": "অ্যাকাউন্টের তথ্য দেখুন" "write:account": "অ্যাকাউন্টের তথ্য সম্পাদন করুন" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index dc5600151a..156af44d89 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -121,6 +121,10 @@ sensitive: "Konten sensitif" add: "Tambahkan" reaction: "Reaksi" reactions: "Reaksi" +emojiPicker: "Emoji Picker" +pinnedEmojisForReactionSettingDescription: "Atur sematan emoji pada reaksi" +pinnedEmojisSettingDescription: "Atur sematan emoji pada masukan emoji" +emojiPickerDisplay: "Tampilan Emoji Picker" reactionSettingDescription2: "Geser untuk memindah urutan emoji, klik untuk menghapus, tekan \"+\" untuk menambahkan" rememberNoteVisibility: "Ingat pengaturan visibilitas catatan" attachCancel: "Hapus lampiran" @@ -641,6 +645,7 @@ smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP" smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS" testEmail: "Tes pengiriman surel" wordMute: "Bisukan kata" +hardWordMute: "Pembisuan kata keras" regexpError: "Kesalahan ekspresi reguler" regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:" instanceMute: "Bisukan instansi" @@ -1154,6 +1159,7 @@ tosAndPrivacyPolicy: "Syarat dan Ketentuan serta Kebijakan Privasi" avatarDecorations: "Dekorasi avatar" attach: "Lampirkan" detach: "Hapus" +detachAll: "Lepas Semua" angle: "Sudut" flip: "Balik" showAvatarDecorations: "Tampilkan dekorasi avatar" @@ -1168,6 +1174,7 @@ doReaction: "Tambahkan reaksi" code: "Kode" reloadRequiredToApplySettings: "Muat ulang diperlukan untuk menerapkan pengaturan." remainingN: "Sisa : {n}" +decorate: "Dekor" _announcement: forExistingUsers: "Hanya pengguna yang telah ada" forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya." @@ -1215,6 +1222,7 @@ _initialTutorial: followers: "Perlihatkan ke pengikut saja. Hanya pengikut yang dapat melihat postinganmu dan tidak dapat direnote oleh siapapun." direct: "Hanya perlihatkan ke pengguna spesifik dan penerima akan diberi tahu. Dapat juga digunakan sebagai alternatif dari pesan langsung." _cw: + title: "Peringatan Konten (CW)" _exampleNote: cw: "Peringatan: Bikin Lapar!" note: "Baru aja makan donat berlapis coklat 🍩😋" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 37bdf1e577..566667ba79 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -260,6 +260,7 @@ removed: "뭉캣십니다" removeAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?" deleteAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?" resetAreYouSure: "아시로 데돌립니꺼?" +areYouSure: "갠찮십니꺼?" saved: "저장햇십니다" messaging: "대화" upload: "올리기" @@ -458,6 +459,7 @@ noMessagesYet: "아직 대화가 없십니다" newMessageExists: "새 메시지가 있십니다" onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니다" invitations: "초대하기" +invitationCode: "초대장" checking: "학인하고 잇십니다" passwordMatched: "맞십니다" passwordNotMatched: "안 맞십니다" @@ -564,6 +566,11 @@ removeAllFollowing: "팔로잉 말캉 무루기" removeAllFollowingDescription: "{host} 서버랑 걸어놓은 모든 팔로잉을 무룹니다. 고 서버가 아예 없어지삐맀든가, 그런 경우에 하이소." userSuspended: "요 게정은... 얼어 있십니다." userSilenced: "요 게정은... 수ᇚ혀 있십니다." +relays: "릴레이" +addRelay: "릴레이 옇기" +addedRelays: "옇은 릴레이" +enableInfiniteScroll: "알아서 더 보기" +author: "맨던 사람" manage: "간리" emailServer: "전자우펜 서버" email: "전자우펜" @@ -572,6 +579,8 @@ smtpHost: "호스트 이럼" smtpPort: "포트" smtpUser: "사용자 이럼" smtpPass: "비밀번호" +display: "보기" +create: "맨걸기" abuseReports: "신고하기" reportAbuse: "신고하기" reportAbuseRenote: "리노트 신고하기" @@ -583,6 +592,7 @@ forwardReport: "웬겍 서버에 신고 보내기" random: "무작이" system: "시스템" clip: "클립 맨걸기" +createNew: "새로 맨걸기" notesCount: "노트 수" renotesCount: "리노트한 수" renotedCount: "리노트덴 수" @@ -608,6 +618,7 @@ tools: "도구" like: "좋네예!" unlike: "좋네예 무루기" numberOfLikes: "좋네예 수" +show: "보기" roles: "옉할" role: "옉할" noRole: "옉할이 없십니다" @@ -637,6 +648,8 @@ _gallery: _email: _follow: title: "새 팔로워가 잇십니다" +_serverDisconnectedBehavior: + reload: "알아서 새로곤침" _channel: removeBanner: "배너 뭉캐기" _theme: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 63d0812e93..4a13012eed 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -425,9 +425,9 @@ setupOf2fa: "2단계 인증 설정" totp: "인증 앱" totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력" moderator: "모더레이터" -moderation: "모더레이션" -moderationNote: "모더레이션 노트" -addModerationNote: "모더레이션 노트 추가하기" +moderation: "조정" +moderationNote: "조정 기록" +addModerationNote: "조정 기록 추가하기" moderationLogs: "모더레이션 로그" nUsersMentioned: "{n}명이 언급함" securityKeyAndPasskey: "보안 키 또는 패스 키" @@ -513,7 +513,7 @@ dayOverDayChanges: "어제보다" appearance: "모양" clientSettings: "클라이언트 설정" accountSettings: "계정 설정" -promotion: "프로모션" +promotion: "홍보" promote: "프로모션하기" numberOfDays: "며칠동안" hideThisNote: "이 노트를 숨기기" @@ -863,8 +863,8 @@ devMode: "개발자 모드" keepCw: "CW 유지하기" pubSub: "Pub/Sub 계정" lastCommunication: "마지막 통신" -resolved: "해결됨" -unresolved: "해결되지 않음" +resolved: "처리함" +unresolved: "처리되지 않음" breakFollow: "팔로워 해제" breakFollowConfirm: "팔로우를 해제하시겠습니까?" itsOn: "켜져 있습니다" @@ -1181,6 +1181,8 @@ remainingN: "나머지: {n}" overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?" seasonalScreenEffect: "계절에 따른 효과 보이기" decorate: "장식하기" +addMfmFunction: "장식 추가하기" +enableQuickAddMfmFunction: "상급자용 MFM 선택기 표시하기" _announcement: forExistingUsers: "기존 유저에게만 알림" forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다." @@ -1557,7 +1559,7 @@ _role: name: "역할 이름" description: "역할 설명" permission: "역할 권한" - descriptionOfPermission: "모더레이터는 기본적인 중재와 관련된 작업을 수행할 수 있습니다.\n관리자는 서버의 모든 설정을 변경할 수 있습니다." + descriptionOfPermission: "조정자는 기본적인 조정 작업을 진행할 수 있습니다.\n관리자는 서버의 모든 설정을 변경할 수 있습니다." assignTarget: "할당 대상" descriptionOfAssignTarget: "수동을 선택하면 누가 이 역할에 포함되는지를 수동으로 관리할 수 있습니다.\n조건부를 선택하면 조건을 설정해 일치하는 사용자를 자동으로 포함되게 할 수 있습니다." manual: "수동" @@ -1628,7 +1630,7 @@ _role: or: "다음을 하나라도 만족" not: "다음을 만족하지 않음" _sensitiveMediaDetection: - description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다." + description: "기계 학습으로 민감한 미디어를 알아서 찾아내어 조정에 참고하도록 합니다. 서버가 부하를 다소 받습니다." sensitivity: "탐지 민감도" sensitivityDescription: "민감도가 낮을수록 안전한 미디어가 잘못 탐지될 확률이 줄어들며, 높을수록 민감한 미디어가 탐지되지 않을 확률이 줄어듭니다." setSensitiveFlagAutomatically: "자동으로 NSFW로 설정하기" @@ -1933,6 +1935,55 @@ _permissions: "write:flash": "Play를 조작합니다" "read:flash-likes": "Play의 좋아요를 봅니다" "write:flash-likes": "Play의 좋아요를 조작합니다" + "read:admin:abuse-user-reports": "사용자 신고 보기" + "write:admin:delete-account": "사용자 계정 삭제하기" + "write:admin:delete-all-files-of-a-user": "모든 사용자 파일 삭제하기" + "read:admin:index-stats": "데이터베이스 색인 정보 보기" + "read:admin:table-stats": "데이터베이스 테이블 정보 보기" + "read:admin:user-ips": "사용자 IP 주소 보기" + "read:admin:meta": "인스턴스 메타데이터 보기" + "write:admin:reset-password": "사용자 비밀번호 재설정하기" + "write:admin:resolve-abuse-user-report": "사용자 신고 처리하기" + "write:admin:send-email": "이메일 보내기" + "read:admin:server-info": "서버 정보 보기" + "read:admin:show-moderation-log": "조정 기록 보기" + "read:admin:show-user": "사용자 개인정보 보기" + "read:admin:show-users": "사용자 개인정보 보기" + "write:admin:suspend-user": "사용자 정지하기" + "write:admin:unset-user-avatar": "사용자 아바타 삭제하기" + "write:admin:unset-user-banner": "사용자 배너 삭제하기" + "write:admin:unsuspend-user": "사용자 정지 해제하기" + "write:admin:meta": "인스턴스 메타데이터 수정하기" + "write:admin:user-note": "조정 기록 수정하기" + "write:admin:roles": "역할 수정하기" + "read:admin:roles": "역할 보기" + "write:admin:relays": "릴레이 수정하기" + "read:admin:relays": "릴레이 보기" + "write:admin:invite-codes": "초대 코드 수정하기" + "read:admin:invite-codes": "초대 코드 보기" + "write:admin:announcements": "공지사항 수정하기" + "read:admin:announcements": "공지사항 보기" + "write:admin:avatar-decorations": "아바타 꾸미기 수정하기" + "read:admin:avatar-decorations": "아바타 꾸미기 보기" + "write:admin:federation": "연합 정보 수정하기" + "write:admin:account": "사용자 계정 수정하기" + "read:admin:account": "사용자 정보 보기" + "write:admin:emoji": "이모지 수정하기" + "read:admin:emoji": "이모지 보기" + "write:admin:queue": "작업 대기열 수정하기" + "read:admin:queue": "작업 대기열 정보 보기" + "write:admin:promo": "홍보 기록 수정하기" + "write:admin:drive": "사용자 드라이브 수정하기" + "read:admin:drive": "사용자 드라이브 정보 보기" + "read:admin:stream": "관리자용 Websocket API 사용하기" + "write:admin:ad": "광고 수정하기" + "read:admin:ad": "광고 보기" + "write:invite-codes": "초대 코드 만들기" + "read:invite-codes": "초대 코드 불러오기" + "write:clip-favorite": "클립의 좋아요 수정하기" + "read:clip-favorite": "클립의 좋아요 보기" + "read:federation": "연합 정보 불러오기" + "write:report-abuse": "위반 내용 신고하기" _auth: shareAccessTitle: "어플리케이션의 접근 허가" shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?" @@ -2267,21 +2318,21 @@ _moderationLogTypes: updateCustomEmoji: "커스텀 이모지 수정" deleteCustomEmoji: "커스텀 이모지 삭제" updateServerSettings: "서버 설정 갱신" - updateUserNote: "모더레이션 노트 갱신" + updateUserNote: "조정 기록 갱신" deleteDriveFile: "파일 삭제" deleteNote: "노트 삭제" - createGlobalAnnouncement: "전역 공지사항 생성" - createUserAnnouncement: "유저 공지사항 생성" - updateGlobalAnnouncement: "전역 공지사항 수정" - updateUserAnnouncement: "유저 공지사항 수정" - deleteGlobalAnnouncement: "전역 공지사항 삭제" - deleteUserAnnouncement: "유저 공지사항 삭제" + createGlobalAnnouncement: "모든 공지사항 만들기" + createUserAnnouncement: "사용자 공지사항 만들기" + updateGlobalAnnouncement: "모든 공지사항 수정" + updateUserAnnouncement: "사용자 공지사항 수정" + deleteGlobalAnnouncement: "모든 공지사항 삭제" + deleteUserAnnouncement: "사용자 공지사항 삭제" resetPassword: "비밀번호 재설정" suspendRemoteInstance: "리모트 서버를 정지" unsuspendRemoteInstance: "리모트 서버의 정지를 해제" markSensitiveDriveFile: "파일에 열람주의를 설정" unmarkSensitiveDriveFile: "파일에 열람주의를 해제" - resolveAbuseReport: "신고 해결" + resolveAbuseReport: "신고 처리" createInvitation: "초대 코드 생성" createAd: "광고 생성" deleteAd: "광고 삭제" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 782f871b1e..36b6e77e9b 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1181,6 +1181,8 @@ remainingN: "剩餘:{n}" overwriteContentConfirm: "確定要覆蓋目前的內容嗎?" seasonalScreenEffect: "隨季節變換畫面的呈現" decorate: "設置頭像裝飾" +addMfmFunction: "插入MFM功能語法" +enableQuickAddMfmFunction: "顯示高級MFM選擇器" _announcement: forExistingUsers: "僅限既有的使用者" forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。" diff --git a/package.json b/package.json index c15e780a31..c2046080c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.12.1-PrisMisskey.1", + "version": "2023.12.2-PrisMisskey.1", "codename": "nasubi", "repository": { "type": "git", @@ -18,7 +18,7 @@ "build-assets": "node ./scripts/build-assets.mjs", "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", - "build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build", + "build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", "init": "pnpm migrate", diff --git a/packages/backend/migration/1703658526000-supportTrueMailApi.js b/packages/backend/migration/1703658526000-supportTrueMailApi.js new file mode 100644 index 0000000000..0054d54122 --- /dev/null +++ b/packages/backend/migration/1703658526000-supportTrueMailApi.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SupportTrueMailApi1703658526000 { + name = 'SupportTrueMailApi1703658526000' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`); + } +} diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 45c46bbb0e..89746a3434 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -156,7 +156,7 @@ export class EmailService { @bindThis public async validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; - reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned'; + reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; }> { const meta = await this.metaService.fetch(); @@ -173,6 +173,8 @@ export class EmailService { if (meta.enableActiveEmailValidation) { if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); + } else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) { + validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey); } else { validated = await validateEmail({ email: emailAddress, @@ -212,6 +214,8 @@ export class EmailService { validated.reason === 'disposable' ? 'disposable' : validated.reason === 'mx' ? 'mx' : validated.reason === 'smtp' ? 'smtp' : + validated.reason === 'network' ? 'network' : + validated.reason === 'blacklist' ? 'blacklist' : null, }; } @@ -276,4 +280,67 @@ export class EmailService { reason: null, }; } + + private async trueMail(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{ + valid: boolean; + reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null; + }> { + const endpoint = truemailInstance + '?email=' + emailAddress; + try { + const res = await this.httpRequestService.send(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: truemailAuthKey + }, + }); + + const json = (await res.json()) as { + email: string; + success: boolean; + errors?: { + list_match?: string; + regex?: string; + mx?: string; + smtp?: string; + } | null; + }; + + if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) { + return { + valid: false, + reason: 'format', + }; + } + if (json.errors?.smtp) { + return { + valid: false, + reason: 'smtp', + }; + } + if (json.errors?.mx) { + return { + valid: false, + reason: 'mx', + }; + } + if (!json.success) { + return { + valid: false, + reason: json.errors?.list_match as T || 'blacklist', + }; + } + + return { + valid: true, + reason: null, + }; + } catch (error) { + return { + valid: false, + reason: 'network', + }; + } + } } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 0ed08d3c24..06d2f73891 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -472,6 +472,23 @@ export class MiMeta { }) public verifymailAuthKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTruemailApi: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public truemailInstance: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public truemailAuthKey: string | null; + @Column('boolean', { default: true, }) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 0503a5b697..26626c54b5 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -284,6 +284,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTruemailApi: { + type: 'boolean', + optional: false, nullable: false, + }, + truemailInstance: { + type: 'string', + optional: false, nullable: true, + }, + truemailAuthKey: { + type: 'string', + optional: false, nullable: true, + }, enableChartsForRemoteUser: { type: 'boolean', optional: false, nullable: false, @@ -525,6 +537,9 @@ export default class extends Endpoint { // eslint- enableActiveEmailValidation: instance.enableActiveEmailValidation, enableVerifymailApi: instance.enableVerifymailApi, verifymailAuthKey: instance.verifymailAuthKey, + enableTruemailApi: instance.enableTruemailApi, + truemailInstance: instance.truemailInstance, + truemailAuthKey: instance.truemailAuthKey, enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, enableServerMachineStats: instance.enableServerMachineStats, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 71d0a13bdb..633bd0ba52 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -118,6 +118,9 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableVerifymailApi: { type: 'boolean' }, verifymailAuthKey: { type: 'string', nullable: true }, + enableTruemailApi: { type: 'boolean' }, + truemailInstance: { type: 'string', nullable: true }, + truemailAuthKey: { type: 'string', nullable: true }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' }, @@ -484,6 +487,26 @@ export default class extends Endpoint { // eslint- set.verifymailAuthKey = ps.verifymailAuthKey; } } + + if (ps.enableTruemailApi !== undefined) { + set.enableTruemailApi = ps.enableTruemailApi; + } + + if (ps.truemailInstance !== undefined) { + if (ps.truemailInstance === '') { + set.truemailInstance = null; + } else { + set.truemailInstance = ps.truemailInstance; + } + } + + if (ps.truemailAuthKey !== undefined) { + if (ps.truemailAuthKey === '') { + set.truemailAuthKey = null; + } else { + set.truemailAuthKey = ps.truemailAuthKey; + } + } if (ps.enableChartsForRemoteUser !== undefined) { set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser; diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index a140722ab3..67de693b20 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -773,10 +773,20 @@ async function post(ev?: MouseEvent) { noteId: props.updateMode ? props.initialNote?.id : undefined, }; - if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { - const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_; - } + if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { + const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); + if (!postData.text) { + postData.text = hashtags_; + } else { + const postTextLines = postData.text.split('\n'); + if (postTextLines[postTextLines.length - 1].trim() === '') { + postTextLines[postTextLines.length - 1] += hashtags_; + } else { + postTextLines[postTextLines.length - 1] += ' ' + hashtags_; + } + postData.text = postTextLines.join('\n'); + } + } // plugin if (notePostInterruptors.length > 0) { diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 7070157ca9..8d79dea20f 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -80,6 +80,17 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + + + @@ -153,6 +164,9 @@ const enableIpLogging = ref(false); const enableActiveEmailValidation = ref(false); const enableVerifymailApi = ref(false); const verifymailAuthKey = ref(null); +const enableTruemailApi = ref(false); +const truemailInstance = ref(null); +const truemailAuthKey = ref(null); const bannedEmailDomains = ref(''); async function init() { @@ -194,6 +208,9 @@ function save() { enableActiveEmailValidation: enableActiveEmailValidation.value, enableVerifymailApi: enableVerifymailApi.value, verifymailAuthKey: verifymailAuthKey.value, + enableTruemailApi: enableTruemailApi.value, + truemailInstance: truemailInstance.value, + truemailAuthKey: truemailAuthKey.value, bannedEmailDomains: bannedEmailDomains.value.split('\n'), }).then(() => { fetchInstance(); diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 421895ea6c..af09189654 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + {{ i18n.ts.search }} diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index e58c89bb77..b7cc5cd36e 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue index e8918c8669..5c0b54e2d9 100644 --- a/packages/frontend/src/pages/search.note.vue +++ b/packages/frontend/src/pages/search.note.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only