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
Verifymail.io API Auth Key
+
+ Use TrueMail API
+
+
+
+ TrueMail API Instance
+
+
+
+ TrueMail API Auth Key
+
@@ -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
-
+
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index 0d978e4107..829c706e0e 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only