Merge branch 'develop' into enh-15290
This commit is contained in:
commit
3afd2c5a75
|
@ -22,7 +22,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Checkout head
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
|
||||
- name: setup node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- uses: actions/setup-node@v4.2.0
|
||||
- uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
@ -69,13 +69,13 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- uses: actions/setup-node@v4.2.0
|
||||
- uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- name: Restore eslint cache
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ${{ env.eslint-cache-path }}
|
||||
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
@ -99,7 +99,7 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- uses: actions/setup-node@v4.2.0
|
||||
- uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- uses: actions/setup-node@v4.2.0
|
||||
- uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -5,13 +5,15 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- develop
|
||||
- dev/storybook8 # for testing
|
||||
pull_request_target:
|
||||
branches-ignore:
|
||||
# Since pull requests targets master mostly is the "develop" branch.
|
||||
# Storybook CI is checked on the "push" event of "develop" branch so it would cause a duplicate build.
|
||||
# This is a waste of chromatic build quota, so we don't run storybook CI on pull requests targets master.
|
||||
- master
|
||||
# Neither Dependabot nor Renovate will change the actual behavior for components.
|
||||
- dependabot/**
|
||||
- renovate/**
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -43,7 +45,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -62,7 +62,7 @@ jobs:
|
|||
fi
|
||||
done
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
@ -109,7 +109,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
fi
|
||||
done
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
@ -93,7 +93,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.2.0
|
||||
uses: actions/setup-node@v4.3.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
|
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -1,17 +1,48 @@
|
|||
## 2025.3.2
|
||||
## 2025.4.0
|
||||
|
||||
### General
|
||||
- セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
||||
- Feat: チャットがリニューアルして復活しました(beta)
|
||||
- 既存のDM機能よりも便利で効率的な実装になっています
|
||||
- チャットを受け付ける相手を制限可能です
|
||||
- 誰でも / フォローユーザーのみ / フォロワーのみ / 相互のみ / 受け付けない から選択できます
|
||||
- 自分からメッセージを送った相手とは上記の設定に関わらずチャット可能です
|
||||
- チャット機能を開放するかどうかをロールで制御可能です
|
||||
- ルームを作成して、複数人でのチャットも可能です
|
||||
- 過去自分が送ったメッセージ・自分に送られたメッセージの検索が可能です
|
||||
- 参加中のルームをミュートして通知が来ないように設定可能です
|
||||
- メッセージにはリアクションも可能です
|
||||
- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
||||
- Misskeyネイティブでダッシュボードを実装予定です
|
||||
- Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように
|
||||
|
||||
### Client
|
||||
- Feat: 設定の管理が強化されました
|
||||
- 自動でバックアップされるように
|
||||
- 内部処理が一新され、安定性とパフォーマンスが向上しました
|
||||
- 全てのクライアント設定がエクスポート(バックアップ)/インポート対象に含まれるようになりました
|
||||
- プラグイン、テーマ、クライアントに追加されたすべてのアカウント情報も含まれるようになりました
|
||||
- 自動で設定データをサーバーにバックアップできるように
|
||||
- 設定→設定のプロファイル→自動バックアップ で有効にできます
|
||||
- 新しいデバイスからログインしたり、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
|
||||
- 任意の設定項目をデバイス間で同期できるように
|
||||
- 設定項目の「...」メニュー→「デバイス間で同期」
|
||||
- 同期をオンにした際にサーバーに保存された値とローカルの値が競合する場合はどちらを優先するか選択できます
|
||||
- 任意の設定項目を初期値にリセットできるように
|
||||
- 設定項目の「...」メニュー→「初期値にリセット」
|
||||
- アカウントごとに設定値が分離される設定とそうでないクライアント設定が混在していた(かつ分離するかどうかを設定不可だった)のを、基本的に一律でクライアント全体に適用されるようにし、個別でアカウントごとに異なる設定を行えるように
|
||||
- 設定項目の「...」メニュー→「アカウントで上書き」をオンにすることで、設定値をそのアカウントでだけ適用するようにできます
|
||||
- ログアウトすると設定データもブラウザから消去されるようになりプライバシーが向上しました
|
||||
- 再度ログインすればサーバーのバックアップから設定データを復元可能です
|
||||
- エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました
|
||||
- 設定情報の移行は自動で行われますが、何らかの理由で失敗した場合、設定→その他→旧設定情報を移行 で再試行可能です
|
||||
- Feat: 画面を重ねて表示するオプションを実装(実験的)
|
||||
- 設定 → その他 → 実験的機能 → Enable stacking router view
|
||||
- Enhance: プラグインの管理が強化されました
|
||||
- インストール/アンインストール/設定の変更時にリロード不要になりました
|
||||
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
|
||||
- Enhance: デッキUIでカラム間のマージンを設定できるように
|
||||
- Enhance: デッキUIでデッキメニューの位置を設定できるように
|
||||
- Enhance: デッキUIでナビゲーションバーの位置を設定できるように
|
||||
- Enhance: アイコンのスクロール追従を無効化してパフォーマンス向上できるように
|
||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
||||
- Enhance: テーマ設定画面のデザインを改善
|
||||
|
@ -19,11 +50,17 @@
|
|||
- 投稿フォームをリセットできるように
|
||||
- 文字数カウントを復活
|
||||
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
|
||||
- Enhance: 全体的なブラッシュアップ
|
||||
- Enhance 全体的なパフォーマンス向上
|
||||
- Enhance: 投稿フォームの絵文字ピッカーに独立したウィンドウを使用できるように
|
||||
- リアクションピッカーと絵文字ピッカーで表示スタイルの設定が分離しました。絵文字ピッカーでのみウィンドウスタイルを使用可能です。
|
||||
- Fix: 読み込み直後にスクロールしようとすると途中で止まる場合があるのを修正
|
||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||
- NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました
|
||||
- デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます
|
||||
|
||||
### Server
|
||||
- Enhance 全体的なパフォーマンス向上
|
||||
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
|
||||
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
|
||||
- Fix: 連合無しモードでも外部から照会可能だった問題を修正
|
||||
|
|
|
@ -251,7 +251,6 @@ removeAreYouSure: "متأكد من أنك تريد حذف {x}؟"
|
|||
deleteAreYouSure: "متأكد من أنك تريد حذف {x}؟"
|
||||
resetAreYouSure: "هل تريد إعادة التعيين؟"
|
||||
saved: "حُفظ"
|
||||
messaging: "المحادثة"
|
||||
upload: "ارفع"
|
||||
keepOriginalUploading: "ابق الصورة الأصلية"
|
||||
keepOriginalUploadingDescription: "يحفظ الصور المرفوعة على حالتها الأصلية، وان عطّل ستولد نسخة مخصصة من الصورة."
|
||||
|
@ -264,7 +263,6 @@ uploadFromUrlMayTakeTime: "سيستغرق بعض الوقت لاتمام الر
|
|||
explore: "استكشاف"
|
||||
messageRead: "مقروءة"
|
||||
noMoreHistory: "لا يوجد المزيد من التاريخ"
|
||||
startMessaging: "ابدأ محادثة"
|
||||
nUsersRead: "قرأه {n}"
|
||||
agreeTo: "اوافق على {0}"
|
||||
agree: "أقبل"
|
||||
|
@ -436,8 +434,6 @@ retype: "أعد الكتابة"
|
|||
noteOf: "ملاحظات {user}"
|
||||
quoteAttached: "اِقتُبسَ"
|
||||
quoteQuestion: "أتريد تضمينها كاقتباس"
|
||||
noMessagesYet: "ليس هناك رسائل بعد"
|
||||
newMessageExists: "لقد تلقيت رسالة جديدة"
|
||||
onlyOneFileCanBeAttached: "يمكنك إرفاق ملف واحد بالرسالة"
|
||||
signinRequired: "رجاءً لِج"
|
||||
invitations: "دعوة"
|
||||
|
@ -1014,6 +1010,12 @@ lastNDays: "آخر {n} أيام"
|
|||
surrender: "ألغِ"
|
||||
postForm: "أنشئ ملاحظة"
|
||||
information: "عن"
|
||||
_chat:
|
||||
invitations: "دعوة"
|
||||
noHistory: "السجل فارغ"
|
||||
members: "الأعضاء"
|
||||
home: "الرئيسي"
|
||||
send: "أرسل"
|
||||
_delivery:
|
||||
stop: "مُعلّق"
|
||||
_initialAccountSetting:
|
||||
|
@ -1313,6 +1315,7 @@ _permissions:
|
|||
"read:gallery": "اعرض المعرض"
|
||||
"write:gallery": "عدّل المعرض"
|
||||
"read:gallery-likes": "يعرض ما أعجبك من مشاركات المعرض"
|
||||
"write:chat": "اكتب أو احذف رسائل محادثة"
|
||||
_auth:
|
||||
shareAccess: "أتريد التفويض لـ \"{name}\" بالوصول لحسابك؟"
|
||||
shareAccessAsk: "هل تخول لهذا التطبيق الوصول لحسابك؟"
|
||||
|
|
|
@ -252,7 +252,6 @@ removeAreYouSure: "আপনি কি \"{x}\" সরানোর ব্যা
|
|||
deleteAreYouSure: "আপনি কি \"{x}\" সরানোর ব্যাপারে নিশ্চিত?"
|
||||
resetAreYouSure: "রিসেট করার ব্যাপারে নিশ্চিত?"
|
||||
saved: "সংরক্ষিত হয়েছে"
|
||||
messaging: "চ্যাট"
|
||||
upload: "আপলোড"
|
||||
keepOriginalUploading: "আসল ছবি রাখুন"
|
||||
keepOriginalUploadingDescription: "ছবিটি আপলোড করার সময় আসল সংস্করণটি রাখুন। অপশনটি বন্ধ থাকলে, আপলোডের সময় ওয়েব প্রকাশনার জন্য ছবি ব্রাউজারে তৈরি করা হবে।"
|
||||
|
@ -265,7 +264,6 @@ uploadFromUrlMayTakeTime: "URL হতে আপলোড হতে কিছু
|
|||
explore: "ঘুরে দেখুন"
|
||||
messageRead: "পড়া"
|
||||
noMoreHistory: "আর কোন ইতিহাস নেই"
|
||||
startMessaging: "চ্যাট শুরু করুন"
|
||||
nUsersRead: "{n} জন পড়েছেন"
|
||||
agreeTo: "{0} এর প্রতি আমি সম্মত"
|
||||
start: "শুরু করুন"
|
||||
|
@ -427,8 +425,6 @@ retype: "পুনঃ প্রবেশ"
|
|||
noteOf: "{user} এর নোট"
|
||||
quoteAttached: "উদ্ধৃত"
|
||||
quoteQuestion: "উদ্ধৃতি হিসাবে সংযুক্ত করবেন?"
|
||||
noMessagesYet: "কোন মেসেজ নেই"
|
||||
newMessageExists: "নতুন মেসেজ পেয়েছেন"
|
||||
onlyOneFileCanBeAttached: "আপনি মেসেজের সাথে সর্বোচ্চ একটি ফাইল যুক্ত করতে পারবেন"
|
||||
signinRequired: "দয়া করে লগ ইন করুন"
|
||||
invitations: "আমন্ত্রণ"
|
||||
|
@ -854,6 +850,12 @@ sourceCode: "সোর্স কোড"
|
|||
flip: "উল্টান"
|
||||
postForm: "নোট লিখুন"
|
||||
information: "আপনার সম্পর্কে"
|
||||
_chat:
|
||||
invitations: "আমন্ত্রণ"
|
||||
noHistory: "কোনো ইতিহাস নেই"
|
||||
members: "সদস্যবৃন্দ"
|
||||
home: "মূল পাতা"
|
||||
send: "পাঠান"
|
||||
_delivery:
|
||||
stop: "স্থগিত করা হয়েছে"
|
||||
_type:
|
||||
|
@ -1086,6 +1088,7 @@ _permissions:
|
|||
"write:gallery": "গ্যালারী সম্পাদনা করুন"
|
||||
"read:gallery-likes": "গ্যালারীর পছন্দগুলি দেখুন"
|
||||
"write:gallery-likes": "গ্যালারীর পছন্দগুলি সম্পাদনা করুন"
|
||||
"write:chat": "চ্যাটগুলি সম্পাদনা করুন"
|
||||
_auth:
|
||||
shareAccess: "\"{name}\" কে অ্যাকাউন্টের অ্যাক্সেস দিবেন?"
|
||||
shareAccessAsk: "অ্যাপ্লিকেশনটিকে অ্যাকাউন্টের অ্যাক্সেস দিবেন?"
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "Segur que vols esborrar «{x}»?"
|
|||
resetAreYouSure: "Segur que vols restablir-ho?"
|
||||
areYouSure: "Estàs segur?"
|
||||
saved: "S'ha desat"
|
||||
messaging: "Xat"
|
||||
upload: "Puja"
|
||||
keepOriginalUploading: "Guarda la imatge original"
|
||||
keepOriginalUploadingDescription: "Guarda la imatge pujada sense modificar. Si està desactivat, es generarà una versió per visualitzar a la web en pujar la imatge."
|
||||
|
@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot trigar un temps"
|
|||
explore: "Explora"
|
||||
messageRead: "Vist"
|
||||
noMoreHistory: "No hi ha res més per veure"
|
||||
startMessaging: "Comença a xatejar"
|
||||
startChat: "Comença a xatejar "
|
||||
nUsersRead: "Vist per {n}"
|
||||
agreeTo: "Accepto que {0}"
|
||||
agree: "Hi estic d'acord"
|
||||
|
@ -491,8 +490,6 @@ noteOf: "Publicació de: {user}"
|
|||
quoteAttached: "Frase adjunta"
|
||||
quoteQuestion: "Vols annexar-la com a cita?"
|
||||
attachAsFileQuestion: "El text copiat és massa llarg. Vols adjuntar-lo com un fitxer de text?"
|
||||
noMessagesYet: "Encara no hi ha missatges"
|
||||
newMessageExists: "Has rebut un nou missatge"
|
||||
onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge"
|
||||
signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar"
|
||||
signinOrContinueOnRemote: "Per continuar necessites moure el teu servidor o registrar-te / iniciar sessió en aquest servidor."
|
||||
|
@ -1131,7 +1128,7 @@ pleaseAgreeAllToContinue: "Has d'acceptar tots els camps de dalt per poder conti
|
|||
continue: "Continuar"
|
||||
preservedUsernames: "Noms d'usuaris reservats"
|
||||
preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats."
|
||||
createNoteFromTheFile: "Compon una nota des d'aquest fitxer"
|
||||
createNoteFromTheFile: "Escriu una nota incloent aquest fitxer"
|
||||
archive: "Arxiu"
|
||||
archived: "Arxivat"
|
||||
unarchive: "Desarxivar"
|
||||
|
@ -1335,6 +1332,58 @@ emojiPalette: "Calaix d'emojis"
|
|||
postForm: "Formulari de publicació"
|
||||
textCount: "Nombre de caràcters "
|
||||
information: "Informació"
|
||||
chat: "Xat"
|
||||
migrateOldSettings: "Migració de la configuració antiga "
|
||||
migrateOldSettings_description: "Normalment això es fa automàticament, però si la transició no es fa, el procés es pot iniciar manualment. S'esborrarà la configuració actual."
|
||||
compress: "Comprimir "
|
||||
right: "Dreta"
|
||||
bottom: "A baix "
|
||||
top: "A dalt "
|
||||
_chat:
|
||||
noMessagesYet: "Encara no tens missatges "
|
||||
newMessage: "Missatge nou"
|
||||
individualChat: "Xat individual "
|
||||
individualChat_description: "Pots mantenir converses individuals amb usuaris concrets."
|
||||
roomChat: "Sala de xat"
|
||||
roomChat_description: "Pots xatejar amb diverses persones.\nTambé pots xatejar amb usuaris que no poden fer xats privats, si ells accepten."
|
||||
createRoom: "Crear una sala"
|
||||
inviteUserToChat: "Invita usuaris per començar a xatejar"
|
||||
yourRooms: "Sales creades"
|
||||
joiningRooms: "Sales a les quals participes"
|
||||
invitations: "Convida"
|
||||
noInvitations: "No tens cap invitació "
|
||||
history: "Historial de converses "
|
||||
noHistory: "No hi ha un registre previ"
|
||||
noRooms: "No hi ha habitacions"
|
||||
inviteUser: "Invitar usuaris"
|
||||
sentInvitations: "Enviar invitacions"
|
||||
join: "Afegir-se "
|
||||
ignore: "Ignorar "
|
||||
leave: "Marxar"
|
||||
members: "Membres"
|
||||
searchMessages: "Buscar missatges "
|
||||
home: "Inici"
|
||||
send: "Envia"
|
||||
newline: "Línia nova "
|
||||
muteThisRoom: "Silenciar aquesta sala"
|
||||
deleteRoom: "Esborrar la sala"
|
||||
chatNotAvailableForThisAccountOrServer: "El xat no està disponible per aquest servidor o aquest compte."
|
||||
chatNotAvailableInOtherAccount: "La funció de xat es troba desactivada al compte de l'altre usuari."
|
||||
cannotChatWithTheUser: "No pots xatejar amb aquest usuari"
|
||||
cannotChatWithTheUser_description: "El xat està desactivat o l'altra part encara no l'ha obert."
|
||||
chatWithThisUser: "Xateja amb aquest usuari"
|
||||
thisUserAllowsChatOnlyFromFollowers: "Aquest usuari només accepta xats d'usuaris que el segueixen."
|
||||
thisUserAllowsChatOnlyFromFollowing: "Aquest usuari només accepta xats d'usuaris que segueix."
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "Aquest usuari només accepta xats d'usuaris que segueixes i et segueixen."
|
||||
thisUserNotAllowedChatAnyone: "Aquest usuari no accepta xats de ningú."
|
||||
chatAllowedUsers: "Usuaris que poden xatejar"
|
||||
chatAllowedUsers_note: "Pots xatejar amb qualsevol usuari a qui hagis enviat un missatge de xat, independentment d'aquesta configuració."
|
||||
_chatAllowedUsers:
|
||||
everyone: "Tothom"
|
||||
followers: "Només els teus seguidors"
|
||||
following: "Només usuaris als que segueixes"
|
||||
mutual: "Només seguidors mutus"
|
||||
none: "Ningú "
|
||||
_emojiPalette:
|
||||
palettes: "Calaixos d'emojis"
|
||||
enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
|
||||
|
@ -1360,6 +1409,13 @@ _settings:
|
|||
timelineAndNote: "Línia de temps i nota"
|
||||
makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables"
|
||||
makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
|
||||
useStickyIcons: "Utilitza icones fixes"
|
||||
showNavbarSubButtons: "Mostrar sub botons a la barra de navegació "
|
||||
ifOn: "Quan s'encén "
|
||||
ifOff: "Quan s'apaga "
|
||||
_chat:
|
||||
showSenderName: "Mostrar el nom del remitent"
|
||||
sendOnEnter: "Introdueix per enviar"
|
||||
_preferencesProfile:
|
||||
profileName: "Nom del perfil"
|
||||
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
|
||||
|
@ -1869,6 +1925,7 @@ _role:
|
|||
canImportFollowing: "Autoritza la importació de seguidors"
|
||||
canImportMuting: "Autoritza la importació de silenciats"
|
||||
canImportUserLists: "Autoritza la importació de llistes d'usuaris "
|
||||
canChat: "Pot xatejar"
|
||||
_condition:
|
||||
roleAssignedTo: "Assignat a rols manuals"
|
||||
isLocal: "Usuari local"
|
||||
|
@ -2099,6 +2156,7 @@ _sfx:
|
|||
noteMy: "Nota (per mi)"
|
||||
notification: "Notificacions"
|
||||
reaction: "Quan se selecciona una reacció "
|
||||
chatMessage: "Missatges del xat"
|
||||
_soundSettings:
|
||||
driveFile: "Fer servir un fitxer d'àudio del disc"
|
||||
driveFileWarn: "Seleccionar un fitxer d'àudio del disc"
|
||||
|
@ -2245,6 +2303,8 @@ _permissions:
|
|||
"read:clip-favorite": "Veure clips favorits"
|
||||
"read:federation": "Veure dades de federació"
|
||||
"write:report-abuse": "Informar d'un abús"
|
||||
"write:chat": "Crear o esborrar missatges de xat"
|
||||
"read:chat": "Explorar xats"
|
||||
_auth:
|
||||
shareAccessTitle: "Concedeix permisos a l'aplicació"
|
||||
shareAccess: "Vols que {name} pugui accedir al vostre compte?"
|
||||
|
@ -2493,6 +2553,7 @@ _notification:
|
|||
newNote: "Nota nova"
|
||||
unreadAntennaNote: "Antena {name}"
|
||||
roleAssigned: "Rol assignat "
|
||||
chatRoomInvitationReceived: "T'han invitat a una sala de xat"
|
||||
emptyPushNotificationMessage: "Les notificacions han sigut actualitzades"
|
||||
achievementEarned: "Aconseguiment desblocat"
|
||||
testNotification: "Notificació de prova"
|
||||
|
@ -2521,6 +2582,7 @@ _notification:
|
|||
receiveFollowRequest: "Rebuda una petició de seguiment"
|
||||
followRequestAccepted: "Petició de seguiment acceptada"
|
||||
roleAssigned: "Rol donat"
|
||||
chatRoomInvitationReceived: "Invitat a la sala de xat"
|
||||
achievementEarned: "Assoliment desbloquejat"
|
||||
exportCompleted: "Exportació completada"
|
||||
login: "Iniciar sessió"
|
||||
|
@ -2534,6 +2596,9 @@ _notification:
|
|||
_deck:
|
||||
alwaysShowMainColumn: "Mostrar sempre la columna principal"
|
||||
columnAlign: "Alinea les columnes"
|
||||
columnGap: "Espai entre columnes"
|
||||
deckMenuPosition: "Posició del menú del tauler"
|
||||
navbarPosition: "Posició de la barra de navegació "
|
||||
addColumn: "Afig una columna"
|
||||
newNoteNotificationSettings: "Configuració de notificacions per a notes noves"
|
||||
configureColumn: "Configuració de columnes"
|
||||
|
@ -2660,6 +2725,7 @@ _moderationLogTypes:
|
|||
deletePage: "Esborrar la pàgina"
|
||||
deleteFlash: "Esborrar el guió"
|
||||
deleteGalleryPost: "Esborrar la publicació de la galeria"
|
||||
deleteChatRoom: "Esborra la sala de xat"
|
||||
updateProxyAccountDescription: "Actualitzar descripció del compte proxy"
|
||||
_fileViewer:
|
||||
title: "Detall del fitxer"
|
||||
|
|
|
@ -9,6 +9,9 @@ reset: "Obnovit"
|
|||
notifications: "Oznámení"
|
||||
username: "Uživatelské jméno"
|
||||
password: "Heslo"
|
||||
initialPasswordForSetup: "Počáteční heslo pro nastavení"
|
||||
initialPasswordIsIncorrect: "Počáteční heslo pro nastavení je nesprávné"
|
||||
initialPasswordForSetupDescription: "Použijte heslo, které jste nastavili v konfiguračním souboru, pokud jste Misskey instalovali ručně.\nPokud užíváte Misskey hostovací službu, použijte poskytnuté heslo.\nPokud jste heslo nenastavovali, zanechte prázdné."
|
||||
forgotPassword: "Zapomenuté heslo"
|
||||
fetchingAsApObject: "Načítám data z Fediversu..."
|
||||
ok: "Potvrdit"
|
||||
|
@ -46,6 +49,8 @@ pin: "Připnout"
|
|||
unpin: "Odepnout"
|
||||
copyContent: "Zkopírovat obsah"
|
||||
copyLink: "Kopírovat odkaz"
|
||||
copyRemoteLink: "Zkoprírovat vzdálený odkaz"
|
||||
copyLinkRenote: "Zkopírovat odkaz renotu"
|
||||
delete: "Smazat"
|
||||
deleteAndEdit: "Smazat a upravit"
|
||||
deleteAndEditConfirm: "Jste si jistí že chcete smazat tuto poznámku a editovat ji? Ztratíte tím všechny reakce, sdílení a odpovědi na ni."
|
||||
|
@ -261,7 +266,6 @@ removeAreYouSure: "Jste si jistí že chcete smazat \"{x}\"?"
|
|||
deleteAreYouSure: "Jste si jistí že chcete smazat \"{x}\"?"
|
||||
resetAreYouSure: "Opravdu resetovat?"
|
||||
saved: "Uloženo"
|
||||
messaging: "Zprávy"
|
||||
upload: "Nahrát soubory"
|
||||
keepOriginalUploading: "Ponechat originální obrázek"
|
||||
keepOriginalUploadingDescription: "Uloží původní nahraný obrázek jak je. Pokud je to vypnuté, vygeneruje se zobrazení verze na webu při nahrátí."
|
||||
|
@ -274,7 +278,6 @@ uploadFromUrlMayTakeTime: "Může trvat nějakou dobu, dokud nebude dokončeno n
|
|||
explore: "Objevovat"
|
||||
messageRead: "Přečtené"
|
||||
noMoreHistory: "To je vše"
|
||||
startMessaging: "Zahájit chat"
|
||||
nUsersRead: "přečteno {n} uživateli"
|
||||
agreeTo: "Souhlasím s {0}"
|
||||
agree: "Souhlasím"
|
||||
|
@ -453,8 +456,6 @@ retype: "Zadejte znovu"
|
|||
noteOf: "{user} poznámky"
|
||||
quoteAttached: "Citace"
|
||||
quoteQuestion: "Přiložit jako citaci?"
|
||||
noMessagesYet: "Zatím tu nejsou žádné zprávy"
|
||||
newMessageExists: "Máte novou zprávu"
|
||||
onlyOneFileCanBeAttached: "Ke zprávě můžete přiložit jenom jeden soubor"
|
||||
signinRequired: "Přihlašte se, prosím"
|
||||
invitations: "Pozvat"
|
||||
|
@ -478,6 +479,8 @@ uiLanguage: "Jazyk uživatelského rozhraní"
|
|||
aboutX: "O {x}"
|
||||
emojiStyle: "Styl emoji"
|
||||
native: "Výchozí"
|
||||
style: "Vzhled"
|
||||
popup: "Vyskakovací okno"
|
||||
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
|
||||
noHistory: "Žádná historie"
|
||||
signinHistory: "Historie přihlášení"
|
||||
|
@ -540,6 +543,7 @@ showInPage: "Zobrazit na stránce"
|
|||
popout: "Pop-out"
|
||||
volume: "Hlasitost"
|
||||
masterVolume: "Celková hlasitost"
|
||||
notUseSound: "Zakázat zvuk"
|
||||
details: "Detaily"
|
||||
chooseEmoji: "Vybrat emotikon"
|
||||
unableToProcess: "Operace nebyla dokončena."
|
||||
|
@ -1104,6 +1108,12 @@ lastNDays: "Posledních {n} dnů"
|
|||
surrender: "Zrušit"
|
||||
postForm: "Formulář pro odeslání"
|
||||
information: "Informace"
|
||||
_chat:
|
||||
invitations: "Pozvat"
|
||||
noHistory: "Žádná historie"
|
||||
members: "Členové"
|
||||
home: "Domů"
|
||||
send: "Odeslat"
|
||||
_delivery:
|
||||
stop: "Suspendováno"
|
||||
_type:
|
||||
|
@ -1719,6 +1729,7 @@ _permissions:
|
|||
"write:gallery": "Upravit galerii"
|
||||
"read:gallery-likes": "Zobrazit seznam to se mi líbí příspěvků v galerii"
|
||||
"write:gallery-likes": "Upravit seznam to se mi líbí příspěvků v galerii"
|
||||
"write:chat": "Sestavit nebo mazat zprávy chatu"
|
||||
_auth:
|
||||
shareAccessTitle: "Udělovat oprávnění k aplikacím"
|
||||
shareAccess: "Chcete autorizovat \"{name}\" pro přístup k tomuto účtu?"
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?"
|
|||
resetAreYouSure: "Wirklich zurücksetzen?"
|
||||
areYouSure: "Bist du sicher?"
|
||||
saved: "Erfolgreich gespeichert"
|
||||
messaging: "Chat"
|
||||
upload: "Hochladen"
|
||||
keepOriginalUploading: "Originalbild speichern"
|
||||
keepOriginalUploadingDescription: "Speichert das Originalbild so, wie es ist. Ist dies deaktiviert, wird eine Version zum Anzeigen im Internet generiert."
|
||||
|
@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschl
|
|||
explore: "Erkunden"
|
||||
messageRead: "Gelesen"
|
||||
noMoreHistory: "Kein weiterer Verlauf vorhanden"
|
||||
startMessaging: "Neuen Chat erstellen"
|
||||
startChat: "Chat starten"
|
||||
nUsersRead: "Von {n} Benutzern gelesen"
|
||||
agreeTo: "Ich stimme {0} zu"
|
||||
agree: "Zustimmen"
|
||||
|
@ -491,8 +490,6 @@ noteOf: "Notiz von {user}"
|
|||
quoteAttached: "Zitat"
|
||||
quoteQuestion: "Als Zitat anhängen?"
|
||||
attachAsFileQuestion: "Der Text in der Zwischenablage ist lang. Möchtest du ihn als Textdatei anhängen?"
|
||||
noMessagesYet: "Noch keine Nachrichten vorhanden"
|
||||
newMessageExists: "Du hast eine neue Nachricht"
|
||||
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
|
||||
signinRequired: "Bitte registriere oder melde dich an, um fortzufahren"
|
||||
signinOrContinueOnRemote: "Um fortzufahren, gehe zu deiner Instanz oder registriere bzw. melde dich an dieser Instanz an. "
|
||||
|
@ -698,6 +695,7 @@ userSaysSomethingAbout: "{name} sagt etwas über '{word}'"
|
|||
makeActive: "Aktivieren"
|
||||
display: "Anzeigeart"
|
||||
copy: "Kopieren"
|
||||
copiedToClipboard: "In die Zwischenablage kopiert"
|
||||
metrics: "Metriken"
|
||||
overview: "Übersicht"
|
||||
logs: "Protokolle"
|
||||
|
@ -964,8 +962,8 @@ cropImageAsk: "Möchtest du das Bild zuschneiden?"
|
|||
cropYes: "Zuschneiden"
|
||||
cropNo: "Unbearbeitet verwenden"
|
||||
file: "Datei"
|
||||
recentNHours: "Letzten {n} Stunden"
|
||||
recentNDays: "Letzten {n} Tage"
|
||||
recentNHours: "Letzte {n} Stunden"
|
||||
recentNDays: "Letzte {n} Tage"
|
||||
noEmailServerWarning: "Es ist kein Email-Server konfiguriert."
|
||||
thereIsUnresolvedAbuseReportWarning: "Es liegen ungelöste Meldungen vor."
|
||||
recommended: "Empfehlung"
|
||||
|
@ -973,7 +971,7 @@ check: "Check"
|
|||
driveCapOverrideLabel: "Die Drive-Kapazität dieses Nutzers verändern"
|
||||
driveCapOverrideCaption: "Gib einen Wert von 0 oder weniger ein, um die Kapazität auf den Standard zurückzusetzen."
|
||||
requireAdminForView: "Melde dich mit einem Administratorkonto an, um dies einzusehen."
|
||||
isSystemAccount: "Ein Benutzerkonto, dass durch das System erstellt und automatisch kontrolliert wird."
|
||||
isSystemAccount: "Ein Benutzerkonto, das durch das System erstellt und automatisch verwaltet wird."
|
||||
typeToConfirm: "Bitte gib zur Bestätigung {x} ein"
|
||||
deleteAccount: "Benutzerkonto löschen"
|
||||
document: "Dokumentation"
|
||||
|
@ -1144,7 +1142,7 @@ preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive
|
|||
preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
|
||||
options: "Optionen"
|
||||
specifyUser: "Spezifischer Benutzer"
|
||||
lookupConfirm: "Zustimmen?"
|
||||
lookupConfirm: "Bist du sicher, dass du das nachschlagen möchtest?"
|
||||
openTagPageConfirm: "Hashtag Seite wirklich öffnen?"
|
||||
specifyHost: "Host"
|
||||
failedToPreviewUrl: "Vorschau nicht anzeigbar"
|
||||
|
@ -1259,7 +1257,7 @@ replaying: "Aufzeichnung"
|
|||
endReplay: "Aufzeichnung verlassen"
|
||||
copyReplayData: "Aufzeichnung kopieren"
|
||||
ranking: "Rangliste"
|
||||
lastNDays: "Letzten {n} Tage"
|
||||
lastNDays: "Letzte {n} Tage"
|
||||
backToTitle: "Zurück zum Startbildschirm"
|
||||
hemisphere: "Hemisphäre"
|
||||
withSensitive: "Zeige \"sensitive Inhalte\" an"
|
||||
|
@ -1308,19 +1306,127 @@ pleaseSelectAccount: "Bitte Konto auswählen"
|
|||
availableRoles: "Verfügbare Rollen"
|
||||
federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren."
|
||||
federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren."
|
||||
confirmOnReact: "Reagieren bestätigen"
|
||||
reactAreYouSure: "Willst du eine \"{emoji}\"-Reaktion hinzufügen?"
|
||||
markAsSensitiveConfirm: "Möchtest du dieses Medium als sensibel kennzeichnen?"
|
||||
unmarkAsSensitiveConfirm: "Möchtest du die Kennzeichnung dieses Mediums als sensibel aufheben?"
|
||||
preferences: "Einstellungen"
|
||||
preferencesProfile: "Einstellungsprofil"
|
||||
copyPreferenceId: "Kopiere die Einstellungs-ID"
|
||||
resetToDefaultValue: "Auf Standard zurücksetzen"
|
||||
untitled: "Unbenannt"
|
||||
noName: "Kein Name"
|
||||
skip: "Überspringen"
|
||||
restore: "Wiederherstellen"
|
||||
syncBetweenDevices: "Zwischen Geräten synchronisieren"
|
||||
preferenceSyncConflictTitle: "Der konfigurierte Wert ist auf dem Server bereits vorhanden."
|
||||
preferenceSyncConflictText: "Die Einstellungen mit aktivierter Synchronisierung werden ihre Werte auf dem Server speichern. Es gibt jedoch bereits Werte auf dem Server. Welche Einstellungswerte sollen überschrieben werden?"
|
||||
preferenceSyncConflictChoiceServer: "Konfigurierte Werte auf dem Server"
|
||||
preferenceSyncConflictChoiceDevice: "Konfigurierte Werte auf dem Gerät"
|
||||
preferenceSyncConflictChoiceCancel: "Einrichten der Synchronisierung abbrechen"
|
||||
paste: "Einfügen"
|
||||
emojiPalette: "Emoji-Palette"
|
||||
postForm: "Notizfenster"
|
||||
textCount: "Zeichenanzahl"
|
||||
information: "Über"
|
||||
chat: "Chat"
|
||||
migrateOldSettings: "Alte Client-Einstellungen migrieren"
|
||||
migrateOldSettings_description: "Dies sollte normalerweise automatisch geschehen, aber wenn die Migration aus irgendeinem Grund nicht erfolgreich war, kannst du den Migrationsprozess selbst manuell auslösen. Die aktuellen Konfigurationsinformationen werden dabei überschrieben."
|
||||
compress: "Komprimieren"
|
||||
_chat:
|
||||
noMessagesYet: "Noch keine Nachrichten"
|
||||
newMessage: "Neue Nachricht"
|
||||
individualChat: "Privater Chat"
|
||||
individualChat_description: "Führe einen privaten Chat mit einer anderen Person."
|
||||
roomChat_description: "Ein Chat-Raum, an dem mehrere Personen teilnehmen können.\nDu kannst auch Personen einladen, die keine privaten Chats zulassen, wenn sie die Einladung annehmen."
|
||||
createRoom: "Raum erstellen"
|
||||
inviteUserToChat: "Lade Benutzer ein, um mit dem Chatten zu beginnen"
|
||||
yourRooms: "Erstellte Räume"
|
||||
invitations: "Einladen"
|
||||
noInvitations: "Keine Einladungen"
|
||||
history: "Verlauf"
|
||||
noHistory: "Kein Verlauf gefunden"
|
||||
noRooms: "Keine Räume gefunden"
|
||||
inviteUser: "Benutzer einladen"
|
||||
sentInvitations: "Verschickte Einladungen"
|
||||
join: "Beitreten"
|
||||
ignore: "Ignorieren"
|
||||
leave: "Raum verlassen"
|
||||
members: "Mitglieder"
|
||||
searchMessages: "Nachrichten suchen"
|
||||
home: "Startseite"
|
||||
send: "Senden"
|
||||
newline: "Neue Zeile"
|
||||
muteThisRoom: "Raum stummschalten"
|
||||
deleteRoom: "Raum löschen"
|
||||
chatNotAvailableForThisAccountOrServer: "Der Chat ist auf diesem Server oder für dieses Konto nicht aktiviert."
|
||||
chatNotAvailableInOtherAccount: "Die Chatfunktion wurde vom anderen Benutzer deaktiviert."
|
||||
cannotChatWithTheUser: "Starten eines Chats mit diesem Benutzer nicht möglich"
|
||||
cannotChatWithTheUser_description: "Der Chat ist entweder nicht verfügbar oder die andere Seite hat den Chat nicht aktiviert."
|
||||
chatWithThisUser: "Mit dem Benutzer chatten"
|
||||
thisUserAllowsChatOnlyFromFollowers: "Dieser Benutzer nimmt nur Chats von Followern an."
|
||||
thisUserAllowsChatOnlyFromFollowing: "Dieser Benutzer nimmt nur Chats von Benutzern an, denen er folgt."
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "Dieser Benutzer akzeptiert nur Chats von Benutzern, die sich gegenseitig folgen."
|
||||
thisUserNotAllowedChatAnyone: "Dieser Benutzer nimmt keine Chats von anderen Benutzern an."
|
||||
chatAllowedUsers: "Wem das Chatten erlaubt werden soll"
|
||||
chatAllowedUsers_note: "Du kannst unabhängig von dieser Einstellung mit allen Personen chatten, denen du eine Chat-Nachricht gesendet hast."
|
||||
_chatAllowedUsers:
|
||||
everyone: "Jeder"
|
||||
followers: "Nur deine Follower"
|
||||
following: "Nur Benutzer, denen du folgst"
|
||||
mutual: "Nur Benutzer, die sich gegenseitig folgen"
|
||||
none: "Niemand"
|
||||
_emojiPalette:
|
||||
palettes: "Palette"
|
||||
enableSyncBetweenDevicesForPalettes: "Synchronisierung der Paletten zwischen Geräten aktivieren"
|
||||
paletteForMain: "Hauptpalette"
|
||||
paletteForReaction: "Reaktions-Palette"
|
||||
_settings:
|
||||
driveBanner: "Du kannst den Drive verwalten und konfigurieren, die Auslastung überprüfen und Einstellungen für das Hochladen von Dateien vornehmen."
|
||||
pluginBanner: "Du kannst die Funktionen des Clients mit Plugins erweitern. Plugins können installiert, individuell konfiguriert und verwaltet werden."
|
||||
api: "API"
|
||||
webhook: "Webhook"
|
||||
serviceConnectionBanner: "Du kannst Zugriffstoken und Webhooks für die Integration mit externen Anwendungen und Diensten verwalten und konfigurieren."
|
||||
accountData: "Kontodaten"
|
||||
accountDataBanner: "Export/Import und Verwaltung von Kontodatenarchiven."
|
||||
muteAndBlockBanner: "Du kannst Einstellungen konfigurieren und verwalten, um Inhalte auszublenden und Aktionen für bestimmte Benutzer zu beschränken."
|
||||
accessibilityBanner: "Die Clients können personalisiert und für eine optimale Nutzung im Hinblick auf ihre Darstellung und ihr Verhalten eingerichtet werden."
|
||||
privacyBanner: "Du kannst Einstellungen für die Privatsphäre deines Kontos vornehmen, z. B. inwieweit Inhalte veröffentlicht werden, wie leicht sie zu finden sind und ob Follower genehmigt werden müssen."
|
||||
securityBanner: "Du kannst Einstellungen für die Kontosicherheit konfigurieren, z. B. Passwörter, Anmeldemethoden, Authentifizierungs-Apps und Passkeys."
|
||||
appearanceBanner: "Du kannst das Erscheinungsbild und die Anzeigeeinstellungen für den Client nach deinen Wünschen konfigurieren."
|
||||
soundsBanner: "Du kannst die Einstellungen für die Wiedergabe von Klängen im Client konfigurieren."
|
||||
timelineAndNote: "Chroniken und Notizen"
|
||||
makeEveryTextElementsSelectable: "Alle Textelemente auswählbar machen"
|
||||
makeEveryTextElementsSelectable_description: "Die Aktivierung kann in manchen Situationen die Benutzerfreundlichkeit beeinträchtigen."
|
||||
ifOn: "Wenn eingeschaltet"
|
||||
ifOff: "Wenn ausgeschaltet"
|
||||
_chat:
|
||||
showSenderName: "Name des Absenders anzeigen"
|
||||
sendOnEnter: "Eingabetaste sendet Nachricht"
|
||||
_preferencesProfile:
|
||||
profileName: "Profilname"
|
||||
profileNameDescription: "Lege einen Namen fest, der dieses Gerät identifiziert."
|
||||
profileNameDescription2: "Beispiel: \"Haupt-PC\", \"Smartphone\""
|
||||
_preferencesBackup:
|
||||
autoBackup: "Automatische Sicherung"
|
||||
restoreFromBackup: "Wiederherstellen aus der Sicherung"
|
||||
noBackupsFoundTitle: "Keine Sicherungen gefunden"
|
||||
noBackupsFoundDescription: "Es wurden keine automatisch erstellten Sicherungen gefunden, aber wenn du eine Sicherungsdatei manuell gespeichert hast, kannst du diese importieren und wiederherstellen."
|
||||
selectBackupToRestore: "Wähle die wiederherzustellende Sicherung"
|
||||
youNeedToNameYourProfileToEnableAutoBackup: "Um die automatische Sicherung zu aktivieren, müssen Profilnamen festgelegt werden."
|
||||
autoPreferencesBackupIsNotEnabledForThisDevice: "Die automatische Sicherung der Einstellungen ist auf diesem Gerät nicht aktiviert."
|
||||
backupFound: "Konfigurationssicherung gefunden."
|
||||
_accountSettings:
|
||||
requireSigninToViewContents: "Anmeldung erfordern, um Inhalte anzuzeigen"
|
||||
requireSigninToViewContentsDescription1: "Erfordere eine Anmeldung, um alle Notizen und andere Inhalte anzuzeigen, die du erstellt hast. Dadurch wird verhindert, dass Crawler deine Informationen sammeln."
|
||||
requireSigninToViewContentsDescription2: "Der Inhalt wird nicht in URL-Vorschauen (OGP), eingebettet in Webseiten oder auf Servern, die keine Zitate unterstützen, angezeigt."
|
||||
requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern."
|
||||
makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar"
|
||||
makeNotesFollowersOnlyBeforeDescription: "Solange diese Funktion aktiviert ist, sind Notizen, die nach dem eingestellten Datum und der eingestellten Zeit liegen oder die eingestellte Zeit abgelaufen ist, nur für Follower sichtbar. Bei Deaktivierung wird auch der öffentliche Status der Notiz wiederhergestellt."
|
||||
makeNotesHiddenBefore: "Frühere Notizen privat machen"
|
||||
makeNotesHiddenBeforeDescription: ""
|
||||
mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden."
|
||||
mayNotEffectSomeSituations: "Diese Einschränkungen sind vereinfacht. Sie gelten möglicherweise nicht in allen Situationen, z. B. bei der Anzeige auf einem fremden Server oder während der Moderation."
|
||||
notesOlderThanSpecifiedDateAndTime: "Notizen vor einem bestimmtem Datum und Uhrzeit"
|
||||
_abuseUserReport:
|
||||
forward: "Weiterleiten"
|
||||
|
@ -1328,11 +1434,15 @@ _abuseUserReport:
|
|||
resolve: "lösen"
|
||||
accept: "Akzeptieren"
|
||||
reject: "Ablehnen"
|
||||
resolveTutorial: "Wenn der Inhalt der Meldung rechtmäßig ist, wähle „Akzeptieren“, um sie als gelöst zu markieren.\nWenn der Inhalt der Meldung unzulässig ist, wähle „Ablehnen“, um sie zu ignorieren."
|
||||
_delivery:
|
||||
status: "Auslieferungsstatus"
|
||||
stop: "Gesperrt"
|
||||
_type:
|
||||
none: "Wird veröffentlicht"
|
||||
manuallySuspended: "Manuell gesperrt"
|
||||
goneSuspended: "Gesperrt wegen Löschung des Servers"
|
||||
autoSuspendedForNotResponding: "Gesperrt, weil der Server nicht antwortet"
|
||||
_bubbleGame:
|
||||
howToPlay: "Wie man spielt"
|
||||
hold: "Halten"
|
||||
|
@ -1342,6 +1452,8 @@ _bubbleGame:
|
|||
highScore: "Höchstpunktzahl"
|
||||
maxChain: "Maximale Anzahl an Verkettungen"
|
||||
yen: "{yen} Yen"
|
||||
estimatedQty: "{qty} Stück"
|
||||
scoreSweets: "{onigiriQtyWithUnit} Onigiri"
|
||||
_howToPlay:
|
||||
section1: "Passe die Position an und lasse das Objekt in das Spielfeld fallen."
|
||||
section2: "Wenn sich zwei Objekte der gleichen Art berühren, verwandeln sie sich in ein anderes Objekt und du bekommst Punkte."
|
||||
|
@ -1399,15 +1511,20 @@ _initialTutorial:
|
|||
reactDone: "Du kannst eine Reaktion zurücknehmen, indem du auf den '-' Button drückst."
|
||||
_timeline:
|
||||
title: "So funktionieren die Chroniken"
|
||||
description1: "Misskey stellt mehrere Chroniken bereit (einige können je nach den Richtlinien des Servers nicht verfügbar sein)."
|
||||
home: "Du kannst Beiträge von den Konten sehen, denen du folgst."
|
||||
local: "Du kannst Beiträge aller Benutzer auf diesem Server sehen."
|
||||
social: "Notizen von der Startseite und der lokalen Chronik werden angezeigt."
|
||||
global: "Du kannst Notizen von allen föderierten Servern sehen."
|
||||
description2: "Du kannst jederzeit am oberen Rand des Bildschirms zwischen den jeweiligen Chroniken wechseln."
|
||||
description3: "Darüber hinaus gibt es Listen-Chroniken und Kanal-Chroniken. Weitere Einzelheiten findest du unter {link}."
|
||||
_postNote:
|
||||
description1: "Wenn du eine Notiz auf Misskey veröffentlichst, stehen dir verschiedene Optionen zur Verfügung. Die Oberfläche sieht folgendermaßen aus."
|
||||
_visibility:
|
||||
description: "Du kannst einschränken, wer deine Notiz sehen kann."
|
||||
public: "Deine Notiz wird für alle Nutzer sichtbar sein."
|
||||
home: "Nur auf der Startseite sichtbar. Kann von Followern, Profilbesuchern und durch Renotes gesehen werden."
|
||||
followers: "Nur für Follower sichtbar. Nur Follower können es sehen und niemand sonst, und es kann nicht von anderen gerenoted werden."
|
||||
direct: "Die Notiz wird nur für den angegebenen Benutzer veröffentlicht und der Empfänger wird benachrichtigt. Kann anstelle von Direktnachrichten verwendet werden."
|
||||
doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!"
|
||||
doNotSendConfidencialOnDirect2: "Die Administratoren des Servers können den Inhalt der Notiz sehen. Sei vorsichtig mit sensiblen Informationen, wenn du Direktnachrichten an Benutzer auf nicht vertrauenswürdigen Servern sendest."
|
||||
|
@ -1418,8 +1535,10 @@ _initialTutorial:
|
|||
_exampleNote:
|
||||
cw: "Das wird dich bestimmt hungrig machen!"
|
||||
note: "Ich hatte gerade einen Donut mit Schokoladenüberzug 🍩😋"
|
||||
useCases: "Dient zur Kennzeichnung von Notizen, wie sie in den Serverrichtlinien vorgeschrieben sind, oder zur eigenen Festlegung von Spoiler-Beiträgen oder sensiblem Text."
|
||||
_howToMakeAttachmentsSensitive:
|
||||
title: "Wie markiert man Anhänge als sensibel?"
|
||||
description: "Markiere Anhänge als sensibel, die aufgrund von den Serverregeln nicht sichtbar sein sollen."
|
||||
tryThisFile: "Versuche, das angehängte Bild als sensibel zu markieren!"
|
||||
_exampleNote:
|
||||
note: "Ups, ich habe es vergeigt, den Natto-Deckel zu öffnen..."
|
||||
|
@ -1430,7 +1549,9 @@ _initialTutorial:
|
|||
title: "Du hast das Tutorial abgeschlossen! 🎉"
|
||||
description: "Die hier beschriebenen Funktionen sind nur ein kleiner Teil dessen, was Misskey zu bieten hat; um mehr darüber zu erfahren, wie du Misskey benutzen kannst, besuche bitte {link}."
|
||||
_timelineDescription:
|
||||
home: "In der Startseiten-Chronik kannst du Notizen von Konten sehen, denen du folgst."
|
||||
local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server."
|
||||
social: "Die soziale Chronik zeigt Notizen von der Startseite und der lokalen Chronik."
|
||||
global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern."
|
||||
_serverRules:
|
||||
description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
|
||||
|
@ -1447,6 +1568,9 @@ _serverSettings:
|
|||
fanoutTimelineDbFallback: "Auf die Datenbank zurückfallen"
|
||||
fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. "
|
||||
reactionsBufferingDescription: "Wenn diese Option aktiviert ist, kann sie die Leistung beim Erstellen von Reaktionen erheblich verbessern und die Belastung der Datenbank verringern. Allerdings steigt die Speichernutzung von Redis."
|
||||
inquiryUrl: "Kontakt-URL"
|
||||
inquiryUrlDescription: "Gib eine URL für das Kontaktformular der Serverbetreiber oder eine Webseite an, die Kontaktinformationen enthält."
|
||||
openRegistration: "Registrierung von Konten aktivieren"
|
||||
openRegistrationWarning: "Das Aktivieren von Registrierungen ist riskant. Es wird empfohlen, sie nur dann zu aktivieren, wenn der Server ständig überwacht wird und im Falle eines Problems sofort reagiert werden kann."
|
||||
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Wenn über einen bestimmten Zeitraum keine Moderatorenaktivität festgestellt wird, wird diese Einstellung automatisch deaktiviert, um Spam zu verhindern."
|
||||
_accountMigration:
|
||||
|
@ -1711,8 +1835,10 @@ _achievements:
|
|||
description: "Tutorial abgeschlossen"
|
||||
_bubbleGameExplodingHead:
|
||||
title: "🤯"
|
||||
description: "Das größte Objekt im Bubble Game"
|
||||
_bubbleGameDoubleExplodingHead:
|
||||
title: "Doppel🤯"
|
||||
description: "Zwei der größten Objekte im Bubble Game zur gleichen Zeit"
|
||||
_role:
|
||||
new: "Rolle erstellen"
|
||||
edit: "Rolle bearbeiten"
|
||||
|
@ -1762,6 +1888,7 @@ _role:
|
|||
canManageAvatarDecorations: "Profilbilddekorationen verwalten"
|
||||
driveCapacity: "Drive-Kapazität"
|
||||
alwaysMarkNsfw: "Dateien immer als NSFW markieren"
|
||||
canUpdateBioMedia: "Kann ein Profil- oder ein Bannerbild bearbeiten"
|
||||
pinMax: "Maximale Anzahl an angehefteten Notizen"
|
||||
antennaMax: "Maximale Anzahl an Antennen"
|
||||
wordMuteMax: "Maximale Zeichenlänge für Wortstummschaltungen"
|
||||
|
@ -1777,11 +1904,20 @@ _role:
|
|||
canUseTranslator: "Verwendung des Übersetzers"
|
||||
avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können"
|
||||
canImportAntennas: "Importieren von Antennen erlauben"
|
||||
canImportBlocking: "Importieren von Blockierungen zulassen"
|
||||
canImportFollowing: "Importieren von Gefolgten zulassen"
|
||||
canImportMuting: "Importieren von Stummgeschalteten zulassen"
|
||||
canImportUserLists: "Importieren von Listen erlauben"
|
||||
canChat: "Chatten erlauben"
|
||||
_condition:
|
||||
roleAssignedTo: "Manuellen Rollen zugewiesen"
|
||||
isLocal: "Lokaler Benutzer"
|
||||
isRemote: "Benutzer fremder Instanz"
|
||||
isCat: "Katzen-Benutzer"
|
||||
isBot: "Bot-Benutzer"
|
||||
isSuspended: "Gesperrter Benutzer"
|
||||
isLocked: "Private Konten"
|
||||
isExplorable: "Benutzer, die ihr Konto im \"Erkunden\"-Bereich sichtbar machen"
|
||||
createdLessThan: "Kontoerstellung liegt weniger als X zurück"
|
||||
createdMoreThan: "Kontoerstellung liegt mehr als X zurück"
|
||||
followersLessThanOrEq: "Hat X oder weniger Follower"
|
||||
|
@ -1936,6 +2072,7 @@ _theme:
|
|||
installed: "{name} wurde installiert"
|
||||
installedThemes: "Installierte Farbschemata"
|
||||
builtinThemes: "Eingebaute Farbschemata"
|
||||
instanceTheme: "Server-Thema"
|
||||
alreadyInstalled: "Dieses Farbschema ist bereits installiert"
|
||||
invalid: "Der Code dieses Farbschemas ist ungültig"
|
||||
make: "Farbschema erstellen"
|
||||
|
@ -2002,6 +2139,7 @@ _sfx:
|
|||
noteMy: "Meine Notizen"
|
||||
notification: "Benachrichtigungen"
|
||||
reaction: "Auswählen einer Reaktion"
|
||||
chatMessage: "Chat-Nachrichten"
|
||||
_soundSettings:
|
||||
driveFile: "Audiodatei aus dem Drive verwenden"
|
||||
driveFileWarn: "Wähle eine Audiodatei aus dem Drive"
|
||||
|
@ -2025,6 +2163,10 @@ _timeIn:
|
|||
seconds: "In {n}s"
|
||||
minutes: "In {n} Min."
|
||||
hours: "In {n} Std."
|
||||
days: "In {n} Tagen"
|
||||
weeks: "In {n} Wochen"
|
||||
months: "In {n} Monaten"
|
||||
years: "In {n} Jahren"
|
||||
_time:
|
||||
second: "Sekunde(n)"
|
||||
minute: "Minute(n)"
|
||||
|
@ -2058,6 +2200,7 @@ _2fa:
|
|||
backupCodesDescription: "Verwende diese Codes, falls du nicht mehr auf deine App zur Zweifaktorauthentifizierung zugreifen kannst. Jeder Code kann nur einmal verwendet werden. Bewahre sie an einem sicheren Ort auf."
|
||||
backupCodeUsedWarning: "Ein Backup-Code wurde verwendet. Falls du den Zugriff zu deiner Zweifaktorauthentifizierungsapp verloren hast, konfiguriere diese bitte möglichst bald erneut."
|
||||
backupCodesExhaustedWarning: "Alle Backup-Codes wurden verwendet. Falls du den Zugang zu deiner Zweifaktorauthentifizierungsapp verlierst, wirst du dich nicht mehr in dieses Konto einloggen können. Bitte konfiguriere diese App erneut."
|
||||
moreDetailedGuideHere: "Hier ist eine ausführliche Anleitung"
|
||||
_permissions:
|
||||
"read:account": "Deine Benutzerkontoinformationen lesen"
|
||||
"write:account": "Deine Benutzerkontoinformationen bearbeiten"
|
||||
|
@ -2095,6 +2238,7 @@ _permissions:
|
|||
"write:flash": "Deine Plays bearbeiten oder löschen"
|
||||
"read:flash-likes": "Liste der Plays, die mir gefallen, lesen"
|
||||
"write:flash-likes": "Liste der Plays, die mir gefallen, bearbeiten"
|
||||
"read:admin:abuse-user-reports": "Meldungen von Benutzern ansehen"
|
||||
"write:admin:delete-account": "Benutzerkonto löschen"
|
||||
"write:admin:delete-all-files-of-a-user": "Alle Dateien eines Benutzers löschen"
|
||||
"read:admin:index-stats": "Statistiken zu Datenbankindizes einsehen"
|
||||
|
@ -2102,10 +2246,17 @@ _permissions:
|
|||
"read:admin:user-ips": "IP-Adressen von Benutzern anzeigen"
|
||||
"read:admin:meta": "Metadaten der Instanz einsehen"
|
||||
"write:admin:reset-password": "Benutzerpasswort zurücksetzen"
|
||||
"write:admin:resolve-abuse-user-report": "Meldungen von Benutzern lösen"
|
||||
"write:admin:send-email": "E-Mail versenden"
|
||||
"read:admin:server-info": "Serverinformationen anzeigen"
|
||||
"read:admin:show-moderation-log": "Moderationsprotokoll einsehen"
|
||||
"read:admin:show-user": "Private Benutzerinformationen einsehen"
|
||||
"write:admin:suspend-user": "Benutzer sperren"
|
||||
"write:admin:unset-user-avatar": "Benutzer-Profilbild entfernen"
|
||||
"write:admin:unset-user-banner": "Benutzer-Banner entfernen"
|
||||
"write:admin:unsuspend-user": "Benutzer entsperren"
|
||||
"write:admin:meta": "Metadaten der Instanz verwalten"
|
||||
"write:admin:user-note": "Moderationsvermerke verwalten"
|
||||
"write:admin:roles": "Rollen verwalten"
|
||||
"read:admin:roles": "Rollen anzeigen"
|
||||
"write:admin:relays": "Relays verwalten"
|
||||
|
@ -2122,6 +2273,17 @@ _permissions:
|
|||
"read:admin:emoji": "Emojis anzeigen"
|
||||
"write:admin:queue": "Job-Warteschlange verwalten"
|
||||
"read:admin:queue": "Job-Warteschlange anzeigen"
|
||||
"write:admin:drive": "Benutzer-Drive verwalten"
|
||||
"read:admin:drive": "Benutzer-Drive ansehen"
|
||||
"read:admin:stream": "Verwendung der Websocket-API für Administratoren"
|
||||
"write:admin:ad": "Werbung verwalten"
|
||||
"read:admin:ad": "Werbung ansehen"
|
||||
"write:invite-codes": "Einladungscodes erstellen"
|
||||
"read:invite-codes": "Einladungscodes anzeigen"
|
||||
"read:federation": "Informationen zur Föderation einsehen"
|
||||
"write:report-abuse": "Verstöße melden"
|
||||
"write:chat": "Chats bedienen"
|
||||
"read:chat": "Chats durchsuchen"
|
||||
_auth:
|
||||
shareAccessTitle: "Verteilung von App-Berechtigungen"
|
||||
shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?"
|
||||
|
@ -2133,6 +2295,7 @@ _auth:
|
|||
accepted: "Zugriff gewährt"
|
||||
denied: "Zugriff verweigert"
|
||||
pleaseLogin: "Bitte logge dich ein, um Apps zu authorisieren."
|
||||
byClickingYouWillBeRedirectedToThisUrl: "Wenn der Zugang gewährt wird, wirst du automatisch zu folgender URL weitergeleitet"
|
||||
_antennaSources:
|
||||
all: "Alle Notizen"
|
||||
homeTimeline: "Notizen von Benutzern, denen gefolgt wird"
|
||||
|
@ -2243,6 +2406,7 @@ _profile:
|
|||
avatarDecorationMax: "Du kannst bis zu {max} Dekorationen hinzufügen."
|
||||
followedMessage: "Nachricht, wenn dir jemand folgt"
|
||||
followedMessageDescription: "Du kannst eine kurze Nachricht festlegen, die dem Empfänger angezeigt wird, wenn er dir folgt."
|
||||
followedMessageDescriptionForLockedAccount: "Wenn Folgeanfragen deine Genehmigung brauchen, wird dies beim Genehmigen einer Anfrage angezeigt."
|
||||
_exportOrImport:
|
||||
allNotes: "Alle Notizen"
|
||||
favoritedNotes: "Als Favorit markierte Notizen"
|
||||
|
@ -2300,6 +2464,7 @@ _play:
|
|||
title: "Titel"
|
||||
script: "Skript"
|
||||
summary: "Beschreibung"
|
||||
visibilityDescription: "Wenn du die Sichtbarkeit auf Privat stellst, wird der Play nicht auf deinem Profil sichtbar sein, aber jeder, der die URL hat, kann ihn trotzdem aufrufen."
|
||||
_pages:
|
||||
newPage: "Seite erstellen"
|
||||
editPage: "Seite bearbeiten"
|
||||
|
@ -2331,6 +2496,7 @@ _pages:
|
|||
eyeCatchingImageSet: "Vorschaubild festlegen"
|
||||
eyeCatchingImageRemove: "Vorschaubild entfernen"
|
||||
chooseBlock: "Block hinzufügen"
|
||||
enterSectionTitle: "Titel des Abschnitts eingeben"
|
||||
selectType: "Typ auswählen"
|
||||
contentBlocks: "Inhalt"
|
||||
inputBlocks: "Eingabe"
|
||||
|
@ -2341,6 +2507,8 @@ _pages:
|
|||
section: "Abschnitt"
|
||||
image: "Bild"
|
||||
button: "Knopf"
|
||||
dynamic: "Dynamische Bausteine"
|
||||
dynamicDescription: "Dieser Baustein wurde abgeschafft. Bitte verwende von nun an {play}."
|
||||
note: "Eingebettete Notiz"
|
||||
_note:
|
||||
id: "Notiz-ID"
|
||||
|
@ -2363,6 +2531,7 @@ _notification:
|
|||
newNote: "Neue Notiz"
|
||||
unreadAntennaNote: "Antenne {name}"
|
||||
roleAssigned: "Rolle zugewiesen"
|
||||
chatRoomInvitationReceived: "Du wurdest in einen Chatraum eingeladen"
|
||||
emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert"
|
||||
achievementEarned: "Errungenschaft freigeschaltet"
|
||||
testNotification: "Testbenachrichtigung"
|
||||
|
@ -2376,6 +2545,7 @@ _notification:
|
|||
flushNotification: "Benachrichtigungen löschen"
|
||||
exportOfXCompleted: "Der Export von {x} ist abgeschlossen"
|
||||
login: "Neue Anmeldung erfolgt"
|
||||
createToken: "Ein Zugangstoken wurde erstellt"
|
||||
_types:
|
||||
all: "Alle"
|
||||
note: "Neue Notizen"
|
||||
|
@ -2389,9 +2559,11 @@ _notification:
|
|||
receiveFollowRequest: "Erhaltene Follow-Anfragen"
|
||||
followRequestAccepted: "Akzeptierte Follow-Anfragen"
|
||||
roleAssigned: "Rolle zugewiesen"
|
||||
chatRoomInvitationReceived: "Einladungen zum Chatraum"
|
||||
achievementEarned: "Errungenschaft freigeschaltet"
|
||||
exportCompleted: "Der Export ist abgeschlossen"
|
||||
login: "Anmeldung"
|
||||
createToken: "Erstellung von Zugriffstokens"
|
||||
test: "Test-Benachrichtigungen"
|
||||
app: "Benachrichtigungen von Apps"
|
||||
_actions:
|
||||
|
@ -2419,6 +2591,7 @@ _deck:
|
|||
useSimpleUiForNonRootPages: "Simple Benutzeroberfläche für navigierte Seiten verwenden"
|
||||
usedAsMinWidthWhenFlexible: "Ist \"Automatische Breitenanpassung\" aktiviert, wird hierfür die minimale Breite verwendet"
|
||||
flexible: "Automatische Breitenanpassung"
|
||||
enableSyncBetweenDevicesForProfiles: "Aktivieren der Synchronisierung von Profilinformationen zwischen Geräten"
|
||||
_columns:
|
||||
main: "Hauptspalte"
|
||||
widgets: "Widgets"
|
||||
|
@ -2454,7 +2627,14 @@ _webhookSettings:
|
|||
renote: "Wenn du ein Renote erhältst"
|
||||
reaction: "Wenn du eine Reaktion erhältst"
|
||||
mention: "Wenn du erwähnt wirst"
|
||||
_systemEvents:
|
||||
abuseReport: "Wenn eine neue Meldung eingeht"
|
||||
abuseReportResolved: "Wenn eine Meldung gelöst wird"
|
||||
userCreated: "Beim Anlegen eines Benutzers"
|
||||
inactiveModeratorsWarning: "Wenn Moderatoren für eine gewisse Zeit inaktiv sind"
|
||||
inactiveModeratorsInvitationOnlyChanged: "Wenn ein Moderator über einen gewissen Zeitraum inaktiv war und der Server auf Einladungsbasis umgestellt wird"
|
||||
deleteConfirm: "Bist du sicher, dass du den Webhook löschen willst?"
|
||||
testRemarks: "Klicke auf die Schaltfläche rechts neben dem Schalter, um einen Test-Webhook mit Dummy-Daten zu senden."
|
||||
_abuseReport:
|
||||
_notificationRecipient:
|
||||
createRecipient: "Meldungsempfänger hinzufügen"
|
||||
|
@ -2494,9 +2674,12 @@ _moderationLogTypes:
|
|||
resetPassword: "Passwort zurückgesetzt"
|
||||
suspendRemoteInstance: "Fremde Instanz gesperrt"
|
||||
unsuspendRemoteInstance: "Fremde Instanz entsperrt"
|
||||
updateRemoteInstanceNote: "Aktualisierung der Moderationshinweise für fremde Server."
|
||||
markSensitiveDriveFile: "Datei als sensitiv markiert"
|
||||
unmarkSensitiveDriveFile: "Datei als nicht sensitiv markiert"
|
||||
resolveAbuseReport: "Meldung bearbeitet"
|
||||
forwardAbuseReport: "Meldung weitergeleitet"
|
||||
updateAbuseReportNote: "Moderationsnotiz einer Meldung aktualisiert"
|
||||
createInvitation: "Einladung erstellt"
|
||||
createAd: "Werbung erstellt"
|
||||
deleteAd: "Werbung gelöscht"
|
||||
|
@ -2509,9 +2692,15 @@ _moderationLogTypes:
|
|||
createSystemWebhook: "System-Webhook erstellt"
|
||||
updateSystemWebhook: "System-Webhook aktualisiert"
|
||||
deleteSystemWebhook: "System-Webhook gelöscht"
|
||||
createAbuseReportNotificationRecipient: "Empfänger für Meldungen erstellt"
|
||||
updateAbuseReportNotificationRecipient: "Empfänger für Meldungen aktualisiert"
|
||||
deleteAbuseReportNotificationRecipient: "Empfänger für Meldungen entfernt"
|
||||
deleteAccount: "Benutzerkonto gelöscht"
|
||||
deletePage: "Seite gelöscht"
|
||||
deleteFlash: "Play gelöscht"
|
||||
deleteGalleryPost: "Galeriebeitrag gelöscht"
|
||||
deleteChatRoom: "Chatraum gelöscht"
|
||||
updateProxyAccountDescription: "Beschreibung des Proxy-Benutzerkontos aktualisiert"
|
||||
_fileViewer:
|
||||
title: "Dateiinformationen"
|
||||
type: "Dateityp"
|
||||
|
@ -2559,16 +2748,64 @@ _externalResourceInstaller:
|
|||
_themeInstallFailed:
|
||||
title: "Das Farbschema konnte nicht installiert werden"
|
||||
description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
|
||||
_dataSaver:
|
||||
_media:
|
||||
title: "Laden von Medien verhindern"
|
||||
description: "Verhindert, dass Bilder/Videos automatisch geladen werden. Ausgeblendete Bilder/Videos werden geladen, wenn du auf sie tippst."
|
||||
_avatar:
|
||||
title: "Animierte Profilbilder deaktivieren"
|
||||
description: "Die Animation von Profilbildern wird angehalten. Da animierte Bilder eine größere Dateigröße haben können als normale Bilder, kann dies den Datenverkehr weiter reduzieren."
|
||||
_urlPreview:
|
||||
title: "URL-Vorschaubilder ausblenden"
|
||||
description: "URL-Vorschaubilder werden nicht mehr geladen."
|
||||
_code:
|
||||
title: "Code-Hervorhebungen ausblenden"
|
||||
description: "Wenn Code-Hervorhebungen in MFM usw. verwendet werden, werden sie erst geladen, wenn sie angetippt werden. Die Syntaxhervorhebung erfordert das Herunterladen der Definitionsdateien für jede Programmiersprache. Es ist daher zu erwarten, dass die Deaktivierung des automatischen Ladens dieser Dateien die Menge des Datenverkehrs reduziert."
|
||||
_hemisphere:
|
||||
N: "Nördliche Erdhalbkugel"
|
||||
S: "Südliche Erdhalbkugel"
|
||||
caption: "Wird in einigen Client-Einstellungen zur Bestimmung der Jahreszeit verwendet."
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Spieleinstellungen"
|
||||
chooseBoard: "Spielbrett auswählen"
|
||||
blackOrWhite: "Schwarz/Weiß"
|
||||
blackIs: "{name} spielt Schwarz"
|
||||
rules: "Regeln"
|
||||
thisGameIsStartedSoon: "Das Spiel wird in Kürze beginnen"
|
||||
waitingForOther: "Warte auf den Zug des Gegenspielers"
|
||||
waitingForMe: "Warte auf deinen Zug"
|
||||
waitingBoth: "Mach dich bereit"
|
||||
ready: "Bereit"
|
||||
cancelReady: "Nicht bereit"
|
||||
opponentTurn: "Dein Gegner ist an der Reihe"
|
||||
myTurn: "Du bist am Zug"
|
||||
turnOf: "{name} ist am Zug"
|
||||
pastTurnOf: "Zug von {name}"
|
||||
surrender: "Aufgeben"
|
||||
surrendered: "Aufgegeben"
|
||||
timeout: "Zeit abgelaufen"
|
||||
drawn: "Unentschieden"
|
||||
won: "{name} hat gewonnen"
|
||||
black: "Schwarz"
|
||||
white: "Weiß"
|
||||
total: "Gesamt"
|
||||
turnCount: " Zug {count}"
|
||||
myGames: "Meine Runden"
|
||||
allGames: "Alle Runden"
|
||||
ended: "Beendet"
|
||||
playing: "Partie läuft"
|
||||
timeLimitForEachTurn: "Zeitlimit eines Zugs"
|
||||
freeMatch: "Freies Spiel"
|
||||
lookingForPlayer: "Gegner werden gesucht..."
|
||||
gameCanceled: "Das Spiel wurde abgesagt."
|
||||
shareToTlTheGameWhenStart: "Spiel in der Chronik teilen, wenn es gestartet wurde"
|
||||
iStartedAGame: "Das Spiel hat begonnen! #MisskeyReversi"
|
||||
opponentHasSettingsChanged: "Der Gegner hat seine Einstellungen geändert."
|
||||
allowIrregularRules: "Irreguläre Regeln (völlig frei)"
|
||||
disallowIrregularRules: "Keine irregulären Regeln"
|
||||
showBoardLabels: "Anzeige der Zeilen- und Spaltennummern am Spielbrett"
|
||||
useAvatarAsStone: "Steine in Benutzeravatare umwandeln"
|
||||
_offlineScreen:
|
||||
title: "Offline - keine Verbindung zum Server möglich"
|
||||
header: "Verbindung zum Server nicht möglich"
|
||||
|
@ -2582,14 +2819,68 @@ _urlPreviewSetting:
|
|||
requireContentLength: "Vorschau nur generieren, wenn Content-Length verfügbar ist"
|
||||
requireContentLengthDescription: "Wenn der Server keine Content-Length zurückgibt, wird keine Vorschau erzeugt."
|
||||
userAgent: "User-Agent"
|
||||
userAgentDescription: "Legt den User-Agent fest, der beim Abrufen der Vorschau verwendet werden soll. Bleibt er leer, wird der Standard-User-Agent verwendet."
|
||||
summaryProxy: "Proxy-Endpunkte, die Vorschaubilder erzeugen"
|
||||
summaryProxyDescription: "Generierung von Vorschaubildern mit Summaly Proxy anstelle von Misskey selbst."
|
||||
summaryProxyDescription2: "Die folgenden Parameter werden als Abfrage-Strings mit dem Proxy verknüpft. Wenn der Proxy sie nicht unterstützt, werden die Werte ignoriert."
|
||||
_mediaControls:
|
||||
pip: "Bild-in-Bild"
|
||||
playbackRate: "Wiedergabegeschwindigkeit"
|
||||
loop: "Endloswiedergabe"
|
||||
_contextMenu:
|
||||
title: "Kontextmenü"
|
||||
app: "Anwendung"
|
||||
appWithShift: "Anwendung per Umschalttaste"
|
||||
native: "Natives Browsermenü"
|
||||
_gridComponent:
|
||||
_error:
|
||||
requiredValue: "Dieser Wert ist ein Pflichtfeld"
|
||||
columnTypeNotSupport: "Die Validierung regulärer Ausdrücke wird nur für Spalten vom Typ \"Text\" unterstützt."
|
||||
patternNotMatch: "Dieser Wert stimmt nicht mit dem Schema in {pattern} überein"
|
||||
notUnique: "Dieser Wert muss eindeutig sein"
|
||||
_roleSelectDialog:
|
||||
notSelected: "Nicht ausgewählt"
|
||||
_customEmojisManager:
|
||||
_gridCommon:
|
||||
copySelectionRows: "Ausgewählte Zeilen kopieren"
|
||||
copySelectionRanges: "Auswahl kopieren"
|
||||
deleteSelectionRows: "Ausgewählte Zeilen löschen"
|
||||
searchSettings: "Sucheinstellungen"
|
||||
searchSettingCaption: "Detaillierte Suchkriterien festlegen."
|
||||
searchLimit: "Anzahl der Ergebnisse"
|
||||
sortOrder: "Sortierung"
|
||||
registrationLogs: "Registrierungsprotokoll"
|
||||
registrationLogsCaption: "Protokolle werden beim Aktualisieren oder Löschen von Emojis angezeigt. Sie verschwinden nach dem Aktualisieren oder Löschen, dem Wechsel zu einer neuen Seite oder dem Neuladen."
|
||||
alertEmojisRegisterFailedDescription: "Emoji konnte nicht aktualisiert oder gelöscht werden. Bitte prüfe das Registrierungsprotokoll für Details."
|
||||
_logs:
|
||||
failureLogNothing: "Es gibt kein Fehlerprotokoll."
|
||||
logNothing: "Keine Protokoll-Einträge."
|
||||
_remote:
|
||||
selectionRowDetail: "Details der ausgewählten Zeile"
|
||||
importSelectionRangesRows: "Zeilen in der Auswahl importieren"
|
||||
importEmojisButton: "Ausgewählte Emojis importieren"
|
||||
confirmImportEmojisTitle: "Emojis importieren"
|
||||
confirmImportEmojisDescription: "Importiere {count} Emoji(s), die von entfernten Server empfangen wurden. Bitte achte genau auf die Lizenz der Emojis. Bist du sicher, dass du fortfahren möchtest?"
|
||||
_local:
|
||||
tabTitleList: "Hinzugefügte Emojis"
|
||||
tabTitleRegister: "Emojis hinzufügen"
|
||||
_list:
|
||||
emojisNothing: "Es wurden keine Emojis hinzugefügt."
|
||||
alertUpdateEmojisNothingDescription: "Es wurden keine Emojis geändert."
|
||||
alertDeleteEmojisNothingDescription: "Es gibt keine zu löschenden Emojis."
|
||||
confirmUpdateEmojisDescription: "Aktualisiere {count} Emoji(s). Willst du fortfahren?"
|
||||
confirmDeleteEmojisDescription: "Lösche {count} ausgewählte Emoji(s). Willst du fortfahren?"
|
||||
confirmMovePageDesciption: "An den Emojis auf dieser Seite wurden Änderungen vorgenommen.\nWenn du die Seite verlässt, ohne zu speichern, werden alle auf dieser Seite vorgenommenen Änderungen verworfen."
|
||||
_register:
|
||||
uploadSettingDescription: "Hier kannst du das Verhalten beim Hochladen von Emojis konfigurieren."
|
||||
directoryToCategoryLabel: "Gib den Namen des Verzeichnisses in das Feld „Kategorie“ ein"
|
||||
directoryToCategoryCaption: "Wenn du ein Verzeichnis ziehst und ablegst, gib den Verzeichnisnamen in das Feld „Kategorie“ ein."
|
||||
emojiInputAreaList1: "Ziehe Bilddateien oder Verzeichnisse per Drag-and-drop in diesen Rahmen"
|
||||
emojiInputAreaList2: "Klicke auf diesen Link, um von deinem PC aus zu wählen"
|
||||
emojiInputAreaList3: "Klicke auf diesen Link, um vom Drive aus zu wählen"
|
||||
confirmRegisterEmojisDescription: "Füge die in der Liste aufgeführten Emojis als neue benutzerdefinierte Emojis hinzu. Bist du sicher? (Um eine Überlastung zu vermeiden, können nur {count} Emoji(s) in einem Vorgang hinzugefügt werden)"
|
||||
confirmClearEmojisDescription: "Verwerfe die Bearbeitungen und lösche die Emojis aus der Liste. Bist du sicher, dass du fortfahren möchtest?"
|
||||
confirmUploadEmojisDescription: "Lade die {count} abgelegte(n) Datei(en) in das Drive hoch. Bist du sicher, dass du fortfahren möchtest?"
|
||||
_embedCodeGen:
|
||||
title: "Einbettungscode anpassen"
|
||||
header: "Kopfzeile anzeigen"
|
||||
|
@ -2597,6 +2888,9 @@ _embedCodeGen:
|
|||
maxHeight: "Maximale Höhe"
|
||||
maxHeightDescription: "Der Wert 0 deaktiviert die Einstellung der maximalen Höhe. Gib einen Wert an, um zu verhindern, dass das Widget weiterhin vertikal vergrößert wird."
|
||||
maxHeightWarn: "Die Begrenzung der maximalen Höhe ist deaktiviert (0). Wenn dies nicht beabsichtigt war, setze die maximale Höhe auf einen Wert fest."
|
||||
previewIsNotActual: "Die Anzeige weicht von der tatsächlichen Einbettung ab, da sie den auf dem Vorschaufenster angezeigten Bereich überschreitet."
|
||||
rounded: "Ecken abrunden"
|
||||
border: "Dem äußeren Rand einen Rahmen hinzufügen"
|
||||
applyToPreview: "Auf die Vorschau anwenden"
|
||||
generateCode: "Einbettungscode generieren"
|
||||
codeGenerated: "Der Code wurde generiert"
|
||||
|
@ -2605,7 +2899,11 @@ _selfXssPrevention:
|
|||
warning: "WARNUNG"
|
||||
title: "„Füge in diesen Bereich etwas ein“ ist eine Betrugsmasche."
|
||||
description1: "Wenn du hier etwas einfügst, könnte ein böswilliger Benutzer dein Konto übernehmen oder deine persönlichen Daten stehlen."
|
||||
description2: "Wenn du das nicht genau verstehst, was du einfügst, %csolltest du die Eingabe abbrechen und das Fenster schließen."
|
||||
description3: "Weitere Informationen findest du hier. {link}"
|
||||
_followRequest:
|
||||
recieved: "Anfrage erhalten"
|
||||
sent: "Anfrage gesendet"
|
||||
_remoteLookupErrors:
|
||||
_federationNotAllowed:
|
||||
title: "Kommunikation mit diesem Server nicht möglich"
|
||||
|
@ -2613,10 +2911,45 @@ _remoteLookupErrors:
|
|||
_uriInvalid:
|
||||
title: "URI ist fehlerhaft"
|
||||
description: "Es gibt ein Problem mit der von dir eingegebenen URI. Bitte prüfe, ob du Zeichen eingegeben hast, die in der URI nicht verwendet werden können."
|
||||
_requestFailed:
|
||||
title: "Anfrage fehlgeschlagen"
|
||||
description: "Die Kommunikation mit diesem Server ist fehlgeschlagen. Der Server ist möglicherweise nicht erreichbar. Bitte vergewissere dich auch, dass du keine ungültige oder nicht existierende URI eingegeben hast."
|
||||
_responseInvalid:
|
||||
title: "Die Antwort ist ungültig"
|
||||
description: "Die Kommunikation mit dem Server war erfolgreich, aber die erhaltenen Daten waren nicht korrekt. Wenn du Remote-Inhalte über einen Server eines Dritten abfragst, verwende bitte erneut eine URI, die vom Ursprungsserver abgerufen werden kann."
|
||||
_noSuchObject:
|
||||
title: "Nicht gefunden"
|
||||
description: "Die angeforderte Ressource konnte nicht gefunden werden, bitte überprüfe die URI erneut."
|
||||
_captcha:
|
||||
verify: "Bitte beantworte das CAPTCHA"
|
||||
testSiteKeyMessage: "Du kannst die Vorschau prüfen, indem du die Testwerte für den Site- und Secret-Key eingibst. Weitere Informationen findest du auf der folgenden Seite."
|
||||
_error:
|
||||
_requestFailed:
|
||||
title: "CAPTCHA-Anfrage fehlgeschlagen."
|
||||
text: "Bitte probiere es später noch einmal oder überprüfe die Einstellungen erneut."
|
||||
_verificationFailed:
|
||||
title: "CAPTCHA-Prüfung fehlgeschlagen"
|
||||
text: "Bitte überprüfe nochmals, ob die Einstellungen korrekt sind."
|
||||
_unknown:
|
||||
title: "CAPTCHA-Fehler"
|
||||
text: "Es ist ein unerwarteter Fehler aufgetreten."
|
||||
_bootErrors:
|
||||
title: "Laden fehlgeschlagen"
|
||||
serverError: "Wenn das Problem nach kurzem Warten und erneutem Laden immer noch nicht behoben ist, wende dich bitte an den Serveradministrator und gib die folgende Fehler-ID an."
|
||||
solution: "Folgendes könnte das Problem lösen."
|
||||
solution1: "Aktualisiere deinen Browser und dein Betriebssystem auf die neueste Version"
|
||||
solution2: "Deaktiviere den Werbeblocker"
|
||||
solution3: "Leere den Browser-Cache"
|
||||
solution4: "(Tor Browser) Setze dom.webaudio.enabled auf true"
|
||||
otherOption: "Weitere Optionen"
|
||||
otherOption1: "Client-Einstellungen und Cache löschen"
|
||||
otherOption2: "Einfachen Client starten"
|
||||
otherOption3: "Starte das Reparaturwerkzeug"
|
||||
_search:
|
||||
searchScopeAll: "Alle"
|
||||
searchScopeLocal: "Lokal"
|
||||
searchScopeServer: "Bestimmter Server"
|
||||
searchScopeUser: "Spezifischer Benutzer"
|
||||
pleaseEnterServerHost: "Gib den Server-Host ein"
|
||||
pleaseSelectUser: "Benutzer auswählen"
|
||||
serverHostPlaceholder: "Beispiel: misskey.example.com"
|
||||
|
|
|
@ -162,14 +162,12 @@ imageUrl: "URL εικόνας"
|
|||
remove: "Διαγραφή"
|
||||
removed: "Η διαγραφή ολοκληρώθηκε επιτυχώς"
|
||||
saved: "Αποθηκεύτηκε"
|
||||
messaging: "Συνομιλία"
|
||||
upload: "Ανεβάστε"
|
||||
fromDrive: "Από τον Αποθηκευτικό Χώρο"
|
||||
fromUrl: "Από URL"
|
||||
uploadFromUrl: "Ανεβάστε από URL"
|
||||
explore: "Εξερευνήστε"
|
||||
messageRead: "Διαβάστηκε"
|
||||
startMessaging: "Ξεκινήστε μία συνομιλία"
|
||||
nUsersRead: "διαβάστηκε από {n}"
|
||||
start: "Ας αρχίσουμε"
|
||||
home: "Κεντρικό"
|
||||
|
@ -290,6 +288,9 @@ replies: "Απάντηση"
|
|||
renotes: "Κοινοποίηση σημειώματος"
|
||||
postForm: "Φόρμα δημοσίευσης"
|
||||
information: "Πληροφορίες"
|
||||
_chat:
|
||||
members: "Μέλη"
|
||||
home: "Κεντρικό"
|
||||
_email:
|
||||
_follow:
|
||||
title: "Έχετε ένα νέο ακόλουθο"
|
||||
|
@ -323,6 +324,7 @@ _permissions:
|
|||
"write:notifications": "Διαχειριστείτε τις ειδοποιήσεις σας"
|
||||
"read:pages": "Δείτε τις Σελίδες σας"
|
||||
"write:pages": "Επεξεργαστείτε ή διαγράψτε τις σελίδες σας"
|
||||
"write:chat": "Γράψτε ή διαγράψτε μηνύματα συνομιλίας"
|
||||
_antennaSources:
|
||||
all: "Όλα τα σημειώματα"
|
||||
homeTimeline: "Σημειώματα από μέλη που ακολουθείτε"
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?"
|
|||
resetAreYouSure: "Really reset?"
|
||||
areYouSure: "Are you sure?"
|
||||
saved: "Saved"
|
||||
messaging: "Chat"
|
||||
upload: "Upload"
|
||||
keepOriginalUploading: "Keep original image"
|
||||
keepOriginalUploadingDescription: "Saves the originally uploaded image as-is. If turned off, a version to display on the web will be generated on upload."
|
||||
|
@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "It may take some time until the upload is complete."
|
|||
explore: "Explore"
|
||||
messageRead: "Read"
|
||||
noMoreHistory: "There is no further history"
|
||||
startMessaging: "Start a new chat"
|
||||
startChat: "Start chat"
|
||||
nUsersRead: "read by {n}"
|
||||
agreeTo: "I agree to {0}"
|
||||
agree: "Agree"
|
||||
|
@ -346,7 +345,7 @@ emptyDrive: "Your Drive is empty"
|
|||
emptyFolder: "This folder is empty"
|
||||
unableToDelete: "Unable to delete"
|
||||
inputNewFileName: "Enter a new filename"
|
||||
inputNewDescription: "Enter new caption"
|
||||
inputNewDescription: "Enter new alt text"
|
||||
inputNewFolderName: "Enter a new folder name"
|
||||
circularReferenceFolder: "The destination folder is a subfolder of the folder you wish to move."
|
||||
hasChildFilesOrFolders: "Since this folder is not empty, it can not be deleted."
|
||||
|
@ -491,8 +490,6 @@ noteOf: "Note by {user}"
|
|||
quoteAttached: "Quote"
|
||||
quoteQuestion: "Append as quote?"
|
||||
attachAsFileQuestion: "The text in clipboard is long. Would you want to attach it as text file?"
|
||||
noMessagesYet: "No messages yet"
|
||||
newMessageExists: "There are new messages"
|
||||
onlyOneFileCanBeAttached: "You can only attach one file to a message"
|
||||
signinRequired: "Please register or sign in before continuing"
|
||||
signinOrContinueOnRemote: "To continue, you need to move your server or sign up / log in to this server."
|
||||
|
@ -646,8 +643,8 @@ disablePlayer: "Close video player"
|
|||
expandTweet: "Expand post"
|
||||
themeEditor: "Theme editor"
|
||||
description: "Description"
|
||||
describeFile: "Add caption"
|
||||
enterFileDescription: "Enter caption"
|
||||
describeFile: "Add alt text"
|
||||
enterFileDescription: "Enter alt text"
|
||||
author: "Author"
|
||||
leaveConfirm: "There are unsaved changes. Do you want to discard them?"
|
||||
manage: "Management"
|
||||
|
@ -1017,7 +1014,7 @@ sendPushNotificationReadMessageCaption: "This may increase the power consumption
|
|||
windowMaximize: "Maximize"
|
||||
windowMinimize: "Minimize"
|
||||
windowRestore: "Restore"
|
||||
caption: "Caption"
|
||||
caption: "Alt text"
|
||||
loggedInAsBot: "Currently logged in as bot"
|
||||
tools: "Tools"
|
||||
cannotLoad: "Unable to load"
|
||||
|
@ -1335,6 +1332,58 @@ emojiPalette: "Emoji palette"
|
|||
postForm: "Posting form"
|
||||
textCount: "Character count"
|
||||
information: "About"
|
||||
chat: "Chat"
|
||||
migrateOldSettings: "Migrate old client settings"
|
||||
migrateOldSettings_description: "This should be done automatically but if for some reason the migration was not successful, you can trigger the migration process yourself manually. The current configuration information will be overwritten."
|
||||
compress: "Compress"
|
||||
right: "Right"
|
||||
bottom: "Bottom"
|
||||
top: "Top"
|
||||
_chat:
|
||||
noMessagesYet: "No messages yet"
|
||||
newMessage: "New message"
|
||||
individualChat: "Private Chat"
|
||||
individualChat_description: "Have a private chat with another person."
|
||||
roomChat: "Room Chat"
|
||||
roomChat_description: "A chat room which can have multiple people.\nYou can also invite people who don't allow private chats if they accept the invite."
|
||||
createRoom: "Create Room"
|
||||
inviteUserToChat: "Invite users to start chatting"
|
||||
yourRooms: "Created rooms"
|
||||
joiningRooms: "Joined rooms"
|
||||
invitations: "Invite"
|
||||
noInvitations: "No invitations"
|
||||
history: "History"
|
||||
noHistory: "No history available"
|
||||
noRooms: "No rooms found"
|
||||
inviteUser: "Invite Users"
|
||||
sentInvitations: "Sent Invites"
|
||||
join: "Join"
|
||||
ignore: "Ignore"
|
||||
leave: "Leave room"
|
||||
members: "Members"
|
||||
searchMessages: "Search messages"
|
||||
home: "Home"
|
||||
send: "Send"
|
||||
newline: "New line"
|
||||
muteThisRoom: "Mute room"
|
||||
deleteRoom: "Delete room"
|
||||
chatNotAvailableForThisAccountOrServer: "Chat is not enabled on this server or for this account."
|
||||
chatNotAvailableInOtherAccount: "The chat function is disabled for the other user."
|
||||
cannotChatWithTheUser: "Cannot start a chat with this user"
|
||||
cannotChatWithTheUser_description: "Chat is either unavailable or the other party has not enabled chat."
|
||||
chatWithThisUser: "Chat with user"
|
||||
thisUserAllowsChatOnlyFromFollowers: "This user accepts chats from followers only."
|
||||
thisUserAllowsChatOnlyFromFollowing: "This user accepts chats only from users they follow."
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "This user only accepts chats from users who are mutual followers."
|
||||
thisUserNotAllowedChatAnyone: "This user is not accepting chats from anyone."
|
||||
chatAllowedUsers: "Who to allow chatting with"
|
||||
chatAllowedUsers_note: "You can chat with anyone to whom you have sent a chat message regardless of this setting."
|
||||
_chatAllowedUsers:
|
||||
everyone: "Everyone"
|
||||
followers: "Only your followers"
|
||||
following: "Only users you are following"
|
||||
mutual: "Mutual followers only"
|
||||
none: "Nobody"
|
||||
_emojiPalette:
|
||||
palettes: "Palette"
|
||||
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
|
||||
|
@ -1360,6 +1409,13 @@ _settings:
|
|||
timelineAndNote: "Timeline and note"
|
||||
makeEveryTextElementsSelectable: "Make all text elements selectable"
|
||||
makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations."
|
||||
useStickyIcons: "Make icons follow while scrolling"
|
||||
showNavbarSubButtons: "Show sub-buttons on the navigation bar"
|
||||
ifOn: "When turned on"
|
||||
ifOff: "When turned off"
|
||||
_chat:
|
||||
showSenderName: "Show sender's name"
|
||||
sendOnEnter: "Press Enter to send"
|
||||
_preferencesProfile:
|
||||
profileName: "Profile name"
|
||||
profileNameDescription: "Set a name that identifies this device."
|
||||
|
@ -1392,7 +1448,7 @@ _abuseUserReport:
|
|||
resolve: "Resolve"
|
||||
accept: "Accept"
|
||||
reject: "Reject"
|
||||
resolveTutorial: "If the report is legitimate in content, select \"Accept\" to mark the case as resolved in the affirmative.\nIf the content of the report is not legitimate, select \"Reject\" to mark the case as resolved in the negative."
|
||||
resolveTutorial: "If the report's content is legitimate, select \"Accept\" to mark it as resolved.\nIf the report's content is illegitimate, select \"Reject\" to ignore it."
|
||||
_delivery:
|
||||
status: "Delivery status"
|
||||
stop: "Suspended"
|
||||
|
@ -1869,6 +1925,7 @@ _role:
|
|||
canImportFollowing: "Allow importing following"
|
||||
canImportMuting: "Allow importing muting"
|
||||
canImportUserLists: "Allow importing lists"
|
||||
canChat: "Allow Chat"
|
||||
_condition:
|
||||
roleAssignedTo: "Assigned to manual roles"
|
||||
isLocal: "Local user"
|
||||
|
@ -2099,6 +2156,7 @@ _sfx:
|
|||
noteMy: "Own note"
|
||||
notification: "Notifications"
|
||||
reaction: "On choosing a reaction"
|
||||
chatMessage: "Chat Messages"
|
||||
_soundSettings:
|
||||
driveFile: "Use an audio file in Drive."
|
||||
driveFileWarn: "Select an audio file from Drive."
|
||||
|
@ -2245,6 +2303,8 @@ _permissions:
|
|||
"read:clip-favorite": "View favorited clips"
|
||||
"read:federation": "Get federation data"
|
||||
"write:report-abuse": "Report violation"
|
||||
"write:chat": "Compose or delete chat messages"
|
||||
"read:chat": "Browse Chat"
|
||||
_auth:
|
||||
shareAccessTitle: "Granting application permissions"
|
||||
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
||||
|
@ -2493,6 +2553,7 @@ _notification:
|
|||
newNote: "New note"
|
||||
unreadAntennaNote: "Antenna {name}"
|
||||
roleAssigned: "Role given"
|
||||
chatRoomInvitationReceived: "You have been invited to a chat room"
|
||||
emptyPushNotificationMessage: "Push notifications have been updated"
|
||||
achievementEarned: "Achievement unlocked"
|
||||
testNotification: "Test notification"
|
||||
|
@ -2521,6 +2582,7 @@ _notification:
|
|||
receiveFollowRequest: "Received follow requests"
|
||||
followRequestAccepted: "Accepted follow requests"
|
||||
roleAssigned: "Role given"
|
||||
chatRoomInvitationReceived: "Invited to chat room"
|
||||
achievementEarned: "Achievement unlocked"
|
||||
exportCompleted: "The export has been completed"
|
||||
login: "Sign In"
|
||||
|
@ -2534,6 +2596,9 @@ _notification:
|
|||
_deck:
|
||||
alwaysShowMainColumn: "Always show main column"
|
||||
columnAlign: "Align columns"
|
||||
columnGap: "Margin between columns"
|
||||
deckMenuPosition: "Deck menu position"
|
||||
navbarPosition: "Navigation bar position"
|
||||
addColumn: "Add column"
|
||||
newNoteNotificationSettings: "Notification setting for new notes"
|
||||
configureColumn: "Column settings"
|
||||
|
@ -2547,7 +2612,7 @@ _deck:
|
|||
newProfile: "New profile"
|
||||
deleteProfile: "Delete profile"
|
||||
introduction: "Create the perfect interface for you by arranging columns freely!"
|
||||
introduction2: "Click on the + on the right of the screen to add new colums whenever you want."
|
||||
introduction2: "Click on the + on the right of the screen to add new columns whenever you want."
|
||||
widgetsIntroduction: "Please select \"Edit widgets\" in the column menu and add a widget."
|
||||
useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
|
||||
usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
|
||||
|
@ -2598,7 +2663,7 @@ _webhookSettings:
|
|||
testRemarks: "Click the button to the right of the switch to send a test Webhook with dummy data."
|
||||
_abuseReport:
|
||||
_notificationRecipient:
|
||||
createRecipient: "Add a recipient for reports"
|
||||
createRecipient: "Add recipient for reports"
|
||||
modifyRecipient: "Edit a recipient for reports"
|
||||
recipientType: "Notification type"
|
||||
_recipientType:
|
||||
|
@ -2660,6 +2725,7 @@ _moderationLogTypes:
|
|||
deletePage: "Page deleted"
|
||||
deleteFlash: "Play deleted"
|
||||
deleteGalleryPost: "Gallery post deleted"
|
||||
deleteChatRoom: "Deleted Chat Room"
|
||||
updateProxyAccountDescription: "Update the description of the proxy account"
|
||||
_fileViewer:
|
||||
title: "File details"
|
||||
|
@ -2828,7 +2894,7 @@ _customEmojisManager:
|
|||
confirmImportEmojisTitle: "Import Emojis"
|
||||
confirmImportEmojisDescription: "Import {count} Emoji(s) received from the remote server. Please pay close attention to the license of the Emoji. Are you sure to continue?"
|
||||
_local:
|
||||
tabTitleList: "List of registered Emojis"
|
||||
tabTitleList: "Registered emojis"
|
||||
tabTitleRegister: "Emoji registration"
|
||||
_list:
|
||||
emojisNothing: "There are no registered Emojis."
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "¿Desea borrar \"{x}\"?"
|
|||
resetAreYouSure: "¿Desea reestablecer?"
|
||||
areYouSure: "¿Estás conforme?"
|
||||
saved: "Guardado"
|
||||
messaging: "Chat"
|
||||
upload: "Subir"
|
||||
keepOriginalUploading: "Mantener la imagen original"
|
||||
keepOriginalUploadingDescription: "Mantener la versión original al cargar imágenes. Si está desactivado, el navegador generará imágenes para la publicación web en el momento de recargar la página"
|
||||
|
@ -302,7 +301,6 @@ uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo."
|
|||
explore: "Explorar"
|
||||
messageRead: "Ya leído"
|
||||
noMoreHistory: "El historial se ha acabado"
|
||||
startMessaging: "Iniciar chat"
|
||||
nUsersRead: "Leído por {n} personas"
|
||||
agreeTo: "De acuerdo con {0}"
|
||||
agree: "De acuerdo."
|
||||
|
@ -491,8 +489,6 @@ noteOf: "Notas de {user}"
|
|||
quoteAttached: "Cita añadida"
|
||||
quoteQuestion: "¿Quiere añadir una cita?"
|
||||
attachAsFileQuestion: "El texto del portapapeles es demasiado grande ¿Desea adjuntarlo como archivo de texto?"
|
||||
noMessagesYet: "Aún no hay chat"
|
||||
newMessageExists: "Tienes un mensaje nuevo"
|
||||
onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje"
|
||||
signinRequired: "Iniciar sesión"
|
||||
signinOrContinueOnRemote: "Para continuar, tendrá que ir a su servidor o registrarse e iniciar sesión en este servidor"
|
||||
|
@ -1299,8 +1295,15 @@ messageToFollower: "Mensaje a seguidores"
|
|||
target: "Para"
|
||||
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
|
||||
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
|
||||
preferences: "Preferencias"
|
||||
postForm: "Formulario"
|
||||
information: "Información"
|
||||
_chat:
|
||||
invitations: "Invitar"
|
||||
noHistory: "No hay datos en el historial"
|
||||
members: "Miembros"
|
||||
home: "Inicio"
|
||||
send: "Enviar"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_accountSettings:
|
||||
|
@ -2141,6 +2144,7 @@ _permissions:
|
|||
"read:clip-favorite": "Ver los clips que me gustan"
|
||||
"read:federation": "Ver instancias federadas"
|
||||
"write:report-abuse": "Crear reportes de usuario"
|
||||
"write:chat": "Administrar chat"
|
||||
_auth:
|
||||
shareAccessTitle: "Permisos de la aplicación"
|
||||
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
|
||||
|
|
|
@ -277,7 +277,6 @@ deleteAreYouSure: "Êtes-vous sûr·e de vouloir supprimer « {x} » ?"
|
|||
resetAreYouSure: "Voulez-vous réinitialiser ?"
|
||||
areYouSure: "Êtes-vous sûr·e ?"
|
||||
saved: "Enregistré"
|
||||
messaging: "Discuter"
|
||||
upload: "Téléverser"
|
||||
keepOriginalUploading: "Garder l’image d’origine"
|
||||
keepOriginalUploadingDescription: "Conserve la version originale lors du téléchargement d'images. S'il est désactivé, le navigateur génère l'image pour la publication web lors du téléchargement."
|
||||
|
@ -290,7 +289,6 @@ uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un c
|
|||
explore: "Découvrir"
|
||||
messageRead: "Lu"
|
||||
noMoreHistory: "Il n’y a plus d’historique"
|
||||
startMessaging: "Commencer à discuter"
|
||||
nUsersRead: "Lu par {n} personnes"
|
||||
agreeTo: "J’accepte {0}"
|
||||
agree: "Accepter"
|
||||
|
@ -477,8 +475,6 @@ retype: "Confirmation"
|
|||
noteOf: "Notes de {user}"
|
||||
quoteAttached: "Avec citation"
|
||||
quoteQuestion: "Souhaitez-vous ajouter une citation ?"
|
||||
noMessagesYet: "Pas encore de discussion"
|
||||
newMessageExists: "Vous avez un nouveau message"
|
||||
onlyOneFileCanBeAttached: "Vous ne pouvez joindre qu’un seul fichier au message"
|
||||
signinRequired: "Veuillez vous connecter"
|
||||
invitations: "Invitations"
|
||||
|
@ -1279,6 +1275,12 @@ pleaseSelectAccount: "Sélectionner un compte"
|
|||
availableRoles: "Rôles disponibles"
|
||||
postForm: "Formulaire de publication"
|
||||
information: "Informations"
|
||||
_chat:
|
||||
invitations: "Inviter"
|
||||
noHistory: "Pas d'historique"
|
||||
members: "Membres"
|
||||
home: "Principal"
|
||||
send: "Envoyer"
|
||||
_abuseUserReport:
|
||||
forward: "Transférer"
|
||||
forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme."
|
||||
|
@ -1951,6 +1953,7 @@ _permissions:
|
|||
"write:admin:unsuspend-user": "Lever la suspension d'un utilisateur"
|
||||
"write:admin:meta": "Gérer les métadonnées de l'instance"
|
||||
"write:admin:roles": "Gérer les rôles"
|
||||
"write:chat": "Gérer les discussions"
|
||||
_auth:
|
||||
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
|
||||
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre compte?"
|
||||
|
|
|
@ -280,7 +280,6 @@ deleteAreYouSure: "Apakah kamu yakin ingin menghapus \"{x}\"?"
|
|||
resetAreYouSure: "Yakin mau atur ulang?"
|
||||
areYouSure: "Apakah kamu yakin?"
|
||||
saved: "Telah disimpan"
|
||||
messaging: "Pesan"
|
||||
upload: "Unggah"
|
||||
keepOriginalUploading: "Simpan gambar asli"
|
||||
keepOriginalUploadingDescription: "Simpan gambar yang diunggah sebagaimana gambar aslinya. Bila dimatikan, versi tampilan web akan dihasilkan pada saat diunggah."
|
||||
|
@ -293,7 +292,6 @@ uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesa
|
|||
explore: "Jelajahi"
|
||||
messageRead: "Telah dibaca"
|
||||
noMoreHistory: "Tidak ada sejarah lagi"
|
||||
startMessaging: "Mulai mengirim pesan"
|
||||
nUsersRead: "Dibaca oleh {n}"
|
||||
agreeTo: "Saya setuju kepada {0}"
|
||||
agree: "Setuju"
|
||||
|
@ -481,8 +479,6 @@ noteOf: "Catatan milik {user}"
|
|||
quoteAttached: "Dikutip"
|
||||
quoteQuestion: "Apakah kamu ingin menambahkan kutipan?"
|
||||
attachAsFileQuestion: "Teks dalam papan klip terlalu panjang. Apakah kamu ingin melampirkannya sebagai berkas teks?"
|
||||
noMessagesYet: "Tidak ada pesan"
|
||||
newMessageExists: "Kamu mendapatkan pesan baru"
|
||||
onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan"
|
||||
signinRequired: "Silahkan login"
|
||||
invitations: "Undangan"
|
||||
|
@ -1263,6 +1259,12 @@ thereAreNChanges: "Ada {n} perubahan"
|
|||
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
|
||||
postForm: "Buat catatan"
|
||||
information: "Informasi"
|
||||
_chat:
|
||||
invitations: "Undang"
|
||||
noHistory: "Tidak ada riwayat"
|
||||
members: "Anggota"
|
||||
home: "Beranda"
|
||||
send: "Kirim"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_abuseUserReport:
|
||||
|
@ -2109,6 +2111,7 @@ _permissions:
|
|||
"read:clip-favorite": "Lihat klip yang difavoritkan"
|
||||
"read:federation": "Mendapatkan data federasi"
|
||||
"write:report-abuse": "Melaporkan pelanggaran"
|
||||
"write:chat": "Buat atau hapus obrolan"
|
||||
_auth:
|
||||
shareAccessTitle: "Mendapatkan ijin akses aplikasi"
|
||||
shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?"
|
||||
|
|
|
@ -1182,10 +1182,6 @@ export interface Locale extends ILocale {
|
|||
* 保存しました
|
||||
*/
|
||||
"saved": string;
|
||||
/**
|
||||
* チャット
|
||||
*/
|
||||
"messaging": string;
|
||||
/**
|
||||
* アップロード
|
||||
*/
|
||||
|
@ -1235,9 +1231,9 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"noMoreHistory": string;
|
||||
/**
|
||||
* チャットを開始
|
||||
* チャットを始める
|
||||
*/
|
||||
"startMessaging": string;
|
||||
"startChat": string;
|
||||
/**
|
||||
* {n}人が読みました
|
||||
*/
|
||||
|
@ -1990,14 +1986,6 @@ export interface Locale extends ILocale {
|
|||
* クリップボードのテキストが長いです。テキストファイルとして添付しますか?
|
||||
*/
|
||||
"attachAsFileQuestion": string;
|
||||
/**
|
||||
* まだチャットはありません
|
||||
*/
|
||||
"noMessagesYet": string;
|
||||
/**
|
||||
* 新しいメッセージがあります
|
||||
*/
|
||||
"newMessageExists": string;
|
||||
/**
|
||||
* メッセージに添付できるファイルはひとつです
|
||||
*/
|
||||
|
@ -5362,6 +5350,211 @@ export interface Locale extends ILocale {
|
|||
* 情報
|
||||
*/
|
||||
"information": string;
|
||||
/**
|
||||
* チャット
|
||||
*/
|
||||
"chat": string;
|
||||
/**
|
||||
* 旧設定情報を移行
|
||||
*/
|
||||
"migrateOldSettings": string;
|
||||
/**
|
||||
* 通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。
|
||||
*/
|
||||
"migrateOldSettings_description": string;
|
||||
/**
|
||||
* 圧縮
|
||||
*/
|
||||
"compress": string;
|
||||
/**
|
||||
* 右
|
||||
*/
|
||||
"right": string;
|
||||
/**
|
||||
* 下
|
||||
*/
|
||||
"bottom": string;
|
||||
/**
|
||||
* 上
|
||||
*/
|
||||
"top": string;
|
||||
"_chat": {
|
||||
/**
|
||||
* まだメッセージはありません
|
||||
*/
|
||||
"noMessagesYet": string;
|
||||
/**
|
||||
* 新しいメッセージ
|
||||
*/
|
||||
"newMessage": string;
|
||||
/**
|
||||
* 個人チャット
|
||||
*/
|
||||
"individualChat": string;
|
||||
/**
|
||||
* 特定ユーザーとの一対一のチャットができます。
|
||||
*/
|
||||
"individualChat_description": string;
|
||||
/**
|
||||
* ルームチャット
|
||||
*/
|
||||
"roomChat": string;
|
||||
/**
|
||||
* 複数人でのチャットができます。
|
||||
* また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。
|
||||
*/
|
||||
"roomChat_description": string;
|
||||
/**
|
||||
* ルームを作成
|
||||
*/
|
||||
"createRoom": string;
|
||||
/**
|
||||
* ユーザーを招待してチャットを始めましょう
|
||||
*/
|
||||
"inviteUserToChat": string;
|
||||
/**
|
||||
* 作成したルーム
|
||||
*/
|
||||
"yourRooms": string;
|
||||
/**
|
||||
* 参加中のルーム
|
||||
*/
|
||||
"joiningRooms": string;
|
||||
/**
|
||||
* 招待
|
||||
*/
|
||||
"invitations": string;
|
||||
/**
|
||||
* 招待はありません
|
||||
*/
|
||||
"noInvitations": string;
|
||||
/**
|
||||
* 履歴
|
||||
*/
|
||||
"history": string;
|
||||
/**
|
||||
* 履歴はありません
|
||||
*/
|
||||
"noHistory": string;
|
||||
/**
|
||||
* ルームはありません
|
||||
*/
|
||||
"noRooms": string;
|
||||
/**
|
||||
* ユーザーを招待
|
||||
*/
|
||||
"inviteUser": string;
|
||||
/**
|
||||
* 送信した招待
|
||||
*/
|
||||
"sentInvitations": string;
|
||||
/**
|
||||
* 参加
|
||||
*/
|
||||
"join": string;
|
||||
/**
|
||||
* 無視
|
||||
*/
|
||||
"ignore": string;
|
||||
/**
|
||||
* ルームから退出
|
||||
*/
|
||||
"leave": string;
|
||||
/**
|
||||
* メンバー
|
||||
*/
|
||||
"members": string;
|
||||
/**
|
||||
* メッセージを検索
|
||||
*/
|
||||
"searchMessages": string;
|
||||
/**
|
||||
* ホーム
|
||||
*/
|
||||
"home": string;
|
||||
/**
|
||||
* 送信
|
||||
*/
|
||||
"send": string;
|
||||
/**
|
||||
* 改行
|
||||
*/
|
||||
"newline": string;
|
||||
/**
|
||||
* このルームをミュート
|
||||
*/
|
||||
"muteThisRoom": string;
|
||||
/**
|
||||
* ルームを削除
|
||||
*/
|
||||
"deleteRoom": string;
|
||||
/**
|
||||
* このサーバー、またはこのアカウントでチャットは有効化されていません。
|
||||
*/
|
||||
"chatNotAvailableForThisAccountOrServer": string;
|
||||
/**
|
||||
* 相手のアカウントでチャット機能が使えない状態になっています。
|
||||
*/
|
||||
"chatNotAvailableInOtherAccount": string;
|
||||
/**
|
||||
* このユーザーとのチャットを開始できません
|
||||
*/
|
||||
"cannotChatWithTheUser": string;
|
||||
/**
|
||||
* チャットが使えない状態になっているか、相手がチャットを開放していません。
|
||||
*/
|
||||
"cannotChatWithTheUser_description": string;
|
||||
/**
|
||||
* チャットする
|
||||
*/
|
||||
"chatWithThisUser": string;
|
||||
/**
|
||||
* このユーザーはフォロワーからのみチャットを受け付けています。
|
||||
*/
|
||||
"thisUserAllowsChatOnlyFromFollowers": string;
|
||||
/**
|
||||
* このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。
|
||||
*/
|
||||
"thisUserAllowsChatOnlyFromFollowing": string;
|
||||
/**
|
||||
* このユーザーは相互フォローのユーザーからのみチャットを受け付けています。
|
||||
*/
|
||||
"thisUserAllowsChatOnlyFromMutualFollowing": string;
|
||||
/**
|
||||
* このユーザーは誰からもチャットを受け付けていません。
|
||||
*/
|
||||
"thisUserNotAllowedChatAnyone": string;
|
||||
/**
|
||||
* チャットを許可する相手
|
||||
*/
|
||||
"chatAllowedUsers": string;
|
||||
/**
|
||||
* 自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。
|
||||
*/
|
||||
"chatAllowedUsers_note": string;
|
||||
"_chatAllowedUsers": {
|
||||
/**
|
||||
* 誰でも
|
||||
*/
|
||||
"everyone": string;
|
||||
/**
|
||||
* 自分のフォロワーのみ
|
||||
*/
|
||||
"followers": string;
|
||||
/**
|
||||
* 自分がフォローしているユーザーのみ
|
||||
*/
|
||||
"following": string;
|
||||
/**
|
||||
* 相互フォローのユーザーのみ
|
||||
*/
|
||||
"mutual": string;
|
||||
/**
|
||||
* 誰も許可しない
|
||||
*/
|
||||
"none": string;
|
||||
};
|
||||
};
|
||||
"_emojiPalette": {
|
||||
/**
|
||||
* パレット
|
||||
|
@ -5457,6 +5650,32 @@ export interface Locale extends ILocale {
|
|||
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
|
||||
*/
|
||||
"makeEveryTextElementsSelectable_description": string;
|
||||
/**
|
||||
* アイコンをスクロールに追従させる
|
||||
*/
|
||||
"useStickyIcons": string;
|
||||
/**
|
||||
* ナビゲーションバーに副ボタンを表示
|
||||
*/
|
||||
"showNavbarSubButtons": string;
|
||||
/**
|
||||
* オンのとき
|
||||
*/
|
||||
"ifOn": string;
|
||||
/**
|
||||
* オフのとき
|
||||
*/
|
||||
"ifOff": string;
|
||||
"_chat": {
|
||||
/**
|
||||
* 送信者の名前を表示
|
||||
*/
|
||||
"showSenderName": string;
|
||||
/**
|
||||
* Enterで送信
|
||||
*/
|
||||
"sendOnEnter": string;
|
||||
};
|
||||
};
|
||||
"_preferencesProfile": {
|
||||
/**
|
||||
|
@ -7289,6 +7508,10 @@ export interface Locale extends ILocale {
|
|||
* リストのインポートを許可
|
||||
*/
|
||||
"canImportUserLists": string;
|
||||
/**
|
||||
* チャットを許可
|
||||
*/
|
||||
"canChat": string;
|
||||
};
|
||||
"_condition": {
|
||||
/**
|
||||
|
@ -8158,6 +8381,10 @@ export interface Locale extends ILocale {
|
|||
* リアクション選択時
|
||||
*/
|
||||
"reaction": string;
|
||||
/**
|
||||
* チャットのメッセージ
|
||||
*/
|
||||
"chatMessage": string;
|
||||
};
|
||||
"_soundSettings": {
|
||||
/**
|
||||
|
@ -8730,6 +8957,14 @@ export interface Locale extends ILocale {
|
|||
* 違反を報告する
|
||||
*/
|
||||
"write:report-abuse": string;
|
||||
/**
|
||||
* チャットを操作する
|
||||
*/
|
||||
"write:chat": string;
|
||||
/**
|
||||
* チャットを閲覧する
|
||||
*/
|
||||
"read:chat": string;
|
||||
};
|
||||
"_auth": {
|
||||
/**
|
||||
|
@ -9680,6 +9915,10 @@ export interface Locale extends ILocale {
|
|||
* ロールが付与されました
|
||||
*/
|
||||
"roleAssigned": string;
|
||||
/**
|
||||
* チャットルームへ招待されました
|
||||
*/
|
||||
"chatRoomInvitationReceived": string;
|
||||
/**
|
||||
* プッシュ通知の更新をしました
|
||||
*/
|
||||
|
@ -9789,6 +10028,10 @@ export interface Locale extends ILocale {
|
|||
* ロールが付与された
|
||||
*/
|
||||
"roleAssigned": string;
|
||||
/**
|
||||
* チャットルームへ招待された
|
||||
*/
|
||||
"chatRoomInvitationReceived": string;
|
||||
/**
|
||||
* 実績の獲得
|
||||
*/
|
||||
|
@ -9838,6 +10081,18 @@ export interface Locale extends ILocale {
|
|||
* カラムの寄せ
|
||||
*/
|
||||
"columnAlign": string;
|
||||
/**
|
||||
* カラム間のマージン
|
||||
*/
|
||||
"columnGap": string;
|
||||
/**
|
||||
* デッキメニューの位置
|
||||
*/
|
||||
"deckMenuPosition": string;
|
||||
/**
|
||||
* ナビゲーションバーの位置
|
||||
*/
|
||||
"navbarPosition": string;
|
||||
/**
|
||||
* カラムを追加
|
||||
*/
|
||||
|
@ -9891,7 +10146,7 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"introduction": string;
|
||||
/**
|
||||
* 画面の右にある + を押して、いつでもカラムを追加できます。
|
||||
* カラムを追加するには、画面の + をクリックします。
|
||||
*/
|
||||
"introduction2": string;
|
||||
/**
|
||||
|
@ -10318,6 +10573,10 @@ export interface Locale extends ILocale {
|
|||
* ギャラリーの投稿を削除
|
||||
*/
|
||||
"deleteGalleryPost": string;
|
||||
/**
|
||||
* チャットルームを削除
|
||||
*/
|
||||
"deleteChatRoom": string;
|
||||
/**
|
||||
* プロキシアカウントの説明を更新
|
||||
*/
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "Vuoi davvero eliminare \"{x}\"?"
|
|||
resetAreYouSure: "Ripristinare?"
|
||||
areYouSure: "Confermi?"
|
||||
saved: "Salvato"
|
||||
messaging: "Messaggi"
|
||||
upload: "Carica"
|
||||
keepOriginalUploading: "Conservare l'immagine originale."
|
||||
keepOriginalUploadingDescription: "Conserva la versione originale quando si caricano le immagini. Se è disattivato, il browser genera l'immagine per la pubblicazione sul Web durante il caricamento."
|
||||
|
@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo."
|
|||
explore: "Esplora"
|
||||
messageRead: "Visualizzato"
|
||||
noMoreHistory: "Non c'è più cronologia da visualizzare"
|
||||
startMessaging: "Nuovo messaggio"
|
||||
startChat: "Inizia a chattare"
|
||||
nUsersRead: "Letto da {n} persone"
|
||||
agreeTo: "Sono d'accordo con {0}"
|
||||
agree: "Accetto"
|
||||
|
@ -491,8 +490,6 @@ noteOf: "Note di {user}"
|
|||
quoteAttached: "Citazione allegata"
|
||||
quoteQuestion: "Vuoi aggiungere una citazione?"
|
||||
attachAsFileQuestion: "Il testo copiato eccede le dimensioni, vuoi allegarlo?"
|
||||
noMessagesYet: "Ancora nessuna chat"
|
||||
newMessageExists: "Hai ricevuto un nuovo messaggio"
|
||||
onlyOneFileCanBeAttached: "È possibile allegare al messaggio soltanto uno file"
|
||||
signinRequired: "Occorre avere un profilo registrato su questa istanza"
|
||||
signinOrContinueOnRemote: "Per continuare, devi accedere alla tua istanza o registrarti su questa e poi accedere"
|
||||
|
@ -1335,6 +1332,55 @@ emojiPalette: "Tavolozza emoji"
|
|||
postForm: "Finestra di pubblicazione"
|
||||
textCount: "Il numero di caratteri"
|
||||
information: "Informazioni"
|
||||
chat: "Chat"
|
||||
migrateOldSettings: "Migrare le vecchie impostazioni"
|
||||
migrateOldSettings_description: "Di solito, viene fatto automaticamente. Se per qualche motivo non fossero migrate con successo, è possibile avviare il processo di migrazione manualmente, sovrascrivendo le configurazioni attuali."
|
||||
compress: "Comprimi"
|
||||
_chat:
|
||||
noMessagesYet: "Ancora nessun messaggio"
|
||||
newMessage: "Nuovo messaggio"
|
||||
individualChat: "Chat individuale"
|
||||
individualChat_description: "Puoi chattare con una persona specifica."
|
||||
roomChat: "Stanza di chat"
|
||||
roomChat_description: "Puoi chattare con più persone.\nInoltre, anche le persone che non consentono chat personalizzate possono chattare se gli altri accettano."
|
||||
createRoom: "Crea stanza"
|
||||
inviteUserToChat: "Invita a chattare altre persone"
|
||||
yourRooms: "Le tue stanze"
|
||||
joiningRooms: "Stanze a cui partecipi"
|
||||
invitations: "Invita"
|
||||
noInvitations: "Nessun invito"
|
||||
history: "Cronologia"
|
||||
noHistory: "Nessuna cronologia"
|
||||
noRooms: "Nessuna stanza"
|
||||
inviteUser: "Invita"
|
||||
sentInvitations: "Inviti spediti"
|
||||
join: "Entra"
|
||||
ignore: "Ignora"
|
||||
leave: "Esci"
|
||||
members: "Membri"
|
||||
searchMessages: "Cerca messaggi"
|
||||
home: "Home"
|
||||
send: "Inviare"
|
||||
newline: "Nuova riga"
|
||||
muteThisRoom: "Silenzia stanza"
|
||||
deleteRoom: "Elimina stanza"
|
||||
chatNotAvailableForThisAccountOrServer: "Questo server, o questo profilo ha disabilitato la chat."
|
||||
chatNotAvailableInOtherAccount: "La chat non è disponibile nel profilo dell'altra persona."
|
||||
cannotChatWithTheUser: "Impossibile chattare con questa persona"
|
||||
cannotChatWithTheUser_description: "La chat potrebbe non essere disponibile, oppure l'altra persona potrebbe non esserlo."
|
||||
chatWithThisUser: "Chatta con questa persona"
|
||||
thisUserAllowsChatOnlyFromFollowers: "Questa persona permette di chattare soltanto i propri Follower."
|
||||
thisUserAllowsChatOnlyFromFollowing: "Questa persona permette di chattare soltanto ai suoi Follow."
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "Questa persona permette di chattare solo a relazioni reciproche."
|
||||
thisUserNotAllowedChatAnyone: "Questa persona non permette di chattare a nessuno."
|
||||
chatAllowedUsers: "Persone ammesse alla chat"
|
||||
chatAllowedUsers_note: "Puoi chattare con le persone a cui hai già inviato un messaggio, indipendentemente da questa impostazione."
|
||||
_chatAllowedUsers:
|
||||
everyone: "Chiunque"
|
||||
followers: "Solo i tuoi Follower"
|
||||
following: "Solo i tuoi Follow"
|
||||
mutual: "Solo relazioni reciproche"
|
||||
none: "Nessuno"
|
||||
_emojiPalette:
|
||||
palettes: "Tavolozza"
|
||||
enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
|
||||
|
@ -1360,6 +1406,12 @@ _settings:
|
|||
timelineAndNote: "Note e Timeline"
|
||||
makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile"
|
||||
makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni."
|
||||
showNavbarSubButtons: "Mostra i pulsanti secondari nella barra di navigazione"
|
||||
ifOn: "Quando attivato"
|
||||
ifOff: "Quando disattivato"
|
||||
_chat:
|
||||
showSenderName: "Mostra il nome del mittente"
|
||||
sendOnEnter: "Invio spedisce"
|
||||
_preferencesProfile:
|
||||
profileName: "Nome del profilo"
|
||||
profileNameDescription: "Impostare il nome che indentifica questo dispositivo."
|
||||
|
@ -1869,6 +1921,7 @@ _role:
|
|||
canImportFollowing: "Può importare Following"
|
||||
canImportMuting: "Può importare Silenziati"
|
||||
canImportUserLists: "Può importare liste di Profili"
|
||||
canChat: "Chat consentita"
|
||||
_condition:
|
||||
roleAssignedTo: "Assegnato a ruoli manualmente"
|
||||
isLocal: "Profilo locale"
|
||||
|
@ -2099,6 +2152,7 @@ _sfx:
|
|||
noteMy: "Mia nota"
|
||||
notification: "Notifiche"
|
||||
reaction: "Quando seleziono una reazione"
|
||||
chatMessage: "Messaggio di chat"
|
||||
_soundSettings:
|
||||
driveFile: "Suoni del Drive"
|
||||
driveFileWarn: "Seleziona file dal dispositivo"
|
||||
|
@ -2245,6 +2299,8 @@ _permissions:
|
|||
"read:clip-favorite": "Vedere Clip preferite"
|
||||
"read:federation": "Vedere la federazione"
|
||||
"write:report-abuse": "Inviare segnalazioni"
|
||||
"write:chat": "Gestire la chat"
|
||||
"read:chat": "Visualizzare le chat"
|
||||
_auth:
|
||||
shareAccessTitle: "Permessi dell'applicazione"
|
||||
shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?"
|
||||
|
@ -2493,6 +2549,7 @@ _notification:
|
|||
newNote: "Nuove Note"
|
||||
unreadAntennaNote: "Antenna {name}"
|
||||
roleAssigned: "Ruolo assegnato"
|
||||
chatRoomInvitationReceived: "Invito in una stanza di chat"
|
||||
emptyPushNotificationMessage: "Le notifiche push sono state aggiornate."
|
||||
achievementEarned: "Obiettivo raggiunto"
|
||||
testNotification: "Provare la notifica"
|
||||
|
@ -2521,6 +2578,7 @@ _notification:
|
|||
receiveFollowRequest: "Richieste di follow in arrivo"
|
||||
followRequestAccepted: "Richieste di follow accettate"
|
||||
roleAssigned: "Ruolo concesso"
|
||||
chatRoomInvitationReceived: "Invito in una stanza di chat"
|
||||
achievementEarned: "Risultato raggiunto"
|
||||
exportCompleted: "Esportazione completata"
|
||||
login: "Accessi"
|
||||
|
@ -2660,6 +2718,7 @@ _moderationLogTypes:
|
|||
deletePage: "Pagina eliminata"
|
||||
deleteFlash: "Play eliminato"
|
||||
deleteGalleryPost: "Eliminazione pubblicazione nella Galleria"
|
||||
deleteChatRoom: "Elimina chat"
|
||||
updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy"
|
||||
_fileViewer:
|
||||
title: "Dettagli del file"
|
||||
|
|
|
@ -291,7 +291,6 @@ deleteAreYouSure: "「{x}」を削除しますか?"
|
|||
resetAreYouSure: "リセットしますか?"
|
||||
areYouSure: "よろしいですか?"
|
||||
saved: "保存しました"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
keepOriginalUploading: "オリジナル画像を保持"
|
||||
keepOriginalUploadingDescription: "画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。"
|
||||
|
@ -304,7 +303,7 @@ uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がか
|
|||
explore: "みつける"
|
||||
messageRead: "既読"
|
||||
noMoreHistory: "これより過去の履歴はありません"
|
||||
startMessaging: "チャットを開始"
|
||||
startChat: "チャットを始める"
|
||||
nUsersRead: "{n}人が読みました"
|
||||
agreeTo: "{0}に同意"
|
||||
agree: "同意する"
|
||||
|
@ -493,8 +492,6 @@ noteOf: "{user}のノート"
|
|||
quoteAttached: "引用付き"
|
||||
quoteQuestion: "引用として添付しますか?"
|
||||
attachAsFileQuestion: "クリップボードのテキストが長いです。テキストファイルとして添付しますか?"
|
||||
noMessagesYet: "まだチャットはありません"
|
||||
newMessageExists: "新しいメッセージがあります"
|
||||
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
|
||||
signinRequired: "続行する前に、登録またはログインが必要です"
|
||||
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります"
|
||||
|
@ -1336,6 +1333,59 @@ emojiPalette: "絵文字パレット"
|
|||
postForm: "投稿フォーム"
|
||||
textCount: "文字数"
|
||||
information: "情報"
|
||||
chat: "チャット"
|
||||
migrateOldSettings: "旧設定情報を移行"
|
||||
migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。"
|
||||
compress: "圧縮"
|
||||
right: "右"
|
||||
bottom: "下"
|
||||
top: "上"
|
||||
|
||||
_chat:
|
||||
noMessagesYet: "まだメッセージはありません"
|
||||
newMessage: "新しいメッセージ"
|
||||
individualChat: "個人チャット"
|
||||
individualChat_description: "特定ユーザーとの一対一のチャットができます。"
|
||||
roomChat: "ルームチャット"
|
||||
roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。"
|
||||
createRoom: "ルームを作成"
|
||||
inviteUserToChat: "ユーザーを招待してチャットを始めましょう"
|
||||
yourRooms: "作成したルーム"
|
||||
joiningRooms: "参加中のルーム"
|
||||
invitations: "招待"
|
||||
noInvitations: "招待はありません"
|
||||
history: "履歴"
|
||||
noHistory: "履歴はありません"
|
||||
noRooms: "ルームはありません"
|
||||
inviteUser: "ユーザーを招待"
|
||||
sentInvitations: "送信した招待"
|
||||
join: "参加"
|
||||
ignore: "無視"
|
||||
leave: "ルームから退出"
|
||||
members: "メンバー"
|
||||
searchMessages: "メッセージを検索"
|
||||
home: "ホーム"
|
||||
send: "送信"
|
||||
newline: "改行"
|
||||
muteThisRoom: "このルームをミュート"
|
||||
deleteRoom: "ルームを削除"
|
||||
chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。"
|
||||
chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。"
|
||||
cannotChatWithTheUser: "このユーザーとのチャットを開始できません"
|
||||
cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。"
|
||||
chatWithThisUser: "チャットする"
|
||||
thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。"
|
||||
thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。"
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみチャットを受け付けています。"
|
||||
thisUserNotAllowedChatAnyone: "このユーザーは誰からもチャットを受け付けていません。"
|
||||
chatAllowedUsers: "チャットを許可する相手"
|
||||
chatAllowedUsers_note: "自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。"
|
||||
_chatAllowedUsers:
|
||||
everyone: "誰でも"
|
||||
followers: "自分のフォロワーのみ"
|
||||
following: "自分がフォローしているユーザーのみ"
|
||||
mutual: "相互フォローのユーザーのみ"
|
||||
none: "誰も許可しない"
|
||||
|
||||
_emojiPalette:
|
||||
palettes: "パレット"
|
||||
|
@ -1363,6 +1413,14 @@ _settings:
|
|||
timelineAndNote: "タイムラインとノート"
|
||||
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
|
||||
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
|
||||
useStickyIcons: "アイコンをスクロールに追従させる"
|
||||
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
|
||||
ifOn: "オンのとき"
|
||||
ifOff: "オフのとき"
|
||||
|
||||
_chat:
|
||||
showSenderName: "送信者の名前を表示"
|
||||
sendOnEnter: "Enterで送信"
|
||||
|
||||
_preferencesProfile:
|
||||
profileName: "プロファイル名"
|
||||
|
@ -1887,6 +1945,7 @@ _role:
|
|||
canImportFollowing: "フォローのインポートを許可"
|
||||
canImportMuting: "ミュートのインポートを許可"
|
||||
canImportUserLists: "リストのインポートを許可"
|
||||
canChat: "チャットを許可"
|
||||
_condition:
|
||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||
isLocal: "ローカルユーザー"
|
||||
|
@ -2140,6 +2199,7 @@ _sfx:
|
|||
noteMy: "ノート(自分)"
|
||||
notification: "通知"
|
||||
reaction: "リアクション選択時"
|
||||
chatMessage: "チャットのメッセージ"
|
||||
|
||||
_soundSettings:
|
||||
driveFile: "ドライブの音声を使用"
|
||||
|
@ -2292,6 +2352,8 @@ _permissions:
|
|||
"read:clip-favorite": "クリップのいいねを見る"
|
||||
"read:federation": "連合に関する情報を取得する"
|
||||
"write:report-abuse": "違反を報告する"
|
||||
"write:chat": "チャットを操作する"
|
||||
"read:chat": "チャットを閲覧する"
|
||||
|
||||
_auth:
|
||||
shareAccessTitle: "アプリへのアクセス許可"
|
||||
|
@ -2558,6 +2620,7 @@ _notification:
|
|||
newNote: "新しい投稿"
|
||||
unreadAntennaNote: "アンテナ {name}"
|
||||
roleAssigned: "ロールが付与されました"
|
||||
chatRoomInvitationReceived: "チャットルームへ招待されました"
|
||||
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
|
||||
achievementEarned: "実績を獲得"
|
||||
testNotification: "通知テスト"
|
||||
|
@ -2587,6 +2650,7 @@ _notification:
|
|||
receiveFollowRequest: "フォロー申請を受け取った"
|
||||
followRequestAccepted: "フォローが受理された"
|
||||
roleAssigned: "ロールが付与された"
|
||||
chatRoomInvitationReceived: "チャットルームへ招待された"
|
||||
achievementEarned: "実績の獲得"
|
||||
exportCompleted: "エクスポートが完了した"
|
||||
login: "ログイン"
|
||||
|
@ -2602,6 +2666,9 @@ _notification:
|
|||
_deck:
|
||||
alwaysShowMainColumn: "常にメインカラムを表示"
|
||||
columnAlign: "カラムの寄せ"
|
||||
columnGap: "カラム間のマージン"
|
||||
deckMenuPosition: "デッキメニューの位置"
|
||||
navbarPosition: "ナビゲーションバーの位置"
|
||||
addColumn: "カラムを追加"
|
||||
newNoteNotificationSettings: "新着ノート通知の設定"
|
||||
configureColumn: "カラムの設定"
|
||||
|
@ -2615,7 +2682,7 @@ _deck:
|
|||
newProfile: "新規プロファイル"
|
||||
deleteProfile: "プロファイルを削除"
|
||||
introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!"
|
||||
introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。"
|
||||
introduction2: "カラムを追加するには、画面の + をクリックします。"
|
||||
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください"
|
||||
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
|
||||
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
|
||||
|
@ -2735,6 +2802,7 @@ _moderationLogTypes:
|
|||
deletePage: "ページを削除"
|
||||
deleteFlash: "Playを削除"
|
||||
deleteGalleryPost: "ギャラリーの投稿を削除"
|
||||
deleteChatRoom: "チャットルームを削除"
|
||||
updateProxyAccountDescription: "プロキシアカウントの説明を更新"
|
||||
|
||||
_fileViewer:
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "「{x}」はほかしてええか?"
|
|||
resetAreYouSure: "リセットしてええん?"
|
||||
areYouSure: "いいん?"
|
||||
saved: "保存したで!"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
keepOriginalUploading: "オリジナル画像のまんま"
|
||||
keepOriginalUploadingDescription: "画像を上げるときにオリジナル版のまんまにするで。オフにしたら、上げたときにブラウザでWeb公開用の画像を生成するで。 "
|
||||
|
@ -302,7 +301,6 @@ uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間か
|
|||
explore: "みつける"
|
||||
messageRead: "もう読んだ"
|
||||
noMoreHistory: "これより昔のんはあらへんで"
|
||||
startMessaging: "チャットやるで"
|
||||
nUsersRead: "{n}人が読んでもうた"
|
||||
agreeTo: "{0}に同意したで"
|
||||
agree: "せやな"
|
||||
|
@ -491,8 +489,6 @@ noteOf: "{user}はんのノート"
|
|||
quoteAttached: "引用付いとるで"
|
||||
quoteQuestion: "引用として添付してもええか?"
|
||||
attachAsFileQuestion: "クリップボードのテキストが長すぎるからテキストファイルとして添付してもええか?"
|
||||
noMessagesYet: "まだチャットはあらへんで"
|
||||
newMessageExists: "新しいメッセージがきたで"
|
||||
onlyOneFileCanBeAttached: "ごめんな、メッセージに添付できるファイルはひとつだけなんよ。"
|
||||
signinRequired: "ログインしてくれへん?"
|
||||
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があるで"
|
||||
|
@ -1313,6 +1309,12 @@ confirmOnReact: "ツッコむときに確認とる"
|
|||
reactAreYouSure: "\" {emoji} \" でツッコむ?"
|
||||
postForm: "投稿フォーム"
|
||||
information: "情報"
|
||||
_chat:
|
||||
invitations: "来てや"
|
||||
noHistory: "履歴はないわ。"
|
||||
members: "メンバーはん"
|
||||
home: "ホーム"
|
||||
send: "送信"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_accountSettings:
|
||||
|
@ -2185,6 +2187,7 @@ _permissions:
|
|||
"read:clip-favorite": "クリップのいいね見る"
|
||||
"read:federation": "連合の情報取得"
|
||||
"write:report-abuse": "違反報告"
|
||||
"write:chat": "チャットを操作するで"
|
||||
_auth:
|
||||
shareAccessTitle: "アプリへのアクセス許してやったらどうや"
|
||||
shareAccess: "「{name}」がアカウントにアクセスすることを許可してええか?"
|
||||
|
|
|
@ -263,7 +263,6 @@ deleteAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?"
|
|||
resetAreYouSure: "아시로 데돌립니꺼?"
|
||||
areYouSure: "갠찮십니꺼?"
|
||||
saved: "저장햇십니다"
|
||||
messaging: "대화"
|
||||
upload: "올리기"
|
||||
keepOriginalUploading: "온본 두기"
|
||||
keepOriginalUploadingDescription: "이미지럴 올릴 때 온본얼 고대로 둡니다. 꺼모 올릴 때 브라우저서 웹 공개 이미지럴 맨겁니다."
|
||||
|
@ -276,7 +275,6 @@ uploadFromUrlMayTakeTime: "올리기가 껕날라먼 시간이 쪼매 걸릴 깁
|
|||
explore: "살펴보기"
|
||||
messageRead: "이럿어예"
|
||||
noMoreHistory: "요카마 옛날 기록이 어ᇝ십니다"
|
||||
startMessaging: "대화하기"
|
||||
nUsersRead: "{n}멩이 이럿십니다"
|
||||
agreeTo: "{0}에 동이하기"
|
||||
agree: "동이합니다"
|
||||
|
@ -457,8 +455,6 @@ retype: "다시 서기"
|
|||
noteOf: "{user}님으 노트"
|
||||
quoteAttached: "따옴"
|
||||
quoteQuestion: "따와가 작성하겠십니까?"
|
||||
noMessagesYet: "아직 대화가 없십니다"
|
||||
newMessageExists: "새 메시지가 있십니다"
|
||||
onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니다"
|
||||
invitations: "초대하기"
|
||||
invitationCode: "초대장"
|
||||
|
@ -656,6 +652,11 @@ renotes: "리노트"
|
|||
attach: "옇기"
|
||||
surrender: "아이예"
|
||||
information: "정보"
|
||||
_chat:
|
||||
invitations: "초대하기"
|
||||
noHistory: "기록이 없십니다"
|
||||
members: "구성원"
|
||||
home: "덜머리"
|
||||
_delivery:
|
||||
stop: "고만 보내예"
|
||||
_type:
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
|||
resetAreYouSure: "초기화 하시겠습니까?"
|
||||
areYouSure: "계속 진행하시겠습니까?"
|
||||
saved: "저장했습니다"
|
||||
messaging: "대화"
|
||||
upload: "업로드"
|
||||
keepOriginalUploading: "원본 이미지를 유지"
|
||||
keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서 웹 공개용 이미지를 생성합니다."
|
||||
|
@ -302,7 +301,6 @@ uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될
|
|||
explore: "둘러보기"
|
||||
messageRead: "읽음"
|
||||
noMoreHistory: "이것보다 과거의 기록이 없습니다"
|
||||
startMessaging: "대화 시작하기"
|
||||
nUsersRead: "{n}명이 읽음"
|
||||
agreeTo: "{0}에 동의"
|
||||
agree: "동의합니다"
|
||||
|
@ -491,8 +489,6 @@ noteOf: "{user}의 노트"
|
|||
quoteAttached: "인용함"
|
||||
quoteQuestion: "인용해서 작성하시겠습니까?"
|
||||
attachAsFileQuestion: "붙여넣으려는 글이 너무 깁니다. 텍스트 파일로 첨부하시겠습니까?"
|
||||
noMessagesYet: "아직 대화가 없습니다"
|
||||
newMessageExists: "새 메시지가 있습니다"
|
||||
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
||||
signinRequired: "진행하기 전에 로그인을 해 주세요"
|
||||
signinOrContinueOnRemote: "계속하려면 사용하는 서버로 이동하거나 이 서버에 로그인해야 합니다."
|
||||
|
@ -698,6 +694,7 @@ userSaysSomethingAbout: "{name}님이 \"{word}\"를 언급했습니다."
|
|||
makeActive: "활성화"
|
||||
display: "보기"
|
||||
copy: "복사"
|
||||
copiedToClipboard: "클립보드에 복사되었습니다."
|
||||
metrics: "통계"
|
||||
overview: "요약"
|
||||
logs: "로그"
|
||||
|
@ -1294,7 +1291,7 @@ thereAreNChanges: "{n}건 변경이 있습니다."
|
|||
signinWithPasskey: "패스키로 로그인"
|
||||
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
|
||||
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
|
||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
|
||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "입력된 패스키는 정상적이나, 비밀번호 없이 로그인 하는 기능이 비활성화 되어있습니다."
|
||||
messageToFollower: "팔로워에게 보낼 메시지"
|
||||
target: "대상"
|
||||
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
|
||||
|
@ -1325,21 +1322,46 @@ skip: "건너뛰기"
|
|||
restore: "복원"
|
||||
syncBetweenDevices: "장치간 동기화"
|
||||
preferenceSyncConflictTitle: "서버에 설정값이 존재합니다."
|
||||
preferenceSyncConflictText: "동기화를 활성화 한 항목의 설정 값은 서버에 저장되지만, 해당 항목은 이미 서버에 설정 값이 저장되어져 있습니다. 어느 쪽의 설정 값을 덮어씌울까요?"
|
||||
preferenceSyncConflictChoiceServer: "서버 설정값"
|
||||
preferenceSyncConflictChoiceDevice: "장치 설정값"
|
||||
preferenceSyncConflictChoiceCancel: "동기화 취소"
|
||||
paste: "붙여넣기"
|
||||
emojiPalette: "이모지 팔레트"
|
||||
postForm: "글 입력란"
|
||||
textCount: "문자 수"
|
||||
information: "정보"
|
||||
_chat:
|
||||
invitations: "초대"
|
||||
noHistory: "기록이 없습니다"
|
||||
members: "멤버"
|
||||
home: "홈"
|
||||
send: "전송"
|
||||
_emojiPalette:
|
||||
palettes: "팔레트"
|
||||
enableSyncBetweenDevicesForPalettes: "팔레트의 디바이스 간 동기화를 활성화"
|
||||
paletteForMain: "메인으로 사용할 팔레트"
|
||||
paletteForReaction: "리액션으로 사용할 팔레트"
|
||||
_settings:
|
||||
driveBanner: "드라이브 관리, 사용량 확인, 파일 업로드에 관한 설정을 합니다."
|
||||
pluginBanner: "플러그인을 사용하면 클라이언트 기능을 확장할 수 있습니다. 플러그인 설치와 개별적인 설정을 합니다."
|
||||
notificationsBanner: "서버에서 받는 알림의 종류 및 범위, 푸시 알림 설정을 합니다."
|
||||
api: "API"
|
||||
webhook: "Webhook"
|
||||
serviceConnection: "서비스 연동"
|
||||
serviceConnectionBanner: "외부 앱, 서비스와 연결하기 위한 액세스 토큰과 웹 훅 관리 설정을 합니다."
|
||||
accountData: "계정 데이터"
|
||||
accountDataBanner: "계정 데이터의 아카이브를 추출하기/가져오기 하여 관리할 수 있습니다."
|
||||
muteAndBlockBanner: "숨길 컨텐츠의 설정과, 특정 유저의 리액션을 제한하는 설정을 관리합니다."
|
||||
accessibilityBanner: "좀 더 쾌적하게 사용할 수 있도록 클라이언트의 시각 및 움직임에 관한 개인화 설정을 합니다."
|
||||
privacyBanner: "컨텐츠, 계정의 발견 범위, 팔로우 승인제 등의 계정의 프라이버시에 관한 설정을 합니다."
|
||||
securityBanner: "비밀번호, 로그인 방법, OTP, 패스 키 등의 계정의 보안에 관련된 설정을 합니다."
|
||||
preferencesBanner: "취향에 알맞는 클라이언트의 전체적인 동작을 설정합니다."
|
||||
appearanceBanner: "취향에 알맞는 클라이언트의 디스플레이, 표시 방법에 관한 설정을 합니다."
|
||||
soundsBanner: "클라이언트에서 재생할 소리에 대한 설정을 합니다."
|
||||
timelineAndNote: "타임라인과 노트"
|
||||
makeEveryTextElementsSelectable: "모든 텍스트 요소를 선택할 수 있도록 함"
|
||||
makeEveryTextElementsSelectable_description: "활성화 시, 일부 동작에서 사용자의 접근성이 나빠질 수도 있습니다."
|
||||
_preferencesProfile:
|
||||
profileName: "프로필 이름"
|
||||
profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요."
|
||||
|
@ -1363,6 +1385,7 @@ _accountSettings:
|
|||
makeNotesHiddenBefore: "과거 노트 비공개로 전환하기"
|
||||
makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
|
||||
mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다."
|
||||
mayNotEffectSomeSituations: "여기서 설정하는 제한은 모더레이션이나 리모트 서버에서 볼 때 등 일부 환경에서는 적용되지 않을 수도 있습니다."
|
||||
notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트"
|
||||
notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트"
|
||||
_abuseUserReport:
|
||||
|
@ -2224,6 +2247,7 @@ _permissions:
|
|||
"read:clip-favorite": "클립의 좋아요 보기"
|
||||
"read:federation": "연합 정보 불러오기"
|
||||
"write:report-abuse": "위반 내용 신고하기"
|
||||
"write:chat": "대화를 시작하거나 메시지를 보냅니다"
|
||||
_auth:
|
||||
shareAccessTitle: "어플리케이션의 접근 허가"
|
||||
shareAccess: "‘{name}’에서 계정에 접근하는 것을 허용하시겠습니까?"
|
||||
|
@ -2503,6 +2527,7 @@ _notification:
|
|||
achievementEarned: "도전 과제 획득"
|
||||
exportCompleted: "추출을 성공함"
|
||||
login: "로그인"
|
||||
createToken: "액세스 토큰 만들기"
|
||||
test: "알림 테스트"
|
||||
app: "연동된 앱을 통한 알림"
|
||||
_actions:
|
||||
|
@ -2530,6 +2555,7 @@ _deck:
|
|||
useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기"
|
||||
usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다"
|
||||
flexible: "폭 자동 조정"
|
||||
enableSyncBetweenDevicesForProfiles: "프로파일 정보의 디바이스 간 동기화를 활성화"
|
||||
_columns:
|
||||
main: "메인"
|
||||
widgets: "위젯"
|
||||
|
|
|
@ -223,7 +223,6 @@ remove: "ລຶບ"
|
|||
removed: "ລຶບແລ້ວ"
|
||||
resetAreYouSure: "ຣີເຊັດບໍ?"
|
||||
saved: "ບັນທຶກແລ້ວ"
|
||||
messaging: "ແຊັຕ"
|
||||
upload: "ອັບໂຫຼດ"
|
||||
keepOriginalUploading: "ຮັກສາຮູບພາບຕົ້ນສະບັບ"
|
||||
fromDrive: "ຈາກ Drive"
|
||||
|
@ -233,7 +232,6 @@ uploadFromUrlDescription: "URL ຂອງໄຟລ໌ທີ່ທ່ານຕ້
|
|||
uploadFromUrlRequested: "ຮ້ອງຂໍການອັບໂຫລດແລ້ວ"
|
||||
explore: "ສຳຫຼວດ"
|
||||
messageRead: "ອ່ານແລ້ວ"
|
||||
startMessaging: "ເລີ່ມການສົນທະນາໃໝ່"
|
||||
nUsersRead: "ອ່ານໂດຍ {n}"
|
||||
agree: "ຍອມຮັບ"
|
||||
termsOfService: "ເງື່ອນໄຂການບໍລິການ"
|
||||
|
@ -395,6 +393,11 @@ file: "ໄຟລ໌"
|
|||
replies: "ຕອບກັບ"
|
||||
renotes: "Renote"
|
||||
information: "ກ່ຽວກັບ"
|
||||
_chat:
|
||||
invitations: "ເຊີນ"
|
||||
noHistory: "ບໍ່ມີປະຫວັດ"
|
||||
members: "ສະມາຊິກ"
|
||||
home: "ໜ້າຫຼັກ"
|
||||
_delivery:
|
||||
stop: "ໂຈະ"
|
||||
_type:
|
||||
|
|
|
@ -256,7 +256,6 @@ removeAreYouSure: "Weet je zeker dat je \"{x}\" wil verwijderen?"
|
|||
deleteAreYouSure: "Weet je zeker dat je \"{x}\" wil verwijderen?"
|
||||
resetAreYouSure: "Resetten?"
|
||||
saved: "Opgeslagen"
|
||||
messaging: "Chat"
|
||||
upload: "Uploaden"
|
||||
keepOriginalUploading: "Origineel beeld behouden."
|
||||
keepOriginalUploadingDescription: "Bewaar de originele versie bij het uploaden van afbeeldingen. Indien uitgeschakeld, wordt bij het uploaden een alternatieve versie voor webpublicatie genereert."
|
||||
|
@ -269,7 +268,6 @@ uploadFromUrlMayTakeTime: "Het kan even duren voordat het uploaden voltooid is."
|
|||
explore: "Verkennen"
|
||||
messageRead: "Lezen"
|
||||
noMoreHistory: "Er is geen verdere geschiedenis"
|
||||
startMessaging: "Start een gesprek"
|
||||
nUsersRead: "gelezen door {n}"
|
||||
agreeTo: "Ik stem in met {0}"
|
||||
start: "Aan de slag"
|
||||
|
@ -463,6 +461,10 @@ icon: "Avatar"
|
|||
replies: "Antwoord"
|
||||
renotes: "Herdelen"
|
||||
information: "Over"
|
||||
_chat:
|
||||
invitations: "Uitnodigen"
|
||||
members: "Leden"
|
||||
home: "Startpagina"
|
||||
_delivery:
|
||||
stop: "Opgeschort"
|
||||
_type:
|
||||
|
|
|
@ -299,8 +299,6 @@ text: "Tekst"
|
|||
next: "Neste"
|
||||
retype: "Gjenta"
|
||||
quoteAttached: "Sitat"
|
||||
noMessagesYet: "Ingen meldinger ennå"
|
||||
newMessageExists: "Det er nye meldinger"
|
||||
onlyOneFileCanBeAttached: "Du kan bare legge ved én fil i en melding"
|
||||
invitations: "Inviter"
|
||||
available: "Tilgjengelig"
|
||||
|
@ -464,6 +462,11 @@ replies: "Svar"
|
|||
renotes: "Renote"
|
||||
surrender: "Avbryt"
|
||||
information: "Informasjon"
|
||||
_chat:
|
||||
invitations: "Inviter"
|
||||
members: "Medlemmer"
|
||||
home: "Hjem"
|
||||
send: "Send"
|
||||
_delivery:
|
||||
stop: "Suspendert"
|
||||
_initialAccountSetting:
|
||||
|
|
|
@ -269,7 +269,6 @@ deleteAreYouSure: "Czy na pewno chcesz usunąć „{x}”?"
|
|||
resetAreYouSure: "Czy na pewno chcesz zresetować?"
|
||||
areYouSure: "Na pewno?"
|
||||
saved: "Zapisano"
|
||||
messaging: "Wiadomości"
|
||||
upload: "Wyślij"
|
||||
keepOriginalUploading: "Zachowaj oryginalny obraz"
|
||||
keepOriginalUploadingDescription: "Zapisuje oryginalnie przesłany obraz w niezmienionej postaci. Jeśli ta opcja jest wyłączona, po przesłaniu zostanie wygenerowana wersja do wyświetlenia w Internecie."
|
||||
|
@ -282,7 +281,6 @@ uploadFromUrlMayTakeTime: "Wysyłanie może chwilę potrwać."
|
|||
explore: "Eksploruj"
|
||||
messageRead: "Przeczytano"
|
||||
noMoreHistory: "Nie ma dalszej historii"
|
||||
startMessaging: "Rozpocznij czat"
|
||||
nUsersRead: "przeczytano przez {n}"
|
||||
agreeTo: "Wyrażam zgodę na {0}"
|
||||
agree: "Zatwierdź"
|
||||
|
@ -466,8 +464,6 @@ retype: "Wprowadź ponownie"
|
|||
noteOf: "Wpisy {user}"
|
||||
quoteAttached: "Zacytowano"
|
||||
quoteQuestion: "Czy na pewno chcesz umieścić cytat?"
|
||||
noMessagesYet: "Nie napisano jeszcze wiadomości"
|
||||
newMessageExists: "Masz nową wiadomość"
|
||||
onlyOneFileCanBeAttached: "Możesz załączyć tylko jeden plik do wiadomości"
|
||||
signinRequired: "Proszę się zalogować"
|
||||
invitations: "Zaproś"
|
||||
|
@ -1046,6 +1042,12 @@ surrender: "Odrzuć"
|
|||
gameRetry: "Spróbuj ponownie"
|
||||
postForm: "Formularz tworzenia wpisu"
|
||||
information: "Informacje"
|
||||
_chat:
|
||||
invitations: "Zaproś"
|
||||
noHistory: "Brak historii"
|
||||
members: "Członkowie"
|
||||
home: "Strona główna"
|
||||
send: "Wyślij"
|
||||
_delivery:
|
||||
stop: "Zawieszono"
|
||||
_type:
|
||||
|
@ -1302,6 +1304,7 @@ _permissions:
|
|||
"write:gallery": "Edytuj swoją galerię"
|
||||
"read:gallery-likes": "Wyświetlanie listy polubionych postów w galerii"
|
||||
"write:gallery-likes": "Edytowanie listy polubionych postów w galerii"
|
||||
"write:chat": "Tworzenie lub usuwanie wiadomości czatu"
|
||||
_auth:
|
||||
shareAccessTitle: "Przyznawanie uprawnień aplikacji"
|
||||
shareAccess: "Czy chcesz autoryzować „{name}” do dostępu do tego konta?"
|
||||
|
|
|
@ -287,7 +287,6 @@ deleteAreYouSure: "Deseja excluir \"{x}\"?"
|
|||
resetAreYouSure: "Deseja reiniciar?"
|
||||
areYouSure: "Tem certeza?"
|
||||
saved: "Salvo"
|
||||
messaging: "Chat"
|
||||
upload: "Fazer upload"
|
||||
keepOriginalUploading: "Manter a imagem original"
|
||||
keepOriginalUploadingDescription: "Ao fazer o upload de uma imagem, ela será mantida em sua versão original. Caso desative esta opção, o navegador irá gerar uma versão da imagem otimizada para publicação na web durante o upload."
|
||||
|
@ -300,7 +299,6 @@ uploadFromUrlMayTakeTime: "Pode levar algum tempo para que o upload seja conclu
|
|||
explore: "Explorar"
|
||||
messageRead: "Lida"
|
||||
noMoreHistory: "Não existe histórico anterior"
|
||||
startMessaging: "Iniciar conversação"
|
||||
nUsersRead: "{n} pessoas leram"
|
||||
agreeTo: "Eu concordo com {0}"
|
||||
agree: "Concordar"
|
||||
|
@ -489,8 +487,6 @@ noteOf: "Publicação de {user}"
|
|||
quoteAttached: "Com citação"
|
||||
quoteQuestion: "Anexar como citação?"
|
||||
attachAsFileQuestion: "O texto na área de transferência é muito longo. Você gostaria de anexá-lo como um arquivo de texto?"
|
||||
noMessagesYet: "Sem conversas até o momento"
|
||||
newMessageExists: "Há uma nova mensagem"
|
||||
onlyOneFileCanBeAttached: "Apenas um arquivo pode ser anexado a uma mensagem"
|
||||
signinRequired: "É necessário se inscrever ou fazer login antes de continuar"
|
||||
signinOrContinueOnRemote: "Para continuar, você precisa mover o seu servidor ou entrar/cadastrar-se nesse servidor."
|
||||
|
@ -1303,6 +1299,12 @@ availableRoles: "Cargos disponíveis"
|
|||
acknowledgeNotesAndEnable: "Ative após compreender as precauções."
|
||||
postForm: "Campo de postagem"
|
||||
information: "Informações"
|
||||
_chat:
|
||||
invitations: "Convidar"
|
||||
noHistory: "Ainda não há histórico"
|
||||
members: "Membros"
|
||||
home: "Início"
|
||||
send: "Enviar"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_accountSettings:
|
||||
|
@ -2175,6 +2177,7 @@ _permissions:
|
|||
"read:clip-favorite": "Ver Clipes favoritados"
|
||||
"read:federation": "Ver dados de federação"
|
||||
"write:report-abuse": "Reportar violação"
|
||||
"write:chat": "Compor ou editar mensagens de chat"
|
||||
_auth:
|
||||
shareAccessTitle: "Conceder permissões do aplicativo"
|
||||
shareAccess: "Você gostaria de autorizar \"{name}\" para acessar essa conta?"
|
||||
|
|
|
@ -254,7 +254,6 @@ removeAreYouSure: "Ești sigur că vrei să înlături {x}?"
|
|||
deleteAreYouSure: "Ești sigur că vrei să ștergi {x}?"
|
||||
resetAreYouSure: "Sigur vrei să resetezi?"
|
||||
saved: "Salvat"
|
||||
messaging: "Chat"
|
||||
upload: "Încarcă"
|
||||
keepOriginalUploading: "Păstrează imaginea originală"
|
||||
keepOriginalUploadingDescription: "Salvează imaginea originala încărcată fără modificări. Dacă e oprită, o versiune pentru afișarea pe web va fi generată la încărcare."
|
||||
|
@ -267,7 +266,6 @@ uploadFromUrlMayTakeTime: "S-ar putea să ia puțin până se finalizează înc
|
|||
explore: "Explorează"
|
||||
messageRead: "Citit"
|
||||
noMoreHistory: "Nu există mai mult istoric"
|
||||
startMessaging: "Începe un chat nou"
|
||||
nUsersRead: "citit de {n}"
|
||||
agreeTo: "Sunt de acord cu {0}"
|
||||
start: "Să începem"
|
||||
|
@ -429,8 +427,6 @@ retype: "Introdu din nou"
|
|||
noteOf: "Notă de {user}"
|
||||
quoteAttached: "Citat"
|
||||
quoteQuestion: "Vrei să adaugi ca citat?"
|
||||
noMessagesYet: "Niciun mesaj încă"
|
||||
newMessageExists: "Ai mesaje noi"
|
||||
onlyOneFileCanBeAttached: "Poți atașa un singur fișier la un mesaj"
|
||||
signinRequired: "Te rog autentifică-te"
|
||||
invitations: "Invită"
|
||||
|
@ -647,6 +643,12 @@ icon: "Avatar"
|
|||
replies: "Răspunde"
|
||||
renotes: "Re-notează"
|
||||
information: "Despre"
|
||||
_chat:
|
||||
invitations: "Invită"
|
||||
noHistory: "Nu există istoric"
|
||||
members: "Membri"
|
||||
home: "Acasă"
|
||||
send: "Trimite"
|
||||
_delivery:
|
||||
stop: "Suspendat"
|
||||
_type:
|
||||
|
|
|
@ -282,7 +282,6 @@ deleteAreYouSure: "Хотите удалить «{x}»?"
|
|||
resetAreYouSure: "На самом деле сбросить?"
|
||||
areYouSure: "Вы уверены?"
|
||||
saved: "Сохранено"
|
||||
messaging: "Сообщения"
|
||||
upload: "Загрузить"
|
||||
keepOriginalUploading: "Сохранить исходное изображение"
|
||||
keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации."
|
||||
|
@ -295,7 +294,6 @@ uploadFromUrlMayTakeTime: "Загрузка может занять некото
|
|||
explore: "Обзор"
|
||||
messageRead: "Прочитали"
|
||||
noMoreHistory: "История закончилась"
|
||||
startMessaging: "Начать общение"
|
||||
nUsersRead: "Прочитали {n}"
|
||||
agreeTo: "Я соглашаюсь с {0}"
|
||||
agree: "Согласен"
|
||||
|
@ -482,8 +480,6 @@ noteOf: "Что пишет {user}"
|
|||
quoteAttached: "Цитата"
|
||||
quoteQuestion: "Хотите добавить цитату?"
|
||||
attachAsFileQuestion: "Текста в буфере обмена слишком много. Прикрепить как текстовый файл?"
|
||||
noMessagesYet: "Пока ни одного сообщения"
|
||||
newMessageExists: "Новое сообщение"
|
||||
onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл"
|
||||
signinRequired: "Пожалуйста, войдите"
|
||||
signinOrContinueOnRemote: "Чтобы продолжить, вам необходимо войти в аккаунт на своём сервере или зарегистрироваться / войти в аккаунт на этом."
|
||||
|
@ -1183,6 +1179,12 @@ inquiry: "Связаться"
|
|||
messageToFollower: "Сообщение подписчикам"
|
||||
postForm: "Форма отправки"
|
||||
information: "Описание"
|
||||
_chat:
|
||||
invitations: "Пригласить"
|
||||
noHistory: "История пока пуста"
|
||||
members: "Участники"
|
||||
home: "Главная"
|
||||
send: "Отправить"
|
||||
_settings:
|
||||
webhook: "Вебхук"
|
||||
_delivery:
|
||||
|
@ -1803,6 +1805,7 @@ _permissions:
|
|||
"read:gallery-likes": "Просмотр списка понравившегося в галерее"
|
||||
"write:gallery-likes": "Изменение списка понравившегося в галерее"
|
||||
"write:admin:reset-password": "Сбросить пароль пользователю"
|
||||
"write:chat": "Писать и удалять сообщения"
|
||||
_auth:
|
||||
shareAccessTitle: "Разрешения для приложений"
|
||||
shareAccess: "Дать доступ для «{name}» к вашей учётной записи?"
|
||||
|
|
|
@ -242,7 +242,6 @@ removeAreYouSure: "Naozaj chcete odstrániť \"{x}\"?"
|
|||
deleteAreYouSure: "Naozaj chcete odstrániť \"{x}\"?"
|
||||
resetAreYouSure: "Naozaj resetovať?"
|
||||
saved: "Uložené"
|
||||
messaging: "Chat"
|
||||
upload: "Nahrať súbor"
|
||||
keepOriginalUploading: "Zachovať pôvodný obrázok"
|
||||
keepOriginalUploadingDescription: "Uloží pôvodný obrázok ako je. Ak je vypnuté, verzia pre web sa vygeneruje pri nahratí."
|
||||
|
@ -255,7 +254,6 @@ uploadFromUrlMayTakeTime: "Nahrávanie môže nejaký čas trvať."
|
|||
explore: "Objavovať"
|
||||
messageRead: "Prečítané"
|
||||
noMoreHistory: "To je všetko"
|
||||
startMessaging: "Začať chat"
|
||||
nUsersRead: "prečítané {n} používateľmi"
|
||||
agreeTo: "Súhlasím s {0}"
|
||||
agreeBelow: "Súhlasím s nasledovným"
|
||||
|
@ -428,8 +426,6 @@ retype: "Zadajte znovu"
|
|||
noteOf: "Poznámky používateľa {user}"
|
||||
quoteAttached: "Citované"
|
||||
quoteQuestion: "Pripojiť ako citát?"
|
||||
noMessagesYet: "Zatiaľ žiadne správy"
|
||||
newMessageExists: "Máte novú správu"
|
||||
onlyOneFileCanBeAttached: "Ku správe môžete priložiť len jeden súbor"
|
||||
signinRequired: "Prihláste sa, prosím!"
|
||||
invitations: "Pozvať"
|
||||
|
@ -919,6 +915,12 @@ flip: "Preklopiť"
|
|||
lastNDays: "Posledných {n} dní"
|
||||
postForm: "Napísať poznámku"
|
||||
information: "Informácie"
|
||||
_chat:
|
||||
invitations: "Pozvať"
|
||||
noHistory: "Žiadna história"
|
||||
members: "Členovia"
|
||||
home: "Domov"
|
||||
send: "Poslať"
|
||||
_delivery:
|
||||
stop: "Zmrazené"
|
||||
_type:
|
||||
|
@ -1178,6 +1180,7 @@ _permissions:
|
|||
"write:gallery": "Upravovať vašu galériu"
|
||||
"read:gallery-likes": "Vidieť zoznam obľúbených príspevkov z galérie"
|
||||
"write:gallery-likes": "Upraviť zoznam obľúbených príspevov z galérie"
|
||||
"write:chat": "Písať alebo odstraňovať správy v chate"
|
||||
_auth:
|
||||
shareAccess: "Prajete si povoliť \"{name}\", aby mal prístup k tomuto účtu?"
|
||||
shareAccessAsk: "Naozaj chcete povoliť tejto aplikácii prístup k tomuto účtu?"
|
||||
|
|
|
@ -249,7 +249,6 @@ removeAreYouSure: "Är du säker att du vill radera \"{x}\"?"
|
|||
deleteAreYouSure: "Är du säker att du vill radera \"{x}\"?"
|
||||
resetAreYouSure: "Vill du återställa?"
|
||||
saved: "Sparad"
|
||||
messaging: "Chatt"
|
||||
upload: "Ladda upp"
|
||||
keepOriginalUploading: "Behåll originalbild"
|
||||
keepOriginalUploadingDescription: "Sparar den originellt uppladdade bilden i sitt i befintliga skick. Om avstängd, kommer en webbversion bli genererad vid uppladdning."
|
||||
|
@ -262,7 +261,6 @@ uploadFromUrlMayTakeTime: "Det kan ta tid tills att uppladdningen blir klar."
|
|||
explore: "Utforska"
|
||||
messageRead: "Läs"
|
||||
noMoreHistory: "Det finns ingen mer historik"
|
||||
startMessaging: "Starta en chatt"
|
||||
nUsersRead: "läst av {n}"
|
||||
agreeTo: "Jag accepterar {0}"
|
||||
agree: "Överens"
|
||||
|
@ -394,7 +392,6 @@ text: "Text"
|
|||
enable: "Aktivera"
|
||||
next: "Nästa"
|
||||
retype: "Ange igen"
|
||||
noMessagesYet: "Inga meddelanden än"
|
||||
invitations: "Inbjudan"
|
||||
invitationCode: "Inbjudningskod"
|
||||
available: "Tillgängligt"
|
||||
|
@ -563,6 +560,11 @@ tryAgain: "Försök igen senare"
|
|||
signinWithPasskey: "Logga in med nyckel"
|
||||
unknownWebAuthnKey: "Okänd nyckel"
|
||||
information: "Om"
|
||||
_chat:
|
||||
invitations: "Inbjudan"
|
||||
members: "Medlemmar"
|
||||
home: "Hem"
|
||||
send: "Skicka"
|
||||
_delivery:
|
||||
stop: "Suspenderad"
|
||||
_type:
|
||||
|
|
|
@ -287,7 +287,6 @@ deleteAreYouSure: "ต้องการลบ “{x}” ใช่ไหม?"
|
|||
resetAreYouSure: "รีเซ็ตเลยไหม?"
|
||||
areYouSure: "แน่ใจแล้วใช่ไหมคะ?"
|
||||
saved: "บันทึกแล้ว"
|
||||
messaging: "แชท"
|
||||
upload: "อัปโหลด"
|
||||
keepOriginalUploading: "เก็บภาพต้นฉบับ"
|
||||
keepOriginalUploadingDescription: "เก็บภาพต้นฉบับไว้เมื่ออัปโหลดภาพ หากปิด รูปภาพสำหรับการเผยแพร่ทางเว็บจะถูกสร้างขึ้นในเบราว์เซอร์เมื่อทำการอัปโหลด"
|
||||
|
@ -300,7 +299,6 @@ uploadFromUrlMayTakeTime: "การอัปโหลดอาจใช้เ
|
|||
explore: "สำรวจ"
|
||||
messageRead: "อ่านแล้ว"
|
||||
noMoreHistory: "ไม่มีประวัติเพิ่มเติม"
|
||||
startMessaging: "เริ่มการสนทนา"
|
||||
nUsersRead: "อ่านโดย {n}"
|
||||
agreeTo: "ฉันยอมรับ {0}"
|
||||
agree: "ยอมรับ"
|
||||
|
@ -489,8 +487,6 @@ noteOf: "โน้ตของ {user}"
|
|||
quoteAttached: "อ้างอิง"
|
||||
quoteQuestion: "ต้องการที่จะแนบมันเพื่ออ้างอิงใช่ไหม?"
|
||||
attachAsFileQuestion: "ข้อความในคลิปบอร์ดยาวเกินไป คุณต้องการแนบเป็นไฟล์ข้อความหรือไม่?"
|
||||
noMessagesYet: "ยังไม่มีข้อความ"
|
||||
newMessageExists: "คุณมีข้อความใหม่"
|
||||
onlyOneFileCanBeAttached: "สามารถแนบไฟล์ได้เพียงไฟล์เดียวต่อ 1 ข้อความ"
|
||||
signinRequired: "ก่อนดำเนินการต่อ กรุณาลงทะเบียนหรือเข้าสู่ระบบ"
|
||||
signinOrContinueOnRemote: "เพื่อดำเนินการต่อได้ คุณต้องไปที่เซิร์ฟเวอร์ที่คุณใช้งานอยู่ หรือลงทะเบียน/เข้าสู่ระบบเซิร์ฟเวอร์นี้"
|
||||
|
@ -1294,6 +1290,12 @@ yourNameContainsProhibitedWords: "ชื่อของคุณนั้นม
|
|||
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
|
||||
postForm: "แบบฟอร์มการโพสต์"
|
||||
information: "เกี่ยวกับ"
|
||||
_chat:
|
||||
invitations: "คำเชิญ"
|
||||
noHistory: "ไม่มีประวัติ"
|
||||
members: "สมาชิก"
|
||||
home: "หน้าหลัก"
|
||||
send: "ส่ง"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_abuseUserReport:
|
||||
|
@ -2152,6 +2154,7 @@ _permissions:
|
|||
"read:clip-favorite": "ดูคลิปที่ถูกใจ"
|
||||
"read:federation": "รับข้อมูลเกี่ยวกับสหพันธ์"
|
||||
"write:report-abuse": "รายงานการละเมิด"
|
||||
"write:chat": "เขียนหรือลบข้อความแชท"
|
||||
_auth:
|
||||
shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน"
|
||||
shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?"
|
||||
|
|
|
@ -261,7 +261,6 @@ removeAreYouSure: "\"{x}\" silmek istediğinizden emin misiniz?"
|
|||
deleteAreYouSure: "\"{x}\" silmek istediğinizden emin misiniz?"
|
||||
resetAreYouSure: "Sıfırlansın mı?"
|
||||
saved: "Kaydedildi"
|
||||
messaging: "Mesajlar"
|
||||
upload: "Yükle"
|
||||
keepOriginalUploading: "Orijinal görseli koru"
|
||||
keepOriginalUploadingDescription: "Orijinal olarak yüklenen görüntüyü olduğu gibi kaydeder. Kapatılırsa, yükleme sırasında web'de görüntülenecek bir sürüm oluşturulur."
|
||||
|
@ -274,7 +273,6 @@ uploadFromUrlMayTakeTime: "Yüklemenin tamamlanması biraz süre alabilir."
|
|||
explore: "Keşfet"
|
||||
messageRead: "Okundu"
|
||||
noMoreHistory: "Bundan öncesi yok"
|
||||
startMessaging: "Yeni bir sohbet başlat"
|
||||
nUsersRead: "{n} kişi okudu"
|
||||
agreeTo: "Kabul Ediyorum: {0}"
|
||||
agree: "Kabul Et"
|
||||
|
@ -351,7 +349,6 @@ pinnedNotes: "Sabitlenen"
|
|||
manageAntennas: "Anten ayarları"
|
||||
userList: "Listeler"
|
||||
resetPassword: "Şifre sıfırlama"
|
||||
noMessagesYet: "Şimdilik mesaj yok"
|
||||
details: "Detaylar"
|
||||
deck: "Güverte"
|
||||
smtpHost: "Sağlayıcı"
|
||||
|
@ -378,6 +375,8 @@ addMemo: "Kısa not ekle"
|
|||
icon: "Avatar"
|
||||
replies: "yanıt"
|
||||
renotes: "vazgeçme"
|
||||
_chat:
|
||||
home: "Ana sayfa"
|
||||
_delivery:
|
||||
stop: "Askıya alınmış"
|
||||
_type:
|
||||
|
|
|
@ -246,7 +246,6 @@ removeAreYouSure: "Ви впевнені, що хочете видалити \"{
|
|||
deleteAreYouSure: "Ви впевнені, що хочете видалити \"{x}\"?"
|
||||
resetAreYouSure: "Справді скинути?"
|
||||
saved: "Збережено"
|
||||
messaging: "Чати"
|
||||
upload: "Завантажити"
|
||||
keepOriginalUploading: "Зберегти оригінальне зображення"
|
||||
keepOriginalUploadingDescription: "Зберігає початково завантажене зображення як є. Якщо вимкнено, версія для відображення в Інтернеті буде створена під час завантаження."
|
||||
|
@ -259,7 +258,6 @@ uploadFromUrlMayTakeTime: "Завантаження може зайняти де
|
|||
explore: "Огляд"
|
||||
messageRead: "Прочитано"
|
||||
noMoreHistory: "Подальшої історії немає"
|
||||
startMessaging: "Розпочати діалог"
|
||||
nUsersRead: "Прочитали {n}"
|
||||
agreeTo: "Я погоджуюсь з {0}"
|
||||
agreeBelow: "Я погоджуюся з наведеним нижче"
|
||||
|
@ -427,8 +425,6 @@ retype: "Введіть ще раз"
|
|||
noteOf: "Нотатка {user}"
|
||||
quoteAttached: "Цитата"
|
||||
quoteQuestion: "Ви хочете додати цитату?"
|
||||
noMessagesYet: "Ще немає повідомлень"
|
||||
newMessageExists: "Є нові повідомлення"
|
||||
onlyOneFileCanBeAttached: "До повідомлення можна вкласти лише один файл"
|
||||
signinRequired: "Будь ласка, авторизуйтесь"
|
||||
invitations: "Запрошення"
|
||||
|
@ -911,6 +907,12 @@ flip: "Перевернути"
|
|||
lastNDays: "Останні {n} днів"
|
||||
postForm: "Створення нотатки"
|
||||
information: "Інформація"
|
||||
_chat:
|
||||
invitations: "Запросити"
|
||||
noHistory: "Історія порожня"
|
||||
members: "Учасники"
|
||||
home: "Домівка"
|
||||
send: "Відправити"
|
||||
_delivery:
|
||||
stop: "Призупинено"
|
||||
_type:
|
||||
|
@ -1367,6 +1369,7 @@ _permissions:
|
|||
"read:channels": "Переглядати канали"
|
||||
"write:channels": "Змінювати канали"
|
||||
"read:gallery": "Перегляд галереї"
|
||||
"write:chat": "Створювати та видаляти повідомлення"
|
||||
_auth:
|
||||
shareAccess: "Ви хочете надати \"{name}\" доступ до цього акаунту?"
|
||||
shareAccessAsk: "Ви впевнені, що хочете надати цій програмі доступ до вашого акаунту?"
|
||||
|
|
|
@ -257,7 +257,6 @@ removeAreYouSure: "“{x}”ni olib tashlamoqchi ekanligingizga ishonchingiz kom
|
|||
deleteAreYouSure: "“{x}”ni chindan ham yo'q qilmoqchimisiz?"
|
||||
resetAreYouSure: "Haqiqatan ham qayta tiklansinmi?"
|
||||
saved: "Saqlandi"
|
||||
messaging: "Suhbat"
|
||||
upload: "Yuklash"
|
||||
keepOriginalUploading: "Asl rasmni saqlang"
|
||||
keepOriginalUploadingDescription: "Rasmlarni yuklashda asl nusxasini saqlaydi. Agar o'chirilgan bo'lsa, brauzer yuklangandan keyin nashr qilish uchun rasm yaratadi."
|
||||
|
@ -270,7 +269,6 @@ uploadFromUrlMayTakeTime: "Yuklash tugallanishi uchun biroz vaqt ketishi mumkin.
|
|||
explore: "Ko'rib chiqish"
|
||||
messageRead: "O‘qildi"
|
||||
noMoreHistory: "Buning ortida hech qanday hikoya yo'q"
|
||||
startMessaging: "Yangi suhbatni boshlash"
|
||||
nUsersRead: "{n} tomonidan o'qildi"
|
||||
agreeTo: "Men {0} ga roziman"
|
||||
agree: "Rozi bo'lish"
|
||||
|
@ -445,8 +443,6 @@ retype: "Qayta kiriting"
|
|||
noteOf: "{user} tomonidan joylandi\n"
|
||||
quoteAttached: "Iqtibos"
|
||||
quoteQuestion: "Iqtibos sifatida qo'shilsinmi?"
|
||||
noMessagesYet: "Bu yerda xabarlar yo'q"
|
||||
newMessageExists: "Yangi xabarlar bor"
|
||||
onlyOneFileCanBeAttached: "Faqat bitta faylni biriktirish mumkin"
|
||||
signinRequired: "Davom etishdan oldin ro'yhatdan o'tishingiz yoki tizimga kirishingiz kerak"
|
||||
invitations: "Taklif qilish"
|
||||
|
@ -842,6 +838,12 @@ replies: "Javob berish"
|
|||
renotes: "Qayta qayd etish"
|
||||
flip: "Teskari"
|
||||
information: "Haqida"
|
||||
_chat:
|
||||
invitations: "Taklif qilish"
|
||||
noHistory: "Tarix yo'q"
|
||||
members: "A'zolar"
|
||||
home: "Bosh sahifa"
|
||||
send: "Yuborish"
|
||||
_delivery:
|
||||
stop: "To'xtatilgan"
|
||||
_type:
|
||||
|
|
|
@ -264,7 +264,6 @@ deleteAreYouSure: "Bạn có chắc muốn xóa \"{x}\"?"
|
|||
resetAreYouSure: "Bạn có chắc muốn đặt lại?"
|
||||
areYouSure: "Bạn chắc chứ?"
|
||||
saved: "Đã lưu"
|
||||
messaging: "Trò chuyện"
|
||||
upload: "Tải lên"
|
||||
keepOriginalUploading: "Giữ hình ảnh gốc"
|
||||
keepOriginalUploadingDescription: "Giữ nguyên như hình ảnh được tải lên ban đầu. Nếu tắt, một phiên bản để hiển thị trên web sẽ được tạo khi tải lên."
|
||||
|
@ -277,7 +276,6 @@ uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lê
|
|||
explore: "Khám phá"
|
||||
messageRead: "Đã đọc"
|
||||
noMoreHistory: "Không còn gì để đọc"
|
||||
startMessaging: "Bắt đầu trò chuyện"
|
||||
nUsersRead: "đọc bởi {n}"
|
||||
agreeTo: "Tôi đồng ý {0}"
|
||||
agree: "Đồng ý"
|
||||
|
@ -463,8 +461,6 @@ noteOf: "Tút của {user}"
|
|||
quoteAttached: "Trích dẫn"
|
||||
quoteQuestion: "Trích dẫn lại?"
|
||||
attachAsFileQuestion: "Văn bản ở trong bộ nhớ tạm rất dài. Bạn có muốn đăng nó dưới dạng một tệp văn bản không?"
|
||||
noMessagesYet: "Chưa có tin nhắn"
|
||||
newMessageExists: "Bạn có tin nhắn mới"
|
||||
onlyOneFileCanBeAttached: "Bạn chỉ có thể đính kèm một tập tin"
|
||||
signinRequired: "Vui lòng đăng nhập"
|
||||
invitations: "Mời"
|
||||
|
@ -1121,6 +1117,12 @@ lastNDays: "{n} ngày trước"
|
|||
surrender: "Từ chối"
|
||||
postForm: "Mẫu đăng"
|
||||
information: "Giới thiệu"
|
||||
_chat:
|
||||
invitations: "Mời"
|
||||
noHistory: "Không có dữ liệu"
|
||||
members: "Thành viên"
|
||||
home: "Trang chính"
|
||||
send: "Gửi"
|
||||
_delivery:
|
||||
stop: "Đã vô hiệu hóa"
|
||||
_type:
|
||||
|
@ -1630,6 +1632,7 @@ _permissions:
|
|||
"write:gallery": "Sửa kho ảnh của tôi"
|
||||
"read:gallery-likes": "Xem danh sách các tút đã thích trong thư viện của tôi"
|
||||
"write:gallery-likes": "Sửa danh sách các tút đã thích trong thư viện của tôi"
|
||||
"write:chat": "Soạn hoặc xóa tin nhắn"
|
||||
_auth:
|
||||
shareAccessTitle: "Cho phép truy cập app"
|
||||
shareAccess: "Bạn có muốn cho phép \"{name}\" truy cập vào tài khoản này không?"
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "要删掉「{x}」吗?"
|
|||
resetAreYouSure: "恢复默认设置?"
|
||||
areYouSure: "你确定吗?"
|
||||
saved: "已保存"
|
||||
messaging: "聊天"
|
||||
upload: "本地上传"
|
||||
keepOriginalUploading: "保留原图"
|
||||
keepOriginalUploadingDescription: "上传图片时保留原始图片。关闭时,浏览器会在上传时生成一张用于web发布的图片。"
|
||||
|
@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "上传可能需要一些时间完成。"
|
|||
explore: "发现"
|
||||
messageRead: "已读"
|
||||
noMoreHistory: "没有更多的历史记录"
|
||||
startMessaging: "添加聊天"
|
||||
startChat: "开始聊天"
|
||||
nUsersRead: "{n} 人已读"
|
||||
agreeTo: "勾选则表示已阅读并同意 {0}"
|
||||
agree: "同意"
|
||||
|
@ -491,8 +490,6 @@ noteOf: "{user} 的帖子"
|
|||
quoteAttached: "已引用"
|
||||
quoteQuestion: "是否引用此链接内容?"
|
||||
attachAsFileQuestion: "剪贴板内的文字过长。要转换为文本文件并添加吗?"
|
||||
noMessagesYet: "现在没有新的聊天"
|
||||
newMessageExists: "新信息"
|
||||
onlyOneFileCanBeAttached: "只能添加一个附件"
|
||||
signinRequired: "请先登录"
|
||||
signinOrContinueOnRemote: "若要继续,需要转到您所使用的实例,或者在此服务器上注册或登录。"
|
||||
|
@ -1335,6 +1332,58 @@ emojiPalette: "表情符号调色板"
|
|||
postForm: "投稿窗口"
|
||||
textCount: "字数"
|
||||
information: "关于"
|
||||
chat: "聊天"
|
||||
migrateOldSettings: "迁移旧设置信息"
|
||||
migrateOldSettings_description: "通常设置信息将自动迁移。但如果由于某种原因迁移不成功,则可以手动触发迁移过程。当前的配置信息将被覆盖。"
|
||||
compress: "压缩"
|
||||
right: "右"
|
||||
bottom: "下"
|
||||
top: "上"
|
||||
_chat:
|
||||
noMessagesYet: "还没有消息"
|
||||
newMessage: "新消息"
|
||||
individualChat: "私聊"
|
||||
individualChat_description: "可以与特定用户进行一对一聊天。"
|
||||
roomChat: "群聊"
|
||||
roomChat_description: "可以进行多人聊天。\n就算用户未允许私聊,只要接受了邀请,仍可以聊天。"
|
||||
createRoom: "创建房间"
|
||||
inviteUserToChat: "邀请用户来开始聊天"
|
||||
yourRooms: "已创建的房间"
|
||||
joiningRooms: "已加入的房间"
|
||||
invitations: "邀请"
|
||||
noInvitations: "没有邀请"
|
||||
history: "历史"
|
||||
noHistory: "没有历史记录"
|
||||
noRooms: "没有房间"
|
||||
inviteUser: "邀请用户"
|
||||
sentInvitations: "已发送的邀请"
|
||||
join: "加入"
|
||||
ignore: "忽略"
|
||||
leave: "退出房间"
|
||||
members: "成员"
|
||||
searchMessages: "搜索消息"
|
||||
home: "首页"
|
||||
send: "发送"
|
||||
newline: "换行"
|
||||
muteThisRoom: "静音此房间"
|
||||
deleteRoom: "删除房间"
|
||||
chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。"
|
||||
chatNotAvailableInOtherAccount: "对方账户目前处于无法使用聊天的状态。"
|
||||
cannotChatWithTheUser: "无法与此用户聊天"
|
||||
cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。"
|
||||
chatWithThisUser: "聊天"
|
||||
thisUserAllowsChatOnlyFromFollowers: "此用户仅接受关注者发起的聊天。"
|
||||
thisUserAllowsChatOnlyFromFollowing: "此用户仅接受关注的人发起的聊天。"
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "此用户仅接受互相关注的人发起的聊天。"
|
||||
thisUserNotAllowedChatAnyone: "此用户不接受任何人发起的聊天。"
|
||||
chatAllowedUsers: "谁可以发起聊天"
|
||||
chatAllowedUsers_note: "主动发起聊天时,对方将不受此设置限制。"
|
||||
_chatAllowedUsers:
|
||||
everyone: "任何人"
|
||||
followers: "仅关注者"
|
||||
following: "仅关注的人"
|
||||
mutual: "仅相互关注"
|
||||
none: "没有人"
|
||||
_emojiPalette:
|
||||
palettes: "调色板"
|
||||
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
|
||||
|
@ -1360,6 +1409,13 @@ _settings:
|
|||
timelineAndNote: "时间线和帖子"
|
||||
makeEveryTextElementsSelectable: "使所有的文字均可选择"
|
||||
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
|
||||
useStickyIcons: "使图标跟随滚动"
|
||||
showNavbarSubButtons: "在导航栏中显示副按钮"
|
||||
ifOn: "启用时"
|
||||
ifOff: "关闭时"
|
||||
_chat:
|
||||
showSenderName: "显示发送者的名字"
|
||||
sendOnEnter: "回车键发送"
|
||||
_preferencesProfile:
|
||||
profileName: "配置名"
|
||||
profileNameDescription: "请指定用于识别此设备的名称"
|
||||
|
@ -1869,6 +1925,7 @@ _role:
|
|||
canImportFollowing: "允许导入关注列表"
|
||||
canImportMuting: "允许导入隐藏列表"
|
||||
canImportUserLists: "允许导入用户列表"
|
||||
canChat: "允许聊天"
|
||||
_condition:
|
||||
roleAssignedTo: "已分配给手动角色"
|
||||
isLocal: "是本地用户"
|
||||
|
@ -2099,6 +2156,7 @@ _sfx:
|
|||
noteMy: "我的帖子"
|
||||
notification: "通知"
|
||||
reaction: "选择回应时"
|
||||
chatMessage: "聊天信息"
|
||||
_soundSettings:
|
||||
driveFile: "使用网盘内的音频"
|
||||
driveFileWarn: "选择网盘上的文件"
|
||||
|
@ -2245,6 +2303,8 @@ _permissions:
|
|||
"read:clip-favorite": "查看便签的点赞"
|
||||
"read:federation": "查看联合相关信息"
|
||||
"write:report-abuse": "举报用户"
|
||||
"write:chat": "撰写或删除消息"
|
||||
"read:chat": "查看聊天"
|
||||
_auth:
|
||||
shareAccessTitle: "应用程序授权许可"
|
||||
shareAccess: "您要授权允许 “{name}” 访问您的帐户吗?"
|
||||
|
@ -2493,6 +2553,7 @@ _notification:
|
|||
newNote: "新的帖子"
|
||||
unreadAntennaNote: "天线 {name}"
|
||||
roleAssigned: "授予的角色"
|
||||
chatRoomInvitationReceived: "受邀加入聊天室"
|
||||
emptyPushNotificationMessage: "推送通知已更新"
|
||||
achievementEarned: "获得成就"
|
||||
testNotification: "测试通知"
|
||||
|
@ -2521,6 +2582,7 @@ _notification:
|
|||
receiveFollowRequest: "收到关注请求"
|
||||
followRequestAccepted: "关注请求已通过"
|
||||
roleAssigned: "授予的角色"
|
||||
chatRoomInvitationReceived: "受邀加入聊天室"
|
||||
achievementEarned: "取得的成就"
|
||||
exportCompleted: "已完成导出"
|
||||
login: "登录"
|
||||
|
@ -2534,6 +2596,9 @@ _notification:
|
|||
_deck:
|
||||
alwaysShowMainColumn: "总是显示主列"
|
||||
columnAlign: "列对齐"
|
||||
columnGap: "列间距"
|
||||
deckMenuPosition: "Deck 菜单位置"
|
||||
navbarPosition: "导航栏位置"
|
||||
addColumn: "添加列"
|
||||
newNoteNotificationSettings: "新帖子通知设定"
|
||||
configureColumn: "列设置"
|
||||
|
@ -2547,7 +2612,7 @@ _deck:
|
|||
newProfile: "新建配置文件"
|
||||
deleteProfile: "删除配置文件"
|
||||
introduction: "将各列进行组合以创建您自己的界面!"
|
||||
introduction2: "您可以随时通过屏幕右侧的 + 来添加列"
|
||||
introduction2: "可以随时通过屏幕右侧的 + 来添加列"
|
||||
widgetsIntroduction: "从列菜单中,选择“小工具编辑”来添加小工具"
|
||||
useSimpleUiForNonRootPages: "用简易UI表示非根页面"
|
||||
usedAsMinWidthWhenFlexible: "「自适应宽度」被启用的时候,这就是最小的宽度"
|
||||
|
@ -2660,6 +2725,7 @@ _moderationLogTypes:
|
|||
deletePage: "删除了页面"
|
||||
deleteFlash: "删除了 Play"
|
||||
deleteGalleryPost: "删除了图库稿件"
|
||||
deleteChatRoom: "删除聊天室"
|
||||
updateProxyAccountDescription: "更新代理账户的简介"
|
||||
_fileViewer:
|
||||
title: "文件信息"
|
||||
|
|
|
@ -289,7 +289,6 @@ deleteAreYouSure: "確定要刪掉「{x}」嗎?"
|
|||
resetAreYouSure: "確定要重設嗎?"
|
||||
areYouSure: "是否確定?"
|
||||
saved: "已儲存"
|
||||
messaging: "聊天"
|
||||
upload: "上傳"
|
||||
keepOriginalUploading: "保留原圖"
|
||||
keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成適用於網路傳送的版本。"
|
||||
|
@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
|
|||
explore: "探索"
|
||||
messageRead: "已讀"
|
||||
noMoreHistory: "沒有更多歷史紀錄"
|
||||
startMessaging: "開始聊天"
|
||||
startChat: "開始聊天"
|
||||
nUsersRead: "{n} 人已讀"
|
||||
agreeTo: "我同意{0}"
|
||||
agree: "同意"
|
||||
|
@ -491,8 +490,6 @@ noteOf: "{user}的貼文"
|
|||
quoteAttached: "引用"
|
||||
quoteQuestion: "是否要引用?"
|
||||
attachAsFileQuestion: "剪貼簿的文字較長。請問是否要將其以文字檔的方式附加呢?"
|
||||
noMessagesYet: "沒有訊息"
|
||||
newMessageExists: "有新的訊息"
|
||||
onlyOneFileCanBeAttached: "只能加入一個附件"
|
||||
signinRequired: "請先登入"
|
||||
signinOrContinueOnRemote: "若要繼續,需前往您所在的伺服器,或者註冊並登入此伺服器"
|
||||
|
@ -1335,6 +1332,58 @@ emojiPalette: "表情符號調色盤"
|
|||
postForm: "發文視窗"
|
||||
textCount: "字數"
|
||||
information: "關於"
|
||||
chat: "聊天"
|
||||
migrateOldSettings: "遷移舊設定資訊"
|
||||
migrateOldSettings_description: "通常情況下,這會自動進行,但若因某些原因未能順利遷移,您可以手動觸發遷移處理。請注意,當前的設定資訊將會被覆寫。"
|
||||
compress: "壓縮"
|
||||
right: "右"
|
||||
bottom: "下"
|
||||
top: "上"
|
||||
_chat:
|
||||
noMessagesYet: "尚無訊息"
|
||||
newMessage: "新訊息"
|
||||
individualChat: "ㄧ對一聊天室"
|
||||
individualChat_description: "可以與特定使用者進行一對一的聊天。"
|
||||
roomChat: "多人聊天室"
|
||||
roomChat_description: "可以進行多人聊天。\n此外,即使是未允許個人聊天的使用者,只要對方接受,也可以進行聊天。"
|
||||
createRoom: "建立聊天室"
|
||||
inviteUserToChat: "邀請使用者開始聊天"
|
||||
yourRooms: "已建立的聊天室"
|
||||
joiningRooms: "已加入的聊天室"
|
||||
invitations: "邀請"
|
||||
noInvitations: "沒有邀請"
|
||||
history: "歷史紀錄"
|
||||
noHistory: "沒有歷史紀錄"
|
||||
noRooms: "沒有可用的聊天室"
|
||||
inviteUser: "邀請使用者"
|
||||
sentInvitations: "已傳送的邀請"
|
||||
join: "加入"
|
||||
ignore: "忽視"
|
||||
leave: "退出聊天室"
|
||||
members: "成員"
|
||||
searchMessages: "搜尋聊天訊息"
|
||||
home: "首頁"
|
||||
send: "發送"
|
||||
newline: "換行"
|
||||
muteThisRoom: "此聊天室已靜音"
|
||||
deleteRoom: "刪除聊天室"
|
||||
chatNotAvailableForThisAccountOrServer: "這個伺服器或這個帳號的聊天功能尚未啟用。"
|
||||
chatNotAvailableInOtherAccount: "對方的帳號無法使用聊天功能。"
|
||||
cannotChatWithTheUser: "無法與此使用者聊天"
|
||||
cannotChatWithTheUser_description: "聊天功能目前無法使用,或對方尚未開放聊天功能。"
|
||||
chatWithThisUser: "聊天"
|
||||
thisUserAllowsChatOnlyFromFollowers: "此使用者僅接受來自追隨者的聊天訊息。"
|
||||
thisUserAllowsChatOnlyFromFollowing: "此使用者僅接受自己追隨的使用者傳送聊天訊息。"
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "此使用者只接受互相追隨的使用者傳送聊天訊息。"
|
||||
thisUserNotAllowedChatAnyone: "此使用者不接受來自任何人的聊天訊息。"
|
||||
chatAllowedUsers: "允許聊天的對象"
|
||||
chatAllowedUsers_note: "無論此設定為何,您仍可與自己曾發送過聊天訊息的對象進行聊天。"
|
||||
_chatAllowedUsers:
|
||||
everyone: "任何人"
|
||||
followers: "追隨自己的使用者"
|
||||
following: "只有您追隨的使用者"
|
||||
mutual: "互相追隨"
|
||||
none: "無"
|
||||
_emojiPalette:
|
||||
palettes: "調色盤"
|
||||
enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
|
||||
|
@ -1360,6 +1409,13 @@ _settings:
|
|||
timelineAndNote: "時間軸及貼文"
|
||||
makeEveryTextElementsSelectable: "允許選取所有文字"
|
||||
makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。"
|
||||
useStickyIcons: "使大頭貼跟隨捲動"
|
||||
showNavbarSubButtons: "在導覽列顯示輔助按鈕"
|
||||
ifOn: "開啟時"
|
||||
ifOff: "關閉時"
|
||||
_chat:
|
||||
showSenderName: "顯示發送者的名稱"
|
||||
sendOnEnter: "按下 Enter 發送訊息"
|
||||
_preferencesProfile:
|
||||
profileName: "設定檔案名稱"
|
||||
profileNameDescription: "設定一個名稱來識別此裝置。"
|
||||
|
@ -1869,6 +1925,7 @@ _role:
|
|||
canImportFollowing: "允許匯入追隨名單"
|
||||
canImportMuting: "允許匯入靜音名單"
|
||||
canImportUserLists: "允許匯入清單"
|
||||
canChat: "允許聊天"
|
||||
_condition:
|
||||
roleAssignedTo: "手動指派角色完成"
|
||||
isLocal: "本地使用者"
|
||||
|
@ -2099,6 +2156,7 @@ _sfx:
|
|||
noteMy: "我的貼文"
|
||||
notification: "通知"
|
||||
reaction: "選擇反應時"
|
||||
chatMessage: "聊天訊息"
|
||||
_soundSettings:
|
||||
driveFile: "使用雲端硬碟的音效檔案"
|
||||
driveFileWarn: "請選擇雲端硬碟中的檔案"
|
||||
|
@ -2245,6 +2303,8 @@ _permissions:
|
|||
"read:clip-favorite": "查看摘錄的讚"
|
||||
"read:federation": "查看站台聯邦的相關資訊"
|
||||
"write:report-abuse": "檢舉違規行為"
|
||||
"write:chat": "撰寫或刪除訊息"
|
||||
"read:chat": "查看聊天訊息"
|
||||
_auth:
|
||||
shareAccessTitle: "應用程式的存取權限"
|
||||
shareAccess: "要授權「“{name}”」存取您的帳戶嗎?"
|
||||
|
@ -2493,6 +2553,7 @@ _notification:
|
|||
newNote: "新的貼文"
|
||||
unreadAntennaNote: "天線 {name}"
|
||||
roleAssigned: "已授予角色"
|
||||
chatRoomInvitationReceived: "您被邀請加入聊天室"
|
||||
emptyPushNotificationMessage: "推送通知已更新"
|
||||
achievementEarned: "獲得成就"
|
||||
testNotification: "通知測試"
|
||||
|
@ -2521,6 +2582,7 @@ _notification:
|
|||
receiveFollowRequest: "已收到追隨請求"
|
||||
followRequestAccepted: "追隨請求已接受"
|
||||
roleAssigned: "已授予角色"
|
||||
chatRoomInvitationReceived: "已被邀請加入聊天室"
|
||||
achievementEarned: "獲得成就"
|
||||
exportCompleted: "已完成匯出。"
|
||||
login: "登入"
|
||||
|
@ -2534,6 +2596,9 @@ _notification:
|
|||
_deck:
|
||||
alwaysShowMainColumn: "總是顯示主欄"
|
||||
columnAlign: "對齊欄位"
|
||||
columnGap: "欄與欄之間的邊距"
|
||||
deckMenuPosition: "多欄模式的選單位置"
|
||||
navbarPosition: "導覽列位置"
|
||||
addColumn: "新增欄位"
|
||||
newNoteNotificationSettings: "新貼文通知的設定"
|
||||
configureColumn: "欄位的設定"
|
||||
|
@ -2660,6 +2725,7 @@ _moderationLogTypes:
|
|||
deletePage: "刪除頁面"
|
||||
deleteFlash: "刪除 Play"
|
||||
deleteGalleryPost: "刪除相簿的貼文"
|
||||
deleteChatRoom: "刪除聊天室"
|
||||
updateProxyAccountDescription: "更新代理帳戶的說明"
|
||||
_fileViewer:
|
||||
title: "檔案詳細資訊"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.3.2-beta.8",
|
||||
"version": "2025.4.0-alpha.0",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat1742203321812 {
|
||||
name = 'Chat1742203321812'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "chat_room" ("id" character varying(32) NOT NULL, "name" character varying(256) NOT NULL, "ownerId" character varying(32) NOT NULL, CONSTRAINT "PK_8aa3a52cf74c96469f0ef9fbe3e" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f0d8ad64243fa2ca2800da0dfd" ON "chat_room" ("ownerId") `);
|
||||
await queryRunner.query(`CREATE TABLE "chat_message" ("id" character varying(32) NOT NULL, "fromUserId" character varying(32) NOT NULL, "toUserId" character varying(32), "toRoomId" character varying(32), "text" character varying(4096), "uri" character varying(512), "reads" character varying(32) array NOT NULL DEFAULT '{}', "fileId" character varying(32), "reactions" character varying(1024) array NOT NULL DEFAULT '{}', CONSTRAINT "PK_3cc0d85193aade457d3077dd06b" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_79a26e7a4d9afa5e4fc05f134e" ON "chat_message" ("fromUserId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_25e097b51d7622c249452c6f75" ON "chat_message" ("toUserId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f006b8a76efd1abf9f221c175c" ON "chat_message" ("toRoomId") `);
|
||||
await queryRunner.query(`CREATE TABLE "chat_room_membership" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "roomId" character varying(32) NOT NULL, CONSTRAINT "PK_2bd59c741e571b283c048beb69a" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_d99c5279460fb77ef58c596ce5" ON "chat_room_membership" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_c25143ebab714e930aeca1c0e8" ON "chat_room_membership" ("roomId") `);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room" ADD CONSTRAINT "FK_f0d8ad64243fa2ca2800da0dfd6" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_79a26e7a4d9afa5e4fc05f134ed" FOREIGN KEY ("fromUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_25e097b51d7622c249452c6f757" FOREIGN KEY ("toUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_f006b8a76efd1abf9f221c175ce" FOREIGN KEY ("toRoomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_fd0f9a4879430239715ad4f8e2a" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_membership" ADD CONSTRAINT "FK_d99c5279460fb77ef58c596ce51" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_membership" ADD CONSTRAINT "FK_c25143ebab714e930aeca1c0e8d" FOREIGN KEY ("roomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_membership" DROP CONSTRAINT "FK_c25143ebab714e930aeca1c0e8d"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_membership" DROP CONSTRAINT "FK_d99c5279460fb77ef58c596ce51"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_fd0f9a4879430239715ad4f8e2a"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_f006b8a76efd1abf9f221c175ce"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_25e097b51d7622c249452c6f757"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_79a26e7a4d9afa5e4fc05f134ed"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room" DROP CONSTRAINT "FK_f0d8ad64243fa2ca2800da0dfd6"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_c25143ebab714e930aeca1c0e8"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_d99c5279460fb77ef58c596ce5"`);
|
||||
await queryRunner.query(`DROP TABLE "chat_room_membership"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_f006b8a76efd1abf9f221c175c"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_25e097b51d7622c249452c6f75"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_79a26e7a4d9afa5e4fc05f134e"`);
|
||||
await queryRunner.query(`DROP TABLE "chat_message"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_f0d8ad64243fa2ca2800da0dfd"`);
|
||||
await queryRunner.query(`DROP TABLE "chat_room"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat21742608337548 {
|
||||
name = 'Chat21742608337548'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "chatScope" character varying(128) NOT NULL DEFAULT 'mutual'`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_185b6b5afa707b5d36d1ce3144" ON "chat_room_membership" ("userId", "roomId") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_185b6b5afa707b5d36d1ce3144"`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "chatScope"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat31742617546147 {
|
||||
name = 'Chat31742617546147'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "chat_approval" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "otherId" character varying(32) NOT NULL, CONSTRAINT "PK_fbbb95d60acf5c85388345b5f5d" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_530257863e1381a7f2f1d3282f" ON "chat_approval" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b1d46037f23d170da5c05fdf75" ON "chat_approval" ("otherId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_12c4768a2f706fc267f2078903" ON "chat_approval" ("userId", "otherId") `);
|
||||
await queryRunner.query(`ALTER TABLE "chat_approval" ADD CONSTRAINT "FK_530257863e1381a7f2f1d3282fe" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_approval" ADD CONSTRAINT "FK_b1d46037f23d170da5c05fdf755" FOREIGN KEY ("otherId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_approval" DROP CONSTRAINT "FK_b1d46037f23d170da5c05fdf755"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_approval" DROP CONSTRAINT "FK_530257863e1381a7f2f1d3282fe"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_12c4768a2f706fc267f2078903"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_b1d46037f23d170da5c05fdf75"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_530257863e1381a7f2f1d3282f"`);
|
||||
await queryRunner.query(`DROP TABLE "chat_approval"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat41742707840715 {
|
||||
name = 'Chat41742707840715'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "chat_room_invitation" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "roomId" character varying(32) NOT NULL, CONSTRAINT "PK_9d489521a312dd28225672de2dc" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8552bb38e7ed038c5bdd398a38" ON "chat_room_invitation" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_5f265075b215fc390a57523b12" ON "chat_room_invitation" ("roomId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_044f2a7962b8ee5bbfaa02e8a3" ON "chat_room_invitation" ("userId", "roomId") `);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD CONSTRAINT "FK_8552bb38e7ed038c5bdd398a384" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD CONSTRAINT "FK_5f265075b215fc390a57523b12a" FOREIGN KEY ("roomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP CONSTRAINT "FK_5f265075b215fc390a57523b12a"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP CONSTRAINT "FK_8552bb38e7ed038c5bdd398a384"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_044f2a7962b8ee5bbfaa02e8a3"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_5f265075b215fc390a57523b12"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_8552bb38e7ed038c5bdd398a38"`);
|
||||
await queryRunner.query(`DROP TABLE "chat_room_invitation"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat51742721896936 {
|
||||
name = 'Chat51742721896936'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD "ignored" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP COLUMN "ignored"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Chat61742795111958 {
|
||||
name = 'Chat61742795111958'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room" ADD "description" character varying(2048) NOT NULL DEFAULT ''`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room" ADD "isArchived" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_membership" ADD "isMuted" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "chat_room_membership" DROP COLUMN "isMuted"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room" DROP COLUMN "isArchived"`);
|
||||
await queryRunner.query(`ALTER TABLE "chat_room" DROP COLUMN "description"`);
|
||||
}
|
||||
}
|
|
@ -37,17 +37,17 @@
|
|||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"@swc/core-darwin-arm64": "1.10.16",
|
||||
"@swc/core-darwin-x64": "1.10.16",
|
||||
"@swc/core-darwin-arm64": "1.11.11",
|
||||
"@swc/core-darwin-x64": "1.11.11",
|
||||
"@swc/core-freebsd-x64": "1.3.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.10.16",
|
||||
"@swc/core-linux-arm64-gnu": "1.10.16",
|
||||
"@swc/core-linux-arm64-musl": "1.10.16",
|
||||
"@swc/core-linux-x64-gnu": "1.10.16",
|
||||
"@swc/core-linux-x64-musl": "1.10.16",
|
||||
"@swc/core-win32-arm64-msvc": "1.10.16",
|
||||
"@swc/core-win32-ia32-msvc": "1.10.16",
|
||||
"@swc/core-win32-x64-msvc": "1.10.16",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.11.11",
|
||||
"@swc/core-linux-arm64-gnu": "1.11.11",
|
||||
"@swc/core-linux-arm64-musl": "1.11.11",
|
||||
"@swc/core-linux-x64-gnu": "1.11.11",
|
||||
"@swc/core-linux-x64-musl": "1.11.11",
|
||||
"@swc/core-win32-arm64-msvc": "1.11.11",
|
||||
"@swc/core-win32-ia32-msvc": "1.11.11",
|
||||
"@swc/core-win32-x64-msvc": "1.11.11",
|
||||
"@tensorflow/tfjs": "4.22.0",
|
||||
"@tensorflow/tfjs-node": "4.22.0",
|
||||
"bufferutil": "4.0.9",
|
||||
|
@ -67,23 +67,23 @@
|
|||
"utf-8-validate": "6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.749.0",
|
||||
"@aws-sdk/lib-storage": "3.749.0",
|
||||
"@aws-sdk/client-s3": "3.772.0",
|
||||
"@aws-sdk/lib-storage": "3.772.0",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
"@fastify/cors": "10.0.2",
|
||||
"@fastify/cors": "10.1.0",
|
||||
"@fastify/express": "4.0.2",
|
||||
"@fastify/http-proxy": "10.0.2",
|
||||
"@fastify/multipart": "9.0.3",
|
||||
"@fastify/static": "8.1.0",
|
||||
"@fastify/static": "8.1.1",
|
||||
"@fastify/view": "10.0.2",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.0",
|
||||
"@napi-rs/canvas": "0.1.67",
|
||||
"@nestjs/common": "11.0.9",
|
||||
"@nestjs/core": "11.0.9",
|
||||
"@nestjs/testing": "11.0.9",
|
||||
"@napi-rs/canvas": "0.1.68",
|
||||
"@nestjs/common": "11.0.12",
|
||||
"@nestjs/core": "11.0.12",
|
||||
"@nestjs/testing": "11.0.12",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "8.55.0",
|
||||
"@sentry/profiling-node": "8.55.0",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"@sinonjs/fake-timers": "11.3.1",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.6.0",
|
||||
"@swc/core": "1.10.16",
|
||||
"@swc/core": "1.11.11",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.17.1",
|
||||
|
@ -100,7 +100,7 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.3",
|
||||
"bullmq": "5.41.1",
|
||||
"bullmq": "5.44.1",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.4.1",
|
||||
|
@ -122,7 +122,7 @@
|
|||
"hpagent": "1.2.0",
|
||||
"htmlescape": "1.1.1",
|
||||
"http-link-header": "1.1.3",
|
||||
"ioredis": "5.5.0",
|
||||
"ioredis": "5.6.0",
|
||||
"ip-cidr": "4.0.2",
|
||||
"ipaddr.js": "2.2.0",
|
||||
"is-svg": "5.1.0",
|
||||
|
@ -131,26 +131,26 @@
|
|||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.3",
|
||||
"jsrsasign": "11.1.0",
|
||||
"juice": "11.0.0",
|
||||
"meilisearch": "0.48.2",
|
||||
"juice": "11.0.1",
|
||||
"meilisearch": "0.49.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"nanoid": "5.1.0",
|
||||
"nanoid": "5.1.5",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.10.0",
|
||||
"nsfwjs": "4.2.0",
|
||||
"oauth": "0.10.0",
|
||||
"oauth": "0.10.2",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.3.6",
|
||||
"parse5": "7.2.1",
|
||||
"pg": "8.13.3",
|
||||
"pg": "8.14.1",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
|
@ -163,8 +163,8 @@
|
|||
"reflect-metadata": "0.2.2",
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.1",
|
||||
"sanitize-html": "2.14.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sanitize-html": "2.15.0",
|
||||
"secure-json-parse": "3.0.2",
|
||||
"sharp": "0.33.5",
|
||||
"slacc": "0.0.10",
|
||||
|
@ -173,14 +173,14 @@
|
|||
"systeminformation": "5.25.11",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.3",
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsc-alias": "1.8.11",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.7.3",
|
||||
"ulid": "2.3.0",
|
||||
"typeorm": "0.3.21",
|
||||
"typescript": "5.8.2",
|
||||
"ulid": "2.4.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.7",
|
||||
"ws": "8.18.0",
|
||||
"ws": "8.18.1",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -204,7 +204,7 @@
|
|||
"@types/jsrsasign": "10.5.15",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "22.13.4",
|
||||
"@types/node": "22.13.10",
|
||||
"@types/nodemailer": "6.4.17",
|
||||
"@types/oauth": "0.9.6",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
|
@ -223,9 +223,9 @@
|
|||
"@types/tmp": "0.2.6",
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.5.14",
|
||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
||||
"@typescript-eslint/parser": "8.24.0",
|
||||
"@types/ws": "8.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.27.0",
|
||||
"@typescript-eslint/parser": "8.27.0",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
|
|
|
@ -0,0 +1,885 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { ChatApprovalsRepository, ChatMessagesRepository, ChatRoomInvitationsRepository, ChatRoomMembershipsRepository, ChatRoomsRepository, MiChatMessage, MiChatRoom, MiChatRoomMembership, MiDriveFile, MiUser, MutingsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { emojiRegex } from '@/misc/emoji-regex.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
const MAX_ROOM_MEMBERS = 30;
|
||||
const MAX_REACTIONS_PER_MESSAGE = 100;
|
||||
const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/;
|
||||
|
||||
// TODO: ReactionServiceのやつと共通化
|
||||
function normalizeEmojiString(x: string) {
|
||||
const match = emojiRegex.exec(x);
|
||||
if (match) {
|
||||
// 合字を含む1つの絵文字
|
||||
const unicode = match[0];
|
||||
|
||||
// 異体字セレクタ除去
|
||||
return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
|
||||
} else {
|
||||
throw new Error('invalid emoji');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChatService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.chatMessagesRepository)
|
||||
private chatMessagesRepository: ChatMessagesRepository,
|
||||
|
||||
@Inject(DI.chatApprovalsRepository)
|
||||
private chatApprovalsRepository: ChatApprovalsRepository,
|
||||
|
||||
@Inject(DI.chatRoomsRepository)
|
||||
private chatRoomsRepository: ChatRoomsRepository,
|
||||
|
||||
@Inject(DI.chatRoomInvitationsRepository)
|
||||
private chatRoomInvitationsRepository: ChatRoomInvitationsRepository,
|
||||
|
||||
@Inject(DI.chatRoomMembershipsRepository)
|
||||
private chatRoomMembershipsRepository: ChatRoomMembershipsRepository,
|
||||
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private apRendererService: ApRendererService,
|
||||
private queueService: QueueService,
|
||||
private pushNotificationService: PushNotificationService,
|
||||
private notificationService: NotificationService,
|
||||
private userBlockingService: UserBlockingService,
|
||||
private queryService: QueryService,
|
||||
private roleService: RoleService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createMessageToUser(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toUser: MiUser, params: {
|
||||
text?: string | null;
|
||||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
}): Promise<Packed<'ChatMessageLite'>> {
|
||||
if (fromUser.id === toUser.id) {
|
||||
throw new Error('yourself');
|
||||
}
|
||||
|
||||
const approvals = await this.chatApprovalsRepository.createQueryBuilder('approval')
|
||||
.where(new Brackets(qb => { // 自分が相手を許可しているか
|
||||
qb.where('approval.userId = :fromUserId', { fromUserId: fromUser.id })
|
||||
.andWhere('approval.otherId = :toUserId', { toUserId: toUser.id });
|
||||
}))
|
||||
.orWhere(new Brackets(qb => { // 相手が自分を許可しているか
|
||||
qb.where('approval.userId = :toUserId', { toUserId: toUser.id })
|
||||
.andWhere('approval.otherId = :fromUserId', { fromUserId: fromUser.id });
|
||||
}))
|
||||
.take(2)
|
||||
.getMany();
|
||||
|
||||
const otherApprovedMe = approvals.some(approval => approval.userId === toUser.id);
|
||||
const iApprovedOther = approvals.some(approval => approval.userId === fromUser.id);
|
||||
|
||||
if (!otherApprovedMe) {
|
||||
if (toUser.chatScope === 'none') {
|
||||
throw new Error('recipient is cannot chat (none)');
|
||||
} else if (toUser.chatScope === 'followers') {
|
||||
const isFollower = await this.userFollowingService.isFollowing(fromUser.id, toUser.id);
|
||||
if (!isFollower) {
|
||||
throw new Error('recipient is cannot chat (followers)');
|
||||
}
|
||||
} else if (toUser.chatScope === 'following') {
|
||||
const isFollowing = await this.userFollowingService.isFollowing(toUser.id, fromUser.id);
|
||||
if (!isFollowing) {
|
||||
throw new Error('recipient is cannot chat (following)');
|
||||
}
|
||||
} else if (toUser.chatScope === 'mutual') {
|
||||
const isMutual = await this.userFollowingService.isMutual(fromUser.id, toUser.id);
|
||||
if (!isMutual) {
|
||||
throw new Error('recipient is cannot chat (mutual)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(await this.roleService.getUserPolicies(toUser.id)).canChat) {
|
||||
throw new Error('recipient is cannot chat (policy)');
|
||||
}
|
||||
|
||||
const blocked = await this.userBlockingService.checkBlocked(toUser.id, fromUser.id);
|
||||
if (blocked) {
|
||||
throw new Error('blocked');
|
||||
}
|
||||
|
||||
const message = {
|
||||
id: this.idService.gen(),
|
||||
fromUserId: fromUser.id,
|
||||
toUserId: toUser.id,
|
||||
text: params.text ? params.text.trim() : null,
|
||||
fileId: params.file ? params.file.id : null,
|
||||
reads: [],
|
||||
uri: params.uri ?? null,
|
||||
} satisfies Partial<MiChatMessage>;
|
||||
|
||||
const inserted = await this.chatMessagesRepository.insertOne(message);
|
||||
|
||||
// 相手を許可しておく
|
||||
if (!iApprovedOther) {
|
||||
this.chatApprovalsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
userId: fromUser.id,
|
||||
otherId: toUser.id,
|
||||
});
|
||||
}
|
||||
|
||||
const packedMessage = await this.chatEntityService.packMessageLiteFor1on1(inserted);
|
||||
|
||||
if (this.userEntityService.isLocalUser(toUser)) {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.set(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`, message.id);
|
||||
redisPipeline.sadd(`newChatMessagesExists:${toUser.id}`, `user:${fromUser.id}`);
|
||||
redisPipeline.exec();
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(fromUser)) {
|
||||
// 自分のストリーム
|
||||
this.globalEventService.publishChatUserStream(fromUser.id, toUser.id, 'message', packedMessage);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(toUser)) {
|
||||
// 相手のストリーム
|
||||
this.globalEventService.publishChatUserStream(toUser.id, fromUser.id, 'message', packedMessage);
|
||||
}
|
||||
|
||||
// 3秒経っても既読にならなかったらイベント発行
|
||||
if (this.userEntityService.isLocalUser(toUser)) {
|
||||
setTimeout(async () => {
|
||||
const marker = await this.redisClient.get(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`);
|
||||
|
||||
if (marker == null) return; // 既読
|
||||
|
||||
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted, toUser);
|
||||
this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||
//this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
return packedMessage;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createMessageToRoom(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toRoom: MiChatRoom, params: {
|
||||
text?: string | null;
|
||||
file?: MiDriveFile | null;
|
||||
uri?: string | null;
|
||||
}): Promise<Packed<'ChatMessageLite'>> {
|
||||
const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id });
|
||||
|
||||
if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) {
|
||||
throw new Error('you are not a member of the room');
|
||||
}
|
||||
|
||||
const membershipsOtherThanMe = memberships.filter(member => member.userId !== fromUser.id);
|
||||
|
||||
const message = {
|
||||
id: this.idService.gen(),
|
||||
fromUserId: fromUser.id,
|
||||
toRoomId: toRoom.id,
|
||||
text: params.text ? params.text.trim() : null,
|
||||
fileId: params.file ? params.file.id : null,
|
||||
reads: [],
|
||||
uri: params.uri ?? null,
|
||||
} satisfies Partial<MiChatMessage>;
|
||||
|
||||
const inserted = await this.chatMessagesRepository.insertOne(message);
|
||||
|
||||
const packedMessage = await this.chatEntityService.packMessageLiteForRoom(inserted);
|
||||
|
||||
this.globalEventService.publishChatRoomStream(toRoom.id, 'message', packedMessage);
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
for (const membership of membershipsOtherThanMe) {
|
||||
if (membership.isMuted) continue;
|
||||
|
||||
redisPipeline.set(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`, message.id);
|
||||
redisPipeline.sadd(`newChatMessagesExists:${membership.userId}`, `room:${toRoom.id}`);
|
||||
}
|
||||
redisPipeline.exec();
|
||||
|
||||
// 3秒経っても既読にならなかったらイベント発行
|
||||
setTimeout(async () => {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
for (const membership of membershipsOtherThanMe) {
|
||||
redisPipeline.get(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`);
|
||||
}
|
||||
const markers = await redisPipeline.exec();
|
||||
if (markers == null) throw new Error('redis error');
|
||||
|
||||
if (markers.every(marker => marker[1] == null)) return;
|
||||
|
||||
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted);
|
||||
|
||||
for (let i = 0; i < membershipsOtherThanMe.length; i++) {
|
||||
const marker = markers[i][1];
|
||||
if (marker == null) continue;
|
||||
|
||||
this.globalEventService.publishMainStream(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
|
||||
//this.pushNotificationService.pushNotification(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return packedMessage;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async readUserChatMessage(
|
||||
readerId: MiUser['id'],
|
||||
senderId: MiUser['id'],
|
||||
): Promise<void> {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.del(`newUserChatMessageExists:${readerId}:${senderId}`);
|
||||
redisPipeline.srem(`newChatMessagesExists:${readerId}`, `user:${senderId}`);
|
||||
await redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async readRoomChatMessage(
|
||||
readerId: MiUser['id'],
|
||||
roomId: MiChatRoom['id'],
|
||||
): Promise<void> {
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.del(`newRoomChatMessageExists:${readerId}:${roomId}`);
|
||||
redisPipeline.srem(`newChatMessagesExists:${readerId}`, `room:${roomId}`);
|
||||
await redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public findMessageById(messageId: MiChatMessage['id']) {
|
||||
return this.chatMessagesRepository.findOneBy({ id: messageId });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public findMyMessageById(userId: MiUser['id'], messageId: MiChatMessage['id']) {
|
||||
return this.chatMessagesRepository.findOneBy({ id: messageId, fromUserId: userId });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async hasPermissionToViewRoomTimeline(meId: MiUser['id'], room: MiChatRoom) {
|
||||
if (await this.isRoomMember(room, meId)) {
|
||||
return true;
|
||||
} else {
|
||||
const iAmModerator = await this.roleService.isModerator({ id: meId });
|
||||
if (iAmModerator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteMessage(message: MiChatMessage) {
|
||||
await this.chatMessagesRepository.delete(message.id);
|
||||
|
||||
if (message.toUserId) {
|
||||
const [fromUser, toUser] = await Promise.all([
|
||||
this.usersRepository.findOneByOrFail({ id: message.fromUserId }),
|
||||
this.usersRepository.findOneByOrFail({ id: message.toUserId }),
|
||||
]);
|
||||
|
||||
if (this.userEntityService.isLocalUser(fromUser)) this.globalEventService.publishChatUserStream(message.fromUserId, message.toUserId, 'deleted', message.id);
|
||||
if (this.userEntityService.isLocalUser(toUser)) this.globalEventService.publishChatUserStream(message.toUserId, message.fromUserId, 'deleted', message.id);
|
||||
|
||||
if (this.userEntityService.isLocalUser(fromUser) && this.userEntityService.isRemoteUser(toUser)) {
|
||||
//const activity = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), fromUser));
|
||||
//this.queueService.deliver(fromUser, activity, toUser.inbox);
|
||||
}
|
||||
} else if (message.toRoomId) {
|
||||
this.globalEventService.publishChatRoomStream(message.toRoomId, 'deleted', message.id);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async userTimeline(meId: MiUser['id'], otherId: MiUser['id'], limit: number, sinceId?: MiChatMessage['id'] | null, untilId?: MiChatMessage['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId)
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where(new Brackets(qb => {
|
||||
qb
|
||||
.where('message.fromUserId = :meId')
|
||||
.andWhere('message.toUserId = :otherId');
|
||||
}))
|
||||
.orWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('message.fromUserId = :otherId')
|
||||
.andWhere('message.toUserId = :meId');
|
||||
}));
|
||||
}))
|
||||
.setParameter('meId', meId)
|
||||
.setParameter('otherId', otherId);
|
||||
|
||||
const messages = await query.take(limit).getMany();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async roomTimeline(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatMessage['id'] | null, untilId?: MiChatMessage['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId)
|
||||
.andWhere('message.toRoomId = :roomId', { roomId })
|
||||
.leftJoinAndSelect('message.file', 'file')
|
||||
.leftJoinAndSelect('message.fromUser', 'fromUser');
|
||||
|
||||
const messages = await query.take(limit).getMany();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async userHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> {
|
||||
const history: MiChatMessage[] = [];
|
||||
|
||||
const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: meId });
|
||||
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const found = history.map(m => (m.fromUserId === meId) ? m.toUserId! : m.fromUserId!);
|
||||
|
||||
const query = this.chatMessagesRepository.createQueryBuilder('message')
|
||||
.orderBy('message.id', 'DESC')
|
||||
.where(new Brackets(qb => {
|
||||
qb
|
||||
.where('message.fromUserId = :meId', { meId: meId })
|
||||
.orWhere('message.toUserId = :meId', { meId: meId });
|
||||
}))
|
||||
.andWhere('message.toRoomId IS NULL')
|
||||
.andWhere(`message.fromUserId NOT IN (${ mutingQuery.getQuery() })`)
|
||||
.andWhere(`message.toUserId NOT IN (${ mutingQuery.getQuery() })`);
|
||||
|
||||
if (found.length > 0) {
|
||||
query.andWhere('message.fromUserId NOT IN (:...found)', { found: found });
|
||||
query.andWhere('message.toUserId NOT IN (:...found)', { found: found });
|
||||
}
|
||||
|
||||
query.setParameters(mutingQuery.getParameters());
|
||||
|
||||
const message = await query.getOne();
|
||||
|
||||
if (message) {
|
||||
history.push(message);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async roomHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> {
|
||||
// TODO: 一回のクエリにまとめられるかも
|
||||
const [memberRoomIds, ownedRoomIds] = await Promise.all([
|
||||
this.chatRoomMembershipsRepository.findBy({
|
||||
userId: meId,
|
||||
}).then(xs => xs.map(x => x.roomId)),
|
||||
this.chatRoomsRepository.findBy({
|
||||
ownerId: meId,
|
||||
}).then(xs => xs.map(x => x.id)),
|
||||
]);
|
||||
|
||||
const roomIds = memberRoomIds.concat(ownedRoomIds);
|
||||
|
||||
if (memberRoomIds.length === 0 && ownedRoomIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const history: MiChatMessage[] = [];
|
||||
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const found = history.map(m => m.toRoomId!);
|
||||
|
||||
const query = this.chatMessagesRepository.createQueryBuilder('message')
|
||||
.orderBy('message.id', 'DESC')
|
||||
.where('message.toRoomId IN (:...roomIds)', { roomIds });
|
||||
|
||||
if (found.length > 0) {
|
||||
query.andWhere('message.toRoomId NOT IN (:...found)', { found: found });
|
||||
}
|
||||
|
||||
const message = await query.getOne();
|
||||
|
||||
if (message) {
|
||||
history.push(message);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getUserReadStateMap(userId: MiUser['id'], otherIds: MiUser['id'][]) {
|
||||
const readStateMap: Record<MiUser['id'], boolean> = {};
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
|
||||
for (const otherId of otherIds) {
|
||||
redisPipeline.get(`newUserChatMessageExists:${userId}:${otherId}`);
|
||||
}
|
||||
|
||||
const markers = await redisPipeline.exec();
|
||||
if (markers == null) throw new Error('redis error');
|
||||
|
||||
for (let i = 0; i < otherIds.length; i++) {
|
||||
const marker = markers[i][1];
|
||||
readStateMap[otherIds[i]] = marker == null;
|
||||
}
|
||||
|
||||
return readStateMap;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getRoomReadStateMap(userId: MiUser['id'], roomIds: MiChatRoom['id'][]) {
|
||||
const readStateMap: Record<MiChatRoom['id'], boolean> = {};
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
|
||||
for (const roomId of roomIds) {
|
||||
redisPipeline.get(`newRoomChatMessageExists:${userId}:${roomId}`);
|
||||
}
|
||||
|
||||
const markers = await redisPipeline.exec();
|
||||
if (markers == null) throw new Error('redis error');
|
||||
|
||||
for (let i = 0; i < roomIds.length; i++) {
|
||||
const marker = markers[i][1];
|
||||
readStateMap[roomIds[i]] = marker == null;
|
||||
}
|
||||
|
||||
return readStateMap;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async hasUnreadMessages(userId: MiUser['id']) {
|
||||
const card = await this.redisClient.scard(`newChatMessagesExists:${userId}`);
|
||||
return card > 0;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createRoom(owner: MiUser, params: Partial<{
|
||||
name: string;
|
||||
description: string;
|
||||
}>) {
|
||||
const room = {
|
||||
id: this.idService.gen(),
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
ownerId: owner.id,
|
||||
} satisfies Partial<MiChatRoom>;
|
||||
|
||||
const created = await this.chatRoomsRepository.insertOne(room);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async hasPermissionToDeleteRoom(meId: MiUser['id'], room: MiChatRoom) {
|
||||
if (room.ownerId === meId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const iAmModerator = await this.roleService.isModerator({ id: meId });
|
||||
if (iAmModerator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteRoom(room: MiChatRoom, deleter?: MiUser) {
|
||||
await this.chatRoomsRepository.delete(room.id);
|
||||
|
||||
if (deleter) {
|
||||
const deleterIsModerator = await this.roleService.isModerator(deleter);
|
||||
|
||||
if (deleterIsModerator) {
|
||||
this.moderationLogService.log(deleter, 'deleteChatRoom', {
|
||||
roomId: room.id,
|
||||
room: room,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async findMyRoomById(ownerId: MiUser['id'], roomId: MiChatRoom['id']) {
|
||||
return this.chatRoomsRepository.findOneBy({ id: roomId, ownerId: ownerId });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async findRoomById(roomId: MiChatRoom['id']) {
|
||||
return this.chatRoomsRepository.findOne({ where: { id: roomId }, relations: ['owner'] });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async isRoomMember(room: MiChatRoom, userId: MiUser['id']) {
|
||||
if (room.ownerId === userId) return true;
|
||||
const membership = await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId });
|
||||
return membership != null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createRoomInvitation(inviterId: MiUser['id'], roomId: MiChatRoom['id'], inviteeId: MiUser['id']) {
|
||||
if (inviterId === inviteeId) {
|
||||
throw new Error('yourself');
|
||||
}
|
||||
|
||||
const room = await this.chatRoomsRepository.findOneByOrFail({ id: roomId, ownerId: inviterId });
|
||||
|
||||
if (await this.isRoomMember(room, inviteeId)) {
|
||||
throw new Error('already member');
|
||||
}
|
||||
|
||||
const existingInvitation = await this.chatRoomInvitationsRepository.findOneBy({ roomId, userId: inviteeId });
|
||||
if (existingInvitation) {
|
||||
throw new Error('already invited');
|
||||
}
|
||||
|
||||
const membershipsCount = await this.chatRoomMembershipsRepository.countBy({ roomId });
|
||||
if (membershipsCount >= MAX_ROOM_MEMBERS) {
|
||||
throw new Error('room is full');
|
||||
}
|
||||
|
||||
// TODO: cehck block
|
||||
|
||||
const invitation = {
|
||||
id: this.idService.gen(),
|
||||
roomId: room.id,
|
||||
userId: inviteeId,
|
||||
} satisfies Partial<MiChatRoomInvitation>;
|
||||
|
||||
const created = await this.chatRoomInvitationsRepository.insertOne(invitation);
|
||||
|
||||
this.notificationService.createNotification(inviteeId, 'chatRoomInvitationReceived', {
|
||||
invitationId: invitation.id,
|
||||
}, inviterId);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getSentRoomInvitationsWithPagination(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatRoomInvitation['id'] | null, untilId?: MiChatRoomInvitation['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatRoomInvitationsRepository.createQueryBuilder('invitation'), sinceId, untilId)
|
||||
.andWhere('invitation.roomId = :roomId', { roomId });
|
||||
|
||||
const invitations = await query.take(limit).getMany();
|
||||
|
||||
return invitations;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getOwnedRoomsWithPagination(ownerId: MiUser['id'], limit: number, sinceId?: MiChatRoom['id'] | null, untilId?: MiChatRoom['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatRoomsRepository.createQueryBuilder('room'), sinceId, untilId)
|
||||
.andWhere('room.ownerId = :ownerId', { ownerId });
|
||||
|
||||
const rooms = await query.take(limit).getMany();
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getReceivedRoomInvitationsWithPagination(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomInvitation['id'] | null, untilId?: MiChatRoomInvitation['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatRoomInvitationsRepository.createQueryBuilder('invitation'), sinceId, untilId)
|
||||
.andWhere('invitation.userId = :userId', { userId })
|
||||
.andWhere('invitation.ignored = FALSE');
|
||||
|
||||
const invitations = await query.take(limit).getMany();
|
||||
|
||||
return invitations;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async joinToRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) {
|
||||
const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
|
||||
|
||||
const membershipsCount = await this.chatRoomMembershipsRepository.countBy({ roomId });
|
||||
if (membershipsCount >= MAX_ROOM_MEMBERS) {
|
||||
throw new Error('room is full');
|
||||
}
|
||||
|
||||
const membership = {
|
||||
id: this.idService.gen(),
|
||||
roomId: roomId,
|
||||
userId: userId,
|
||||
} satisfies Partial<MiChatRoomMembership>;
|
||||
|
||||
// TODO: transaction
|
||||
await this.chatRoomMembershipsRepository.insertOne(membership);
|
||||
await this.chatRoomInvitationsRepository.delete(invitation.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async ignoreRoomInvitation(userId: MiUser['id'], roomId: MiChatRoom['id']) {
|
||||
const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
|
||||
await this.chatRoomInvitationsRepository.update(invitation.id, { ignored: true });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async leaveRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) {
|
||||
const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId });
|
||||
await this.chatRoomMembershipsRepository.delete(membership.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async muteRoom(userId: MiUser['id'], roomId: MiChatRoom['id'], mute: boolean) {
|
||||
const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId });
|
||||
await this.chatRoomMembershipsRepository.update(membership.id, { isMuted: mute });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async updateRoom(room: MiChatRoom, params: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}): Promise<MiChatRoom> {
|
||||
return this.chatRoomsRepository.createQueryBuilder().update()
|
||||
.set(params)
|
||||
.where('id = :id', { id: room.id })
|
||||
.returning('*')
|
||||
.execute()
|
||||
.then((response) => {
|
||||
return response.raw[0];
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getRoomMembershipsWithPagination(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatRoomMembership['id'] | null, untilId?: MiChatRoomMembership['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatRoomMembershipsRepository.createQueryBuilder('membership'), sinceId, untilId)
|
||||
.andWhere('membership.roomId = :roomId', { roomId });
|
||||
|
||||
const memberships = await query.take(limit).getMany();
|
||||
|
||||
return memberships;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async searchMessages(meId: MiUser['id'], query: string, limit: number, params: {
|
||||
userId?: MiUser['id'] | null;
|
||||
roomId?: MiChatRoom['id'] | null;
|
||||
}) {
|
||||
const q = this.chatMessagesRepository.createQueryBuilder('message');
|
||||
|
||||
if (params.userId) {
|
||||
q.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where(new Brackets(qb => {
|
||||
qb
|
||||
.where('message.fromUserId = :meId')
|
||||
.andWhere('message.toUserId = :otherId');
|
||||
}))
|
||||
.orWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('message.fromUserId = :otherId')
|
||||
.andWhere('message.toUserId = :meId');
|
||||
}));
|
||||
}))
|
||||
.setParameter('meId', meId)
|
||||
.setParameter('otherId', params.userId);
|
||||
} else if (params.roomId) {
|
||||
q.where('message.toRoomId = :roomId', { roomId: params.roomId });
|
||||
} else {
|
||||
const membershipsQuery = this.chatRoomMembershipsRepository.createQueryBuilder('membership')
|
||||
.select('membership.roomId')
|
||||
.where('membership.userId = :meId', { meId: meId });
|
||||
|
||||
const ownedRoomsQuery = this.chatRoomsRepository.createQueryBuilder('room')
|
||||
.select('room.id')
|
||||
.where('room.ownerId = :meId', { meId });
|
||||
|
||||
q.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('message.fromUserId = :meId')
|
||||
.orWhere('message.toUserId = :meId')
|
||||
.orWhere(`message.toRoomId IN (${membershipsQuery.getQuery()})`)
|
||||
.orWhere(`message.toRoomId IN (${ownedRoomsQuery.getQuery()})`);
|
||||
}));
|
||||
|
||||
q.setParameters(membershipsQuery.getParameters());
|
||||
q.setParameters(ownedRoomsQuery.getParameters());
|
||||
}
|
||||
|
||||
q.andWhere('LOWER(message.text) LIKE :q', { q: `%${ sqlLikeEscape(query.toLowerCase()) }%` });
|
||||
|
||||
q.leftJoinAndSelect('message.file', 'file');
|
||||
q.leftJoinAndSelect('message.fromUser', 'fromUser');
|
||||
q.leftJoinAndSelect('message.toUser', 'toUser');
|
||||
q.leftJoinAndSelect('message.toRoom', 'toRoom');
|
||||
q.leftJoinAndSelect('toRoom.owner', 'toRoomOwner');
|
||||
|
||||
const messages = await q.orderBy('message.id', 'DESC').take(limit).getMany();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async react(messageId: MiChatMessage['id'], userId: MiUser['id'], reaction_: string) {
|
||||
let reaction;
|
||||
|
||||
const custom = reaction_.match(isCustomEmojiRegexp);
|
||||
|
||||
if (custom == null) {
|
||||
reaction = normalizeEmojiString(reaction_);
|
||||
} else {
|
||||
const name = custom[1];
|
||||
const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name);
|
||||
|
||||
if (emoji == null) {
|
||||
throw new Error('no such emoji');
|
||||
} else {
|
||||
reaction = `:${name}:`;
|
||||
}
|
||||
}
|
||||
|
||||
const message = await this.chatMessagesRepository.findOneByOrFail({ id: messageId });
|
||||
|
||||
if (message.fromUserId === userId) {
|
||||
throw new Error('cannot react to own message');
|
||||
}
|
||||
|
||||
if (message.toRoomId === null && message.toUserId !== userId) {
|
||||
throw new Error('cannot react to others message');
|
||||
}
|
||||
|
||||
if (message.reactions.length >= MAX_REACTIONS_PER_MESSAGE) {
|
||||
throw new Error('too many reactions');
|
||||
}
|
||||
|
||||
const room = message.toRoomId ? await this.chatRoomsRepository.findOneByOrFail({ id: message.toRoomId }) : null;
|
||||
|
||||
if (room) {
|
||||
if (!await this.isRoomMember(room, userId)) {
|
||||
throw new Error('cannot react to others message');
|
||||
}
|
||||
}
|
||||
|
||||
await this.chatMessagesRepository.createQueryBuilder().update()
|
||||
.set({
|
||||
reactions: () => `array_append("reactions", '${userId}/${reaction}')`,
|
||||
})
|
||||
.where('id = :id', { id: message.id })
|
||||
.execute();
|
||||
|
||||
if (room) {
|
||||
this.globalEventService.publishChatRoomStream(room.id, 'react', {
|
||||
messageId: message.id,
|
||||
user: await this.userEntityService.pack(userId),
|
||||
reaction,
|
||||
});
|
||||
} else {
|
||||
this.globalEventService.publishChatUserStream(message.fromUserId, message.toUserId!, 'react', {
|
||||
messageId: message.id,
|
||||
reaction,
|
||||
});
|
||||
this.globalEventService.publishChatUserStream(message.toUserId!, message.fromUserId, 'react', {
|
||||
messageId: message.id,
|
||||
reaction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async unreact(messageId: MiChatMessage['id'], userId: MiUser['id'], reaction_: string) {
|
||||
let reaction;
|
||||
|
||||
const custom = reaction_.match(isCustomEmojiRegexp);
|
||||
|
||||
if (custom == null) {
|
||||
reaction = normalizeEmojiString(reaction_);
|
||||
} else { // 削除されたカスタム絵文字のリアクションを削除したいかもしれないので絵文字の存在チェックはする必要なし
|
||||
const name = custom[1];
|
||||
reaction = `:${name}:`;
|
||||
}
|
||||
|
||||
// NOTE: 自分のリアクションを(あれば)削除するだけなので諸々の権限チェックは必要なし
|
||||
|
||||
const message = await this.chatMessagesRepository.findOneByOrFail({ id: messageId });
|
||||
|
||||
const room = message.toRoomId ? await this.chatRoomsRepository.findOneByOrFail({ id: message.toRoomId }) : null;
|
||||
|
||||
await this.chatMessagesRepository.createQueryBuilder().update()
|
||||
.set({
|
||||
reactions: () => `array_remove("reactions", '${userId}/${reaction}')`,
|
||||
})
|
||||
.where('id = :id', { id: message.id })
|
||||
.execute();
|
||||
|
||||
// TODO: 実際に削除が行われたときのみイベントを発行する
|
||||
|
||||
if (room) {
|
||||
this.globalEventService.publishChatRoomStream(room.id, 'unreact', {
|
||||
messageId: message.id,
|
||||
user: await this.userEntityService.pack(userId),
|
||||
reaction,
|
||||
});
|
||||
} else {
|
||||
this.globalEventService.publishChatUserStream(message.fromUserId, message.toUserId!, 'unreact', {
|
||||
messageId: message.id,
|
||||
reaction,
|
||||
});
|
||||
this.globalEventService.publishChatUserStream(message.toUserId!, message.fromUserId, 'unreact', {
|
||||
messageId: message.id,
|
||||
reaction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getMyMemberships(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomMembership['id'] | null, untilId?: MiChatRoomMembership['id'] | null) {
|
||||
const query = this.queryService.makePaginationQuery(this.chatRoomMembershipsRepository.createQueryBuilder('membership'), sinceId, untilId)
|
||||
.andWhere('membership.userId = :userId', { userId });
|
||||
|
||||
const memberships = await query.take(limit).getMany();
|
||||
|
||||
return memberships;
|
||||
}
|
||||
}
|
|
@ -44,7 +44,6 @@ import { ModerationLogService } from './ModerationLogService.js';
|
|||
import { NoteCreateService } from './NoteCreateService.js';
|
||||
import { NoteDeleteService } from './NoteDeleteService.js';
|
||||
import { NotePiningService } from './NotePiningService.js';
|
||||
import { NoteReadService } from './NoteReadService.js';
|
||||
import { NotificationService } from './NotificationService.js';
|
||||
import { PollService } from './PollService.js';
|
||||
import { PushNotificationService } from './PushNotificationService.js';
|
||||
|
@ -75,6 +74,7 @@ import { ClipService } from './ClipService.js';
|
|||
import { FeaturedService } from './FeaturedService.js';
|
||||
import { FanoutTimelineService } from './FanoutTimelineService.js';
|
||||
import { ChannelFollowingService } from './ChannelFollowingService.js';
|
||||
import { ChatService } from './ChatService.js';
|
||||
import { RegistryApiService } from './RegistryApiService.js';
|
||||
import { ReversiService } from './ReversiService.js';
|
||||
|
||||
|
@ -100,6 +100,7 @@ import { AppEntityService } from './entities/AppEntityService.js';
|
|||
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
|
||||
import { BlockingEntityService } from './entities/BlockingEntityService.js';
|
||||
import { ChannelEntityService } from './entities/ChannelEntityService.js';
|
||||
import { ChatEntityService } from './entities/ChatEntityService.js';
|
||||
import { ClipEntityService } from './entities/ClipEntityService.js';
|
||||
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
|
||||
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
|
||||
|
@ -184,7 +185,6 @@ const $ModerationLogService: Provider = { provide: 'ModerationLogService', useEx
|
|||
const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService };
|
||||
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
|
||||
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
|
||||
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
||||
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
|
||||
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
|
||||
const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService };
|
||||
|
@ -221,6 +221,7 @@ const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: Fe
|
|||
const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService };
|
||||
const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
|
||||
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
|
||||
const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService };
|
||||
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
|
||||
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
|
||||
|
||||
|
@ -247,6 +248,7 @@ const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting:
|
|||
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
||||
const $BlockingEntityService: Provider = { provide: 'BlockingEntityService', useExisting: BlockingEntityService };
|
||||
const $ChannelEntityService: Provider = { provide: 'ChannelEntityService', useExisting: ChannelEntityService };
|
||||
const $ChatEntityService: Provider = { provide: 'ChatEntityService', useExisting: ChatEntityService };
|
||||
const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting: ClipEntityService };
|
||||
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
||||
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
|
||||
|
@ -333,7 +335,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
NoteCreateService,
|
||||
NoteDeleteService,
|
||||
NotePiningService,
|
||||
NoteReadService,
|
||||
NotificationService,
|
||||
PollService,
|
||||
SystemAccountService,
|
||||
|
@ -370,6 +371,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
FanoutTimelineService,
|
||||
FanoutTimelineEndpointService,
|
||||
ChannelFollowingService,
|
||||
ChatService,
|
||||
RegistryApiService,
|
||||
ReversiService,
|
||||
|
||||
|
@ -396,6 +398,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
AuthSessionEntityService,
|
||||
BlockingEntityService,
|
||||
ChannelEntityService,
|
||||
ChatEntityService,
|
||||
ClipEntityService,
|
||||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
|
@ -478,7 +481,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$NoteCreateService,
|
||||
$NoteDeleteService,
|
||||
$NotePiningService,
|
||||
$NoteReadService,
|
||||
$NotificationService,
|
||||
$PollService,
|
||||
$SystemAccountService,
|
||||
|
@ -515,6 +517,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$FanoutTimelineService,
|
||||
$FanoutTimelineEndpointService,
|
||||
$ChannelFollowingService,
|
||||
$ChatService,
|
||||
$RegistryApiService,
|
||||
$ReversiService,
|
||||
|
||||
|
@ -541,6 +544,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$AuthSessionEntityService,
|
||||
$BlockingEntityService,
|
||||
$ChannelEntityService,
|
||||
$ChatEntityService,
|
||||
$ClipEntityService,
|
||||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
|
@ -624,7 +628,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
NoteCreateService,
|
||||
NoteDeleteService,
|
||||
NotePiningService,
|
||||
NoteReadService,
|
||||
NotificationService,
|
||||
PollService,
|
||||
SystemAccountService,
|
||||
|
@ -661,6 +664,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
FanoutTimelineService,
|
||||
FanoutTimelineEndpointService,
|
||||
ChannelFollowingService,
|
||||
ChatService,
|
||||
RegistryApiService,
|
||||
ReversiService,
|
||||
|
||||
|
@ -686,6 +690,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
AuthSessionEntityService,
|
||||
BlockingEntityService,
|
||||
ChannelEntityService,
|
||||
ChatEntityService,
|
||||
ClipEntityService,
|
||||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
|
@ -768,7 +773,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$NoteCreateService,
|
||||
$NoteDeleteService,
|
||||
$NotePiningService,
|
||||
$NoteReadService,
|
||||
$NotificationService,
|
||||
$PollService,
|
||||
$SystemAccountService,
|
||||
|
@ -804,6 +808,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$FanoutTimelineService,
|
||||
$FanoutTimelineEndpointService,
|
||||
$ChannelFollowingService,
|
||||
$ChatService,
|
||||
$RegistryApiService,
|
||||
$ReversiService,
|
||||
|
||||
|
@ -829,6 +834,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$AuthSessionEntityService,
|
||||
$BlockingEntityService,
|
||||
$ChannelEntityService,
|
||||
$ChatEntityService,
|
||||
$ClipEntityService,
|
||||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
|
|
|
@ -20,7 +20,7 @@ import type { MiPage } from '@/models/Page.js';
|
|||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||
import { MiAvatarDecoration, MiChatMessage, MiChatRoom, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -72,12 +72,8 @@ export interface MainEventTypes {
|
|||
readAllNotifications: undefined;
|
||||
notificationFlushed: undefined;
|
||||
unreadNotification: Packed<'Notification'>;
|
||||
unreadMention: MiNote['id'];
|
||||
readAllUnreadMentions: undefined;
|
||||
unreadSpecifiedNote: MiNote['id'];
|
||||
readAllUnreadSpecifiedNotes: undefined;
|
||||
readAllAntennas: undefined;
|
||||
unreadAntenna: MiAntenna;
|
||||
newChatMessage: Packed<'ChatMessage'>;
|
||||
readAllAnnouncements: undefined;
|
||||
myTokenRegenerated: undefined;
|
||||
signin: {
|
||||
|
@ -163,6 +159,21 @@ export interface AdminEventTypes {
|
|||
};
|
||||
}
|
||||
|
||||
export interface ChatEventTypes {
|
||||
message: Packed<'ChatMessageLite'>;
|
||||
deleted: Packed<'ChatMessageLite'>['id'];
|
||||
react: {
|
||||
reaction: string;
|
||||
user?: Packed<'UserLite'>;
|
||||
messageId: MiChatMessage['id'];
|
||||
};
|
||||
unreact: {
|
||||
reaction: string;
|
||||
user?: Packed<'UserLite'>;
|
||||
messageId: MiChatMessage['id'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ReversiEventTypes {
|
||||
matched: {
|
||||
game: Packed<'ReversiGameDetailed'>;
|
||||
|
@ -202,7 +213,7 @@ export interface ReversiGameEventTypes {
|
|||
type Events<T extends object> = { [K in keyof T]: { type: K; body: T[K]; } };
|
||||
type EventUnionFromDictionary<
|
||||
T extends object,
|
||||
U = Events<T>
|
||||
U = Events<T>,
|
||||
> = U[keyof U];
|
||||
|
||||
type SerializedAll<T> = {
|
||||
|
@ -295,6 +306,14 @@ export type GlobalEvents = {
|
|||
name: 'notesStream';
|
||||
payload: Serialized<Packed<'Note'>>;
|
||||
};
|
||||
chatUser: {
|
||||
name: `chatUserStream:${MiUser['id']}-${MiUser['id']}`;
|
||||
payload: EventTypesToEventPayload<ChatEventTypes>;
|
||||
};
|
||||
chatRoom: {
|
||||
name: `chatRoomStream:${MiChatRoom['id']}`;
|
||||
payload: EventTypesToEventPayload<ChatEventTypes>;
|
||||
};
|
||||
reversi: {
|
||||
name: `reversiStream:${MiUser['id']}`;
|
||||
payload: EventTypesToEventPayload<ReversiEventTypes>;
|
||||
|
@ -393,6 +412,16 @@ export class GlobalEventService {
|
|||
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishChatUserStream<K extends keyof ChatEventTypes>(fromUserId: MiUser['id'], toUserId: MiUser['id'], type: K, value?: ChatEventTypes[K]): void {
|
||||
this.publish(`chatUserStream:${fromUserId}-${toUserId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishChatRoomStream<K extends keyof ChatEventTypes>(toRoomId: MiChatRoom['id'], type: K, value?: ChatEventTypes[K]): void {
|
||||
this.publish(`chatRoomStream:${toRoomId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishReversiStream<K extends keyof ReversiEventTypes>(userId: MiUser['id'], type: K, value?: ReversiEventTypes[K]): void {
|
||||
this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
|
|
|
@ -42,7 +42,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
|
@ -199,7 +198,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private globalEventService: GlobalEventService,
|
||||
private queueService: QueueService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private noteReadService: NoteReadService,
|
||||
private notificationService: NotificationService,
|
||||
private relayService: RelayService,
|
||||
private federatedInstanceService: FederatedInstanceService,
|
||||
|
@ -582,31 +580,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (!silent) {
|
||||
if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user);
|
||||
|
||||
// 未読通知を作成
|
||||
if (data.visibility === 'specified') {
|
||||
if (data.visibleUsers == null) throw new Error('invalid param');
|
||||
|
||||
for (const u of data.visibleUsers) {
|
||||
// ローカルユーザーのみ
|
||||
if (!this.userEntityService.isLocalUser(u)) continue;
|
||||
|
||||
this.noteReadService.insertNoteUnread(u.id, note, {
|
||||
isSpecified: true,
|
||||
isMentioned: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const u of mentionedUsers) {
|
||||
// ローカルユーザーのみ
|
||||
if (!this.userEntityService.isLocalUser(u)) continue;
|
||||
|
||||
this.noteReadService.insertNoteUnread(u.id, note, {
|
||||
isSpecified: false,
|
||||
isMentioned: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Pack the note
|
||||
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
|
||||
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||
|
||||
@Injectable()
|
||||
export class NoteReadService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.noteUnreadsRepository)
|
||||
private noteUnreadsRepository: NoteUnreadsRepository,
|
||||
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
@Inject(DI.noteThreadMutingsRepository)
|
||||
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async insertNoteUnread(userId: MiUser['id'], note: MiNote, params: {
|
||||
// NOTE: isSpecifiedがtrueならisMentionedは必ずfalse
|
||||
isSpecified: boolean;
|
||||
isMentioned: boolean;
|
||||
}): Promise<void> {
|
||||
//#region ミュートしているなら無視
|
||||
const mute = await this.mutingsRepository.findBy({
|
||||
muterId: userId,
|
||||
});
|
||||
if (mute.map(m => m.muteeId).includes(note.userId)) return;
|
||||
//#endregion
|
||||
|
||||
// スレッドミュート
|
||||
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||
where: {
|
||||
userId: userId,
|
||||
threadId: note.threadId ?? note.id,
|
||||
},
|
||||
});
|
||||
if (isThreadMuted) return;
|
||||
|
||||
const unread = {
|
||||
id: this.idService.gen(),
|
||||
noteId: note.id,
|
||||
userId: userId,
|
||||
isSpecified: params.isSpecified,
|
||||
isMentioned: params.isMentioned,
|
||||
noteUserId: note.userId,
|
||||
};
|
||||
|
||||
await this.noteUnreadsRepository.insert(unread);
|
||||
|
||||
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
|
||||
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
|
||||
const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } });
|
||||
|
||||
if (!exist) return;
|
||||
|
||||
if (params.isMentioned) {
|
||||
this.globalEventService.publishMainStream(userId, 'unreadMention', note.id);
|
||||
}
|
||||
if (params.isSpecified) {
|
||||
this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id);
|
||||
}
|
||||
}, () => { /* aborted, ignore it */ });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async read(
|
||||
userId: MiUser['id'],
|
||||
notes: (MiNote | Packed<'Note'>)[],
|
||||
): Promise<void> {
|
||||
if (notes.length === 0) return;
|
||||
|
||||
const noteIds = new Set<MiNote['id']>();
|
||||
|
||||
for (const note of notes) {
|
||||
if (note.mentions && note.mentions.includes(userId)) {
|
||||
noteIds.add(note.id);
|
||||
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
|
||||
noteIds.add(note.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (noteIds.size === 0) return;
|
||||
|
||||
// Remove the record
|
||||
await this.noteUnreadsRepository.delete({
|
||||
userId: userId,
|
||||
noteId: In(Array.from(noteIds)),
|
||||
});
|
||||
|
||||
// TODO: ↓まとめてクエリしたい
|
||||
|
||||
trackPromise(this.noteUnreadsRepository.countBy({
|
||||
userId: userId,
|
||||
isMentioned: true,
|
||||
}).then(mentionsCount => {
|
||||
if (mentionsCount === 0) {
|
||||
// 全て既読になったイベントを発行
|
||||
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
|
||||
}
|
||||
}));
|
||||
|
||||
trackPromise(this.noteUnreadsRepository.countBy({
|
||||
userId: userId,
|
||||
isSpecified: true,
|
||||
}).then(specifiedCount => {
|
||||
if (specifiedCount === 0) {
|
||||
// 全て既読になったイベントを発行
|
||||
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ export class QueryService {
|
|||
|
||||
// ここでいうBlockedは被Blockedの意
|
||||
@bindThis
|
||||
public generateBlockedUserQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
||||
public generateBlockedUserQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
||||
const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking')
|
||||
.select('blocking.blockerId')
|
||||
.where('blocking.blockeeId = :blockeeId', { blockeeId: me.id });
|
||||
|
@ -127,7 +127,7 @@ export class QueryService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public generateMutedUserQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void {
|
||||
public generateMutedUserQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void {
|
||||
const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: me.id });
|
||||
|
|
|
@ -63,6 +63,7 @@ export type RolePolicies = {
|
|||
canImportFollowing: boolean;
|
||||
canImportMuting: boolean;
|
||||
canImportUserLists: boolean;
|
||||
canChat: boolean;
|
||||
};
|
||||
|
||||
export const DEFAULT_POLICIES: RolePolicies = {
|
||||
|
@ -97,6 +98,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
canImportFollowing: true,
|
||||
canImportMuting: true,
|
||||
canImportUserLists: true,
|
||||
canChat: true,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
@ -400,6 +402,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
|
||||
canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
|
||||
canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
|
||||
canChat: calc('canChat', vs => vs.some(v => v === true)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -234,8 +234,8 @@ export class SearchService {
|
|||
}
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
||||
if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||
if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||
|
||||
return query.limit(pagination.limit).getMany();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { Brackets, IsNull } from 'typeorm';
|
||||
import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@ -736,4 +736,30 @@ export class UserFollowingService implements OnModuleInit {
|
|||
.where('following.followerId = :followerId', { followerId: userId })
|
||||
.getMany();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public isFollowing(followerId: MiUser['id'], followeeId: MiUser['id']) {
|
||||
return this.followingsRepository.exists({
|
||||
where: {
|
||||
followerId,
|
||||
followeeId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async isMutual(aUserId: MiUser['id'], bUserId: MiUser['id']) {
|
||||
const count = await this.followingsRepository.createQueryBuilder('following')
|
||||
.where(new Brackets(qb => {
|
||||
qb.where('following.followerId = :aUserId', { aUserId })
|
||||
.andWhere('following.followeeId = :bUserId', { bUserId });
|
||||
}))
|
||||
.orWhere(new Brackets(qb => {
|
||||
qb.where('following.followerId = :bUserId', { bUserId })
|
||||
.andWhere('following.followeeId = :aUserId', { aUserId });
|
||||
}))
|
||||
.getCount();
|
||||
|
||||
return count === 2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets, SelectQueryBuilder } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { type FollowingsRepository, MiUser, type UsersRepository } from '@/models/_.js';
|
||||
import { type FollowingsRepository, MiUser, type MutingsRepository, type UserProfilesRepository, type UsersRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -22,10 +22,19 @@ export class UserSearchService {
|
|||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
}
|
||||
|
@ -58,7 +67,7 @@ export class UserSearchService {
|
|||
* @see {@link UserSearchService#buildSearchUserNoLoginQueries}
|
||||
*/
|
||||
@bindThis
|
||||
public async search(
|
||||
public async searchByUsernameAndHost(
|
||||
params: {
|
||||
username?: string | null,
|
||||
host?: string | null,
|
||||
|
@ -202,4 +211,91 @@ export class UserSearchService {
|
|||
|
||||
return userQuery;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async search(query: string, meId: MiUser['id'] | null, options: Partial<{
|
||||
limit: number;
|
||||
offset: number;
|
||||
origin: 'local' | 'remote' | 'combined';
|
||||
}> = {}) {
|
||||
const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
|
||||
|
||||
const isUsername = query.startsWith('@') && !query.includes(' ') && query.indexOf('@', 1) === -1;
|
||||
|
||||
let users: MiUser[] = [];
|
||||
|
||||
const mutingQuery = meId == null ? null : this.mutingsRepository.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: meId });
|
||||
|
||||
const nameQuery = this.usersRepository.createQueryBuilder('user')
|
||||
.where(new Brackets(qb => {
|
||||
qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(query) + '%' });
|
||||
|
||||
if (isUsername) {
|
||||
qb.orWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(query.replace('@', '').toLowerCase()) + '%' });
|
||||
} else if (this.userEntityService.validateLocalUsername(query)) { // Also search username if it qualifies as username
|
||||
qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(query.toLowerCase()) + '%' });
|
||||
}
|
||||
}))
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('user.updatedAt IS NULL')
|
||||
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
|
||||
}))
|
||||
.andWhere('user.isSuspended = FALSE');
|
||||
|
||||
if (mutingQuery) {
|
||||
nameQuery.andWhere(`user.id NOT IN (${mutingQuery.getQuery()})`);
|
||||
nameQuery.setParameters(mutingQuery.getParameters());
|
||||
}
|
||||
|
||||
if (options.origin === 'local') {
|
||||
nameQuery.andWhere('user.host IS NULL');
|
||||
} else if (options.origin === 'remote') {
|
||||
nameQuery.andWhere('user.host IS NOT NULL');
|
||||
}
|
||||
|
||||
users = await nameQuery
|
||||
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
|
||||
.limit(options.limit)
|
||||
.offset(options.offset)
|
||||
.getMany();
|
||||
|
||||
if (users.length < (options.limit ?? 30)) {
|
||||
const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
|
||||
.select('prof.userId')
|
||||
.where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(query) + '%' });
|
||||
|
||||
if (mutingQuery) {
|
||||
profQuery.andWhere(`prof.userId NOT IN (${mutingQuery.getQuery()})`);
|
||||
profQuery.setParameters(mutingQuery.getParameters());
|
||||
}
|
||||
|
||||
if (options.origin === 'local') {
|
||||
profQuery.andWhere('prof.userHost IS NULL');
|
||||
} else if (options.origin === 'remote') {
|
||||
profQuery.andWhere('prof.userHost IS NOT NULL');
|
||||
}
|
||||
|
||||
const userQuery = this.usersRepository.createQueryBuilder('user')
|
||||
.where(`user.id IN (${ profQuery.getQuery() })`)
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('user.updatedAt IS NULL')
|
||||
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
|
||||
}))
|
||||
.andWhere('user.isSuspended = FALSE')
|
||||
.setParameters(profQuery.getParameters());
|
||||
|
||||
users = users.concat(await userQuery
|
||||
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
|
||||
.limit(options.limit)
|
||||
.offset(options.offset)
|
||||
.getMany(),
|
||||
);
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
|
|||
requireSigninToViewContents: false,
|
||||
makeNotesFollowersOnlyBefore: null,
|
||||
makeNotesHiddenBefore: null,
|
||||
chatScope: 'mutual',
|
||||
emojis: [],
|
||||
score: 0,
|
||||
host: null,
|
||||
|
@ -461,6 +462,8 @@ export class WebhookTestService {
|
|||
publicReactions: true,
|
||||
followersVisibility: 'public',
|
||||
followingVisibility: 'public',
|
||||
chatScope: 'mutual',
|
||||
canChat: true,
|
||||
twoFactorEnabled: false,
|
||||
usePasswordLessLogin: false,
|
||||
securityKeys: false,
|
||||
|
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser, ChatMessagesRepository, MiChatMessage, ChatRoomsRepository, MiChatRoom, MiChatRoomInvitation, ChatRoomInvitationsRepository, MiChatRoomMembership, ChatRoomMembershipsRepository } from '@/models/_.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { } from '@/models/Blocking.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class ChatEntityService {
|
||||
constructor(
|
||||
@Inject(DI.chatMessagesRepository)
|
||||
private chatMessagesRepository: ChatMessagesRepository,
|
||||
|
||||
@Inject(DI.chatRoomsRepository)
|
||||
private chatRoomsRepository: ChatRoomsRepository,
|
||||
|
||||
@Inject(DI.chatRoomInvitationsRepository)
|
||||
private chatRoomInvitationsRepository: ChatRoomInvitationsRepository,
|
||||
|
||||
@Inject(DI.chatRoomMembershipsRepository)
|
||||
private chatRoomMembershipsRepository: ChatRoomMembershipsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessageDetailed(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
me?: { id: MiUser['id'] },
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles?: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
packedUsers?: Map<MiChatMessage['id'], Packed<'UserLite'>>;
|
||||
packedRooms?: Map<MiChatMessage['toRoomId'], Packed<'ChatRoom'> | null>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessage'>> {
|
||||
const packedUsers = options?._hint_?.packedUsers;
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
const packedRooms = options?._hint_?.packedRooms;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = [];
|
||||
|
||||
for (const record of message.reactions) {
|
||||
const [userId, reaction] = record.split('/');
|
||||
reactions.push({
|
||||
user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId),
|
||||
reaction,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId, me),
|
||||
toUserId: message.toUserId,
|
||||
toUser: message.toUserId ? (packedUsers?.get(message.toUserId) ?? await this.userEntityService.pack(message.toUser ?? message.toUserId, me)) : undefined,
|
||||
toRoomId: message.toRoomId,
|
||||
toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||
reactions,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessagesDetailed(
|
||||
messages: MiChatMessage[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const excludeMe = (x: MiUser | string) => {
|
||||
if (typeof x === 'string') {
|
||||
return x !== me.id;
|
||||
} else {
|
||||
return x.id !== me.id;
|
||||
}
|
||||
};
|
||||
|
||||
const users = [
|
||||
...messages.map((m) => m.fromUser ?? m.fromUserId).filter(excludeMe),
|
||||
...messages.map((m) => m.toUser ?? m.toUserId).filter(x => x != null).filter(excludeMe),
|
||||
];
|
||||
|
||||
const reactedUserIds = messages.flatMap(x => x.reactions.map(r => r.split('/')[0]));
|
||||
|
||||
for (const reactedUserId of reactedUserIds) {
|
||||
if (!users.some(x => typeof x === 'string' ? x === reactedUserId : x.id === reactedUserId)) {
|
||||
users.push(reactedUserId);
|
||||
}
|
||||
}
|
||||
|
||||
const [packedUsers, packedFiles, packedRooms] = await Promise.all([
|
||||
this.userEntityService.packMany(users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null))
|
||||
.then(files => new Map(files.map(f => [f.id, f]))),
|
||||
this.packRooms(messages.map(m => m.toRoom ?? m.toRoomId).filter(x => x != null), me)
|
||||
.then(rooms => new Map(rooms.map(r => [r.id, r]))),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packMessageDetailed(message, me, { _hint_: { packedUsers, packedFiles, packedRooms } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessageLiteFor1on1(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const reactions: { reaction: string; }[] = [];
|
||||
|
||||
for (const record of message.reactions) {
|
||||
const [userId, reaction] = record.split('/');
|
||||
reactions.push({
|
||||
reaction,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
toUserId: message.toUserId,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||
reactions,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessagesLiteFor1on1(
|
||||
messages: MiChatMessage[],
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const [packedFiles] = await Promise.all([
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null))
|
||||
.then(files => new Map(files.map(f => [f.id, f]))),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packMessageLiteFor1on1(message, { _hint_: { packedFiles } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessageLiteForRoom(
|
||||
src: MiChatMessage['id'] | MiChatMessage,
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatMessageLite'>> {
|
||||
const packedFiles = options?._hint_?.packedFiles;
|
||||
const packedUsers = options?._hint_?.packedUsers;
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = [];
|
||||
|
||||
for (const record of message.reactions) {
|
||||
const [userId, reaction] = record.split('/');
|
||||
reactions.push({
|
||||
user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId),
|
||||
reaction,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||
text: message.text,
|
||||
fromUserId: message.fromUserId,
|
||||
fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId),
|
||||
toRoomId: message.toRoomId,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||
reactions,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMessagesLiteForRoom(
|
||||
messages: MiChatMessage[],
|
||||
) {
|
||||
if (messages.length === 0) return [];
|
||||
|
||||
const users = messages.map(x => x.fromUser ?? x.fromUserId);
|
||||
const reactedUserIds = messages.flatMap(x => x.reactions.map(r => r.split('/')[0]));
|
||||
|
||||
for (const reactedUserId of reactedUserIds) {
|
||||
if (!users.some(x => typeof x === 'string' ? x === reactedUserId : x.id === reactedUserId)) {
|
||||
users.push(reactedUserId);
|
||||
}
|
||||
}
|
||||
|
||||
const [packedUsers, packedFiles] = await Promise.all([
|
||||
this.userEntityService.packMany(users)
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null))
|
||||
.then(files => new Map(files.map(f => [f.id, f]))),
|
||||
]);
|
||||
|
||||
return Promise.all(messages.map(message => this.packMessageLiteForRoom(message, { _hint_: { packedFiles, packedUsers } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoom(
|
||||
src: MiChatRoom['id'] | MiChatRoom,
|
||||
me?: { id: MiUser['id'] },
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedOwners: Map<MiChatRoom['id'], Packed<'UserLite'>>;
|
||||
memberships?: Map<MiChatRoom['id'], MiChatRoomMembership | null | undefined>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatRoom'>> {
|
||||
const room = typeof src === 'object' ? src : await this.chatRoomsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const membership = me && me.id !== room.ownerId ? (options?._hint_?.memberships?.get(room.id) ?? await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null;
|
||||
|
||||
return {
|
||||
id: room.id,
|
||||
createdAt: this.idService.parse(room.id).date.toISOString(),
|
||||
name: room.name,
|
||||
description: room.description,
|
||||
ownerId: room.ownerId,
|
||||
owner: options?._hint_?.packedOwners.get(room.ownerId) ?? await this.userEntityService.pack(room.owner ?? room.ownerId, me),
|
||||
isMuted: membership != null ? membership.isMuted : false,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRooms(
|
||||
rooms: (MiChatRoom | MiChatRoom['id'])[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
if (rooms.length === 0) return [];
|
||||
|
||||
const _rooms = rooms.filter((room): room is MiChatRoom => typeof room !== 'string');
|
||||
if (_rooms.length !== rooms.length) {
|
||||
_rooms.push(
|
||||
...await this.chatRoomsRepository.find({
|
||||
where: {
|
||||
id: In(rooms.filter((room): room is string => typeof room === 'string')),
|
||||
},
|
||||
relations: ['owner'],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const owners = _rooms.map(x => x.owner ?? x.ownerId);
|
||||
|
||||
const [packedOwners, memberships] = await Promise.all([
|
||||
this.userEntityService.packMany(owners, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.chatRoomMembershipsRepository.find({
|
||||
where: {
|
||||
roomId: In(_rooms.map(x => x.id)),
|
||||
userId: me.id,
|
||||
},
|
||||
}).then(memberships => new Map(_rooms.map(r => [r.id, memberships.find(m => m.roomId === r.id)]))),
|
||||
]);
|
||||
|
||||
return Promise.all(_rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners, memberships } })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoomInvitation(
|
||||
src: MiChatRoomInvitation['id'] | MiChatRoomInvitation,
|
||||
me: { id: MiUser['id'] },
|
||||
options?: {
|
||||
_hint_?: {
|
||||
packedRooms: Map<MiChatRoomInvitation['roomId'], Packed<'ChatRoom'>>;
|
||||
packedUsers: Map<MiChatRoomInvitation['id'], Packed<'UserLite'>>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatRoomInvitation'>> {
|
||||
const invitation = typeof src === 'object' ? src : await this.chatRoomInvitationsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: invitation.id,
|
||||
createdAt: this.idService.parse(invitation.id).date.toISOString(),
|
||||
roomId: invitation.roomId,
|
||||
room: options?._hint_?.packedRooms.get(invitation.roomId) ?? await this.packRoom(invitation.room ?? invitation.roomId, me),
|
||||
userId: invitation.userId,
|
||||
user: options?._hint_?.packedUsers.get(invitation.userId) ?? await this.userEntityService.pack(invitation.user ?? invitation.userId, me),
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoomInvitations(
|
||||
invitations: MiChatRoomInvitation[],
|
||||
me: { id: MiUser['id'] },
|
||||
) {
|
||||
if (invitations.length === 0) return [];
|
||||
|
||||
return Promise.all(invitations.map(invitation => this.packRoomInvitation(invitation, me)));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoomMembership(
|
||||
src: MiChatRoomMembership['id'] | MiChatRoomMembership,
|
||||
me: { id: MiUser['id'] },
|
||||
options?: {
|
||||
populateUser?: boolean;
|
||||
populateRoom?: boolean;
|
||||
_hint_?: {
|
||||
packedRooms: Map<MiChatRoomMembership['roomId'], Packed<'ChatRoom'>>;
|
||||
packedUsers: Map<MiChatRoomMembership['id'], Packed<'UserLite'>>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'ChatRoomMembership'>> {
|
||||
const membership = typeof src === 'object' ? src : await this.chatRoomMembershipsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: membership.id,
|
||||
createdAt: this.idService.parse(membership.id).date.toISOString(),
|
||||
userId: membership.userId,
|
||||
user: options?.populateUser ? (options._hint_?.packedUsers.get(membership.userId) ?? await this.userEntityService.pack(membership.user ?? membership.userId, me)) : undefined,
|
||||
roomId: membership.roomId,
|
||||
room: options?.populateRoom ? (options._hint_?.packedRooms.get(membership.roomId) ?? await this.packRoom(membership.room ?? membership.roomId, me)) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoomMemberships(
|
||||
memberships: MiChatRoomMembership[],
|
||||
me: { id: MiUser['id'] },
|
||||
options: {
|
||||
populateUser?: boolean;
|
||||
populateRoom?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
if (memberships.length === 0) return [];
|
||||
|
||||
const users = memberships.map(x => x.user ?? x.userId);
|
||||
const rooms = memberships.map(x => x.room ?? x.roomId);
|
||||
|
||||
const [packedUsers, packedRooms] = await Promise.all([
|
||||
this.userEntityService.packMany(users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u]))),
|
||||
this.packRooms(rooms, me)
|
||||
.then(rooms => new Map(rooms.map(r => [r.id, r]))),
|
||||
]);
|
||||
|
||||
return Promise.all(memberships.map(membership => this.packRoomMembership(membership, me, { ...options, _hint_: { packedUsers, packedRooms } })));
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { RoleEntityService } from './RoleEntityService.js';
|
||||
import { ChatEntityService } from './ChatEntityService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { UserEntityService } from './UserEntityService.js';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
|
@ -27,6 +28,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
private userEntityService: UserEntityService;
|
||||
private noteEntityService: NoteEntityService;
|
||||
private roleEntityService: RoleEntityService;
|
||||
private chatEntityService: ChatEntityService;
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
|
@ -41,9 +43,6 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
private followRequestsRepository: FollowRequestsRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
|
||||
//private userEntityService: UserEntityService,
|
||||
//private noteEntityService: NoteEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -51,6 +50,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
this.userEntityService = this.moduleRef.get('UserEntityService');
|
||||
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
||||
this.roleEntityService = this.moduleRef.get('RoleEntityService');
|
||||
this.chatEntityService = this.moduleRef.get('ChatEntityService');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,6 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
async #packInternal <T extends MiNotification | MiGroupedNotification> (
|
||||
src: T,
|
||||
meId: MiUser['id'],
|
||||
|
||||
options: {
|
||||
checkValidNotifier?: boolean;
|
||||
},
|
||||
|
@ -92,7 +91,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
// if the user has been deleted, don't show this notification
|
||||
if (needsUser && !userIfNeed) return null;
|
||||
|
||||
// #region Grouped notifications
|
||||
//#region Grouped notifications
|
||||
if (notification.type === 'reaction:grouped') {
|
||||
const reactions = (await Promise.all(notification.reactions.map(async reaction => {
|
||||
const user = hint?.packedUsers != null
|
||||
|
@ -137,7 +136,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
users,
|
||||
});
|
||||
}
|
||||
// #endregion
|
||||
//#endregion
|
||||
|
||||
const needsRole = notification.type === 'roleAssigned';
|
||||
const role = needsRole ? await this.roleEntityService.pack(notification.roleId) : undefined;
|
||||
|
@ -146,6 +145,13 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
return null;
|
||||
}
|
||||
|
||||
const needsChatRoomInvitation = notification.type === 'chatRoomInvitationReceived';
|
||||
const chatRoomInvitation = needsChatRoomInvitation ? await this.chatEntityService.packRoomInvitation(notification.invitationId, { id: meId }).catch(() => null) : undefined;
|
||||
// if the invitation has been deleted, don't show this notification
|
||||
if (needsChatRoomInvitation && !chatRoomInvitation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await awaitAll({
|
||||
id: notification.id,
|
||||
createdAt: new Date(notification.createdAt).toISOString(),
|
||||
|
@ -159,6 +165,9 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
...(notification.type === 'roleAssigned' ? {
|
||||
role: role,
|
||||
} : {}),
|
||||
...(notification.type === 'chatRoomInvitationReceived' ? {
|
||||
invitation: chatRoomInvitation,
|
||||
} : {}),
|
||||
...(notification.type === 'followRequestAccepted' ? {
|
||||
message: notification.message,
|
||||
} : {}),
|
||||
|
@ -236,7 +245,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
public async pack(
|
||||
src: MiNotification | MiGroupedNotification,
|
||||
meId: MiUser['id'],
|
||||
|
||||
|
||||
options: {
|
||||
checkValidNotifier?: boolean;
|
||||
},
|
||||
|
|
|
@ -32,7 +32,6 @@ import type {
|
|||
MiUserNotePining,
|
||||
MiUserProfile,
|
||||
MutingsRepository,
|
||||
NoteUnreadsRepository,
|
||||
RenoteMutingsRepository,
|
||||
UserMemoRepository,
|
||||
UserNotePiningsRepository,
|
||||
|
@ -48,9 +47,9 @@ import { IdService } from '@/core/IdService.js';
|
|||
import type { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||
import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
import type { PageEntityService } from './PageEntityService.js';
|
||||
|
||||
const Ajv = _Ajv.default;
|
||||
|
@ -94,6 +93,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
private federatedInstanceService: FederatedInstanceService;
|
||||
private idService: IdService;
|
||||
private avatarDecorationService: AvatarDecorationService;
|
||||
private chatService: ChatService;
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
|
@ -128,9 +128,6 @@ export class UserEntityService implements OnModuleInit {
|
|||
@Inject(DI.renoteMutingsRepository)
|
||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||
|
||||
@Inject(DI.noteUnreadsRepository)
|
||||
private noteUnreadsRepository: NoteUnreadsRepository,
|
||||
|
||||
@Inject(DI.userNotePiningsRepository)
|
||||
private userNotePiningsRepository: UserNotePiningsRepository,
|
||||
|
||||
|
@ -152,6 +149,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
||||
this.idService = this.moduleRef.get('IdService');
|
||||
this.avatarDecorationService = this.moduleRef.get('AvatarDecorationService');
|
||||
this.chatService = this.moduleRef.get('ChatService');
|
||||
}
|
||||
|
||||
//#region Validators
|
||||
|
@ -558,6 +556,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
|
||||
followersVisibility: profile!.followersVisibility,
|
||||
followingVisibility: profile!.followingVisibility,
|
||||
chatScope: user.chatScope,
|
||||
canChat: this.roleService.getUserPolicies(user.id).then(r => r.canChat),
|
||||
roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
|
@ -598,14 +598,9 @@ export class UserEntityService implements OnModuleInit {
|
|||
isDeleted: user.isDeleted,
|
||||
twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
|
||||
hideOnlineStatus: user.hideOnlineStatus,
|
||||
hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
|
||||
where: { userId: user.id, isSpecified: true },
|
||||
take: 1,
|
||||
}).then(count => count > 0),
|
||||
hasUnreadMentions: this.noteUnreadsRepository.count({
|
||||
where: { userId: user.id, isMentioned: true },
|
||||
take: 1,
|
||||
}).then(count => count > 0),
|
||||
hasUnreadSpecifiedNotes: false, // 後方互換性のため
|
||||
hasUnreadMentions: false, // 後方互換性のため
|
||||
hasUnreadChatMessages: this.chatService.hasUnreadMessages(user.id),
|
||||
hasUnreadAnnouncement: unreadAnnouncements!.length > 0,
|
||||
unreadAnnouncements,
|
||||
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
|
||||
|
|
|
@ -24,7 +24,6 @@ export const DI = {
|
|||
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
|
||||
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
|
||||
noteReactionsRepository: Symbol('noteReactionsRepository'),
|
||||
noteUnreadsRepository: Symbol('noteUnreadsRepository'),
|
||||
pollsRepository: Symbol('pollsRepository'),
|
||||
pollVotesRepository: Symbol('pollVotesRepository'),
|
||||
userProfilesRepository: Symbol('userProfilesRepository'),
|
||||
|
@ -83,6 +82,11 @@ export const DI = {
|
|||
flashsRepository: Symbol('flashsRepository'),
|
||||
flashLikesRepository: Symbol('flashLikesRepository'),
|
||||
userMemosRepository: Symbol('userMemosRepository'),
|
||||
chatMessagesRepository: Symbol('chatMessagesRepository'),
|
||||
chatApprovalsRepository: Symbol('chatApprovalsRepository'),
|
||||
chatRoomsRepository: Symbol('chatRoomsRepository'),
|
||||
chatRoomMembershipsRepository: Symbol('chatRoomMembershipsRepository'),
|
||||
chatRoomInvitationsRepository: Symbol('chatRoomInvitationsRepository'),
|
||||
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
|
||||
reversiGamesRepository: Symbol('reversiGamesRepository'),
|
||||
//#endregion
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { WritableStream } from 'node:stream/web';
|
||||
import type { PathLike } from 'node:fs';
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,6 +63,10 @@ import {
|
|||
} from '@/models/json-schema/meta.js';
|
||||
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
||||
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
|
||||
import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js';
|
||||
import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js';
|
||||
import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js';
|
||||
import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
|
||||
|
||||
export const refs = {
|
||||
UserLite: packedUserLiteSchema,
|
||||
|
@ -120,6 +124,11 @@ export const refs = {
|
|||
MetaDetailed: packedMetaDetailedSchema,
|
||||
SystemWebhook: packedSystemWebhookSchema,
|
||||
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
||||
ChatMessage: packedChatMessageSchema,
|
||||
ChatMessageLite: packedChatMessageLiteSchema,
|
||||
ChatRoom: packedChatRoomSchema,
|
||||
ChatRoomInvitation: packedChatRoomInvitationSchema,
|
||||
ChatRoomMembership: packedChatRoomMembershipSchema,
|
||||
};
|
||||
|
||||
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
|
||||
|
@ -171,11 +180,11 @@ export interface Schema extends OfSchema {
|
|||
|
||||
type RequiredPropertyNames<s extends Obj> = {
|
||||
[K in keyof s]:
|
||||
// K is not optional
|
||||
s[K]['optional'] extends false ? K :
|
||||
// K has default value
|
||||
s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K :
|
||||
never
|
||||
// K is not optional
|
||||
s[K]['optional'] extends false ? K :
|
||||
// K has default value
|
||||
s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K :
|
||||
never
|
||||
}[keyof s];
|
||||
|
||||
export type Obj = Record<string, Schema>;
|
||||
|
@ -214,18 +223,18 @@ type ObjectSchemaTypeDef<p extends Schema> =
|
|||
p['anyOf'] extends ReadonlyArray<Schema> ? p['anyOf'][number]['required'] extends ReadonlyArray<keyof p['properties']> ?
|
||||
UnionObjType<p['properties'], NonNullable<p['anyOf'][number]['required']>> & ObjType<p['properties'], NonNullable<p['required']>>
|
||||
: never
|
||||
: ObjType<p['properties'], NonNullable<p['required']>>
|
||||
:
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
|
||||
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
|
||||
p['additionalProperties'] extends true ? Record<string, any> :
|
||||
p['additionalProperties'] extends Schema ?
|
||||
p['additionalProperties'] extends infer AdditionalProperties ?
|
||||
AdditionalProperties extends Schema ?
|
||||
Record<string, SchemaType<AdditionalProperties>> :
|
||||
: ObjType<p['properties'], NonNullable<p['required']>>
|
||||
:
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
|
||||
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
|
||||
p['additionalProperties'] extends true ? Record<string, any> :
|
||||
p['additionalProperties'] extends Schema ?
|
||||
p['additionalProperties'] extends infer AdditionalProperties ?
|
||||
AdditionalProperties extends Schema ?
|
||||
Record<string, SchemaType<AdditionalProperties>> :
|
||||
never :
|
||||
never :
|
||||
never :
|
||||
any;
|
||||
any;
|
||||
|
||||
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;
|
||||
|
||||
|
@ -235,30 +244,30 @@ export type SchemaTypeDef<p extends Schema> =
|
|||
p['type'] extends 'number' ? number :
|
||||
p['type'] extends 'string' ? (
|
||||
p['enum'] extends readonly (string | null)[] ?
|
||||
p['enum'][number] :
|
||||
p['format'] extends 'date-time' ? string : // Dateにする??
|
||||
string
|
||||
p['enum'][number] :
|
||||
p['format'] extends 'date-time' ? string : // Dateにする??
|
||||
string
|
||||
) :
|
||||
p['type'] extends 'boolean' ? boolean :
|
||||
p['type'] extends 'object' ? ObjectSchemaTypeDef<p> :
|
||||
p['type'] extends 'array' ? (
|
||||
p['items'] extends OfSchema ? (
|
||||
p['items']['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
|
||||
p['items']['oneOf'] extends ReadonlyArray<Schema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
|
||||
p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
|
||||
never
|
||||
p['type'] extends 'boolean' ? boolean :
|
||||
p['type'] extends 'object' ? ObjectSchemaTypeDef<p> :
|
||||
p['type'] extends 'array' ? (
|
||||
p['items'] extends OfSchema ? (
|
||||
p['items']['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
|
||||
p['items']['oneOf'] extends ReadonlyArray<Schema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
|
||||
p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
|
||||
never
|
||||
) :
|
||||
p['prefixItems'] extends ReadonlyArray<Schema> ? (
|
||||
p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] :
|
||||
p['items'] extends false ? ArrayToTuple<p['prefixItems']> :
|
||||
p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> :
|
||||
[...ArrayToTuple<p['prefixItems']>, ...unknown[]]
|
||||
) :
|
||||
p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
|
||||
any[]
|
||||
) :
|
||||
p['prefixItems'] extends ReadonlyArray<Schema> ? (
|
||||
p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] :
|
||||
p['items'] extends false ? ArrayToTuple<p['prefixItems']> :
|
||||
p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> :
|
||||
[...ArrayToTuple<p['prefixItems']>, ...unknown[]]
|
||||
) :
|
||||
p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
|
||||
any[]
|
||||
) :
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & PartialIntersection<UnionSchemaType<p['anyOf']>> :
|
||||
p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> :
|
||||
any;
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & PartialIntersection<UnionSchemaType<p['anyOf']>> :
|
||||
p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> :
|
||||
any;
|
||||
|
||||
export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
@Entity('chat_approval')
|
||||
@Index(['userId', 'otherId'], { unique: true })
|
||||
export class MiChatApproval {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public otherId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public other: MiUser | null;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiDriveFile } from './DriveFile.js';
|
||||
import { MiChatRoom } from './ChatRoom.js';
|
||||
|
||||
@Entity('chat_message')
|
||||
export class MiChatMessage {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public fromUserId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public fromUser: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(), nullable: true,
|
||||
})
|
||||
public toUserId: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public toUser: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(), nullable: true,
|
||||
})
|
||||
public toRoomId: MiChatRoom['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public toRoom: MiChatRoom | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 4096, nullable: true,
|
||||
})
|
||||
public text: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true,
|
||||
})
|
||||
public uri: string | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true, default: '{}',
|
||||
})
|
||||
public reads: MiUser['id'][];
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public fileId: MiDriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
public file: MiDriveFile | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, array: true, default: '{}',
|
||||
})
|
||||
public reactions: string[];
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
@Entity('chat_room')
|
||||
export class MiChatRoom {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public ownerId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public owner: MiUser | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 2048, default: '',
|
||||
})
|
||||
public description: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isArchived: boolean;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiChatRoom } from './ChatRoom.js';
|
||||
|
||||
@Entity('chat_room_invitation')
|
||||
@Index(['userId', 'roomId'], { unique: true })
|
||||
export class MiChatRoomInvitation {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public roomId: MiChatRoom['id'];
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public room: MiChatRoom | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public ignored: boolean;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiChatRoom } from './ChatRoom.js';
|
||||
|
||||
@Entity('chat_room_membership')
|
||||
@Index(['userId', 'roomId'], { unique: true })
|
||||
export class MiChatRoomMembership {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public roomId: MiChatRoom['id'];
|
||||
|
||||
@ManyToOne(type => MiChatRoom, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public room: MiChatRoom | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isMuted: boolean;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiNote } from './Note.js';
|
||||
import type { MiChannel } from './Channel.js';
|
||||
|
||||
@Entity('note_unread')
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
export class MiNoteUnread {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: MiUser | null;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public noteId: MiNote['id'];
|
||||
|
||||
@ManyToOne(type => MiNote, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: MiNote | null;
|
||||
|
||||
/**
|
||||
* メンションか否か
|
||||
*/
|
||||
@Index()
|
||||
@Column('boolean')
|
||||
public isMentioned: boolean;
|
||||
|
||||
/**
|
||||
* ダイレクト投稿か否か
|
||||
*/
|
||||
@Index()
|
||||
@Column('boolean')
|
||||
public isSpecified: boolean;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: '[Denormalized]',
|
||||
})
|
||||
public noteUserId: MiUser['id'];
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: '[Denormalized]',
|
||||
})
|
||||
public noteChannelId: MiChannel['id'] | null;
|
||||
//#endregion
|
||||
}
|
|
@ -75,6 +75,12 @@ export type MiNotification = {
|
|||
id: string;
|
||||
createdAt: string;
|
||||
roleId: MiRole['id'];
|
||||
} | {
|
||||
type: 'chatRoomInvitationReceived';
|
||||
id: string;
|
||||
createdAt: string;
|
||||
notifierId: MiUser['id'];
|
||||
invitationId: string;
|
||||
} | {
|
||||
type: 'achievementEarned';
|
||||
id: string;
|
||||
|
|
|
@ -42,7 +42,6 @@ import {
|
|||
MiNoteFavorite,
|
||||
MiNoteReaction,
|
||||
MiNoteThreadMuting,
|
||||
MiNoteUnread,
|
||||
MiPage,
|
||||
MiPageLike,
|
||||
MiPasswordResetRequest,
|
||||
|
@ -78,6 +77,11 @@ import {
|
|||
MiUserPublickey,
|
||||
MiUserSecurityKey,
|
||||
MiWebhook,
|
||||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiChatRoomInvitation,
|
||||
MiChatApproval,
|
||||
} from './_.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
@ -136,12 +140,6 @@ const $noteReactionsRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $noteUnreadsRepository: Provider = {
|
||||
provide: DI.noteUnreadsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteUnread).extend(miRepository as MiRepository<MiNoteUnread>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $pollsRepository: Provider = {
|
||||
provide: DI.pollsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPoll).extend(miRepository as MiRepository<MiPoll>),
|
||||
|
@ -288,7 +286,7 @@ const $swSubscriptionsRepository: Provider = {
|
|||
|
||||
const $systemAccountsRepository: Provider = {
|
||||
provide: DI.systemAccountsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSystemAccount),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSystemAccount).extend(miRepository as MiRepository<MiSystemAccount>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
|
@ -306,7 +304,7 @@ const $abuseUserReportsRepository: Provider = {
|
|||
|
||||
const $abuseReportNotificationRecipientRepository: Provider = {
|
||||
provide: DI.abuseReportNotificationRecipientRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAbuseReportNotificationRecipient),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAbuseReportNotificationRecipient).extend(miRepository as MiRepository<MiAbuseReportNotificationRecipient>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
|
@ -438,7 +436,7 @@ const $webhooksRepository: Provider = {
|
|||
|
||||
const $systemWebhooksRepository: Provider = {
|
||||
provide: DI.systemWebhooksRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSystemWebhook),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSystemWebhook).extend(miRepository as MiRepository<MiSystemWebhook>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
|
@ -490,6 +488,36 @@ const $userMemosRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatMessagesRepository: Provider = {
|
||||
provide: DI.chatMessagesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatMessage).extend(miRepository as MiRepository<MiChatMessage>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatRoomsRepository: Provider = {
|
||||
provide: DI.chatRoomsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatRoom).extend(miRepository as MiRepository<MiChatRoom>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatRoomMembershipsRepository: Provider = {
|
||||
provide: DI.chatRoomMembershipsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatRoomMembership).extend(miRepository as MiRepository<MiChatRoomMembership>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatRoomInvitationsRepository: Provider = {
|
||||
provide: DI.chatRoomInvitationsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatRoomInvitation).extend(miRepository as MiRepository<MiChatRoomInvitation>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $chatApprovalsRepository: Provider = {
|
||||
provide: DI.chatApprovalsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChatApproval).extend(miRepository as MiRepository<MiChatApproval>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $bubbleGameRecordsRepository: Provider = {
|
||||
provide: DI.bubbleGameRecordsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord).extend(miRepository as MiRepository<MiBubbleGameRecord>),
|
||||
|
@ -514,7 +542,6 @@ const $reversiGamesRepository: Provider = {
|
|||
$noteFavoritesRepository,
|
||||
$noteThreadMutingsRepository,
|
||||
$noteReactionsRepository,
|
||||
$noteUnreadsRepository,
|
||||
$pollsRepository,
|
||||
$pollVotesRepository,
|
||||
$userProfilesRepository,
|
||||
|
@ -573,6 +600,11 @@ const $reversiGamesRepository: Provider = {
|
|||
$flashsRepository,
|
||||
$flashLikesRepository,
|
||||
$userMemosRepository,
|
||||
$chatMessagesRepository,
|
||||
$chatRoomsRepository,
|
||||
$chatRoomMembershipsRepository,
|
||||
$chatRoomInvitationsRepository,
|
||||
$chatApprovalsRepository,
|
||||
$bubbleGameRecordsRepository,
|
||||
$reversiGamesRepository,
|
||||
],
|
||||
|
@ -586,7 +618,6 @@ const $reversiGamesRepository: Provider = {
|
|||
$noteFavoritesRepository,
|
||||
$noteThreadMutingsRepository,
|
||||
$noteReactionsRepository,
|
||||
$noteUnreadsRepository,
|
||||
$pollsRepository,
|
||||
$pollVotesRepository,
|
||||
$userProfilesRepository,
|
||||
|
@ -645,6 +676,11 @@ const $reversiGamesRepository: Provider = {
|
|||
$flashsRepository,
|
||||
$flashLikesRepository,
|
||||
$userMemosRepository,
|
||||
$chatMessagesRepository,
|
||||
$chatRoomsRepository,
|
||||
$chatRoomMembershipsRepository,
|
||||
$chatRoomInvitationsRepository,
|
||||
$chatApprovalsRepository,
|
||||
$bubbleGameRecordsRepository,
|
||||
$reversiGamesRepository,
|
||||
],
|
||||
|
|
|
@ -225,6 +225,17 @@ export class MiUser {
|
|||
})
|
||||
public emojis: string[];
|
||||
|
||||
// チャットを許可する相手
|
||||
// everyone: 誰からでも
|
||||
// followers: フォロワーのみ
|
||||
// following: フォローしているユーザーのみ
|
||||
// mutual: 相互フォローのみ
|
||||
// none: 誰からも受け付けない
|
||||
@Column('varchar', {
|
||||
length: 128, default: 'mutual',
|
||||
})
|
||||
public chatScope: 'everyone' | 'followers' | 'following' | 'mutual' | 'none';
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder, TypeORMError } from 'typeorm';
|
||||
import { DriverUtils } from 'typeorm/driver/DriverUtils.js';
|
||||
import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm';
|
||||
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
||||
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
||||
import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
||||
import { ObjectUtils } from 'typeorm/util/ObjectUtils.js';
|
||||
import { OrmUtils } from 'typeorm/util/OrmUtils.js';
|
||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||
|
@ -43,7 +40,6 @@ import { MiNote } from '@/models/Note.js';
|
|||
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
|
||||
import { MiNoteReaction } from '@/models/NoteReaction.js';
|
||||
import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js';
|
||||
import { MiNoteUnread } from '@/models/NoteUnread.js';
|
||||
import { MiPage } from '@/models/Page.js';
|
||||
import { MiPageLike } from '@/models/PageLike.js';
|
||||
import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js';
|
||||
|
@ -78,6 +74,11 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
|||
import { MiFlash } from '@/models/Flash.js';
|
||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||
import { MiChatApproval } from '@/models/ChatApproval.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||
|
@ -159,7 +160,6 @@ export {
|
|||
MiNoteFavorite,
|
||||
MiNoteReaction,
|
||||
MiNoteThreadMuting,
|
||||
MiNoteUnread,
|
||||
MiPage,
|
||||
MiPageLike,
|
||||
MiPasswordResetRequest,
|
||||
|
@ -194,6 +194,11 @@ export {
|
|||
MiFlash,
|
||||
MiFlashLike,
|
||||
MiUserMemo,
|
||||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiChatRoomInvitation,
|
||||
MiChatApproval,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
};
|
||||
|
@ -231,7 +236,6 @@ export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
|
|||
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
|
||||
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
|
||||
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
|
||||
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
|
||||
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
|
||||
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
|
||||
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;
|
||||
|
@ -266,5 +270,10 @@ export type RoleAssignmentsRepository = Repository<MiRoleAssignment> & MiReposit
|
|||
export type FlashsRepository = Repository<MiFlash> & MiRepository<MiFlash>;
|
||||
export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlashLike>;
|
||||
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
|
||||
export type ChatMessagesRepository = Repository<MiChatMessage> & MiRepository<MiChatMessage>;
|
||||
export type ChatRoomsRepository = Repository<MiChatRoom> & MiRepository<MiChatRoom>;
|
||||
export type ChatRoomMembershipsRepository = Repository<MiChatRoomMembership> & MiRepository<MiChatRoomMembership>;
|
||||
export type ChatRoomInvitationsRepository = Repository<MiChatRoomInvitation> & MiRepository<MiChatRoomInvitation>;
|
||||
export type ChatApprovalsRepository = Repository<MiChatApproval> & MiRepository<MiChatApproval>;
|
||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
|
||||
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedChatMessageSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUserId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUser: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
toUserId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
toUser: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
toRoomId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
toRoom: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
file: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'DriveFile',
|
||||
},
|
||||
isRead: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedChatMessageLiteSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUserId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fromUser: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
toUserId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
toRoomId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
file: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'DriveFile',
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedChatRoomInvitationSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
roomId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
room: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedChatRoomMembershipSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
roomId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
room: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedChatRoomSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
ownerId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
owner: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isMuted: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -287,6 +287,21 @@ export const packedNotificationSchema = {
|
|||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['chatRoomInvitationReceived'],
|
||||
},
|
||||
invitation: {
|
||||
type: 'object',
|
||||
ref: 'ChatRoomInvitation',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
|
|
@ -292,6 +292,10 @@ export const packedRolePoliciesSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canChat: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -358,6 +358,15 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
},
|
||||
chatScope: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['everyone', 'following', 'followers', 'mutual', 'none'],
|
||||
},
|
||||
canChat: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
roles: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
|
@ -540,6 +549,10 @@ export const packedMeDetailedOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
hasUnreadChatMessages: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
hasUnreadNotification: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
|
@ -599,6 +612,7 @@ export const packedMeDetailedOnlySchema = {
|
|||
receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
|
||||
followRequestAccepted: { optional: true, ...notificationRecieveConfig },
|
||||
roleAssigned: { optional: true, ...notificationRecieveConfig },
|
||||
chatRoomInvitationReceived: { optional: true, ...notificationRecieveConfig },
|
||||
achievementEarned: { optional: true, ...notificationRecieveConfig },
|
||||
app: { optional: true, ...notificationRecieveConfig },
|
||||
test: { optional: true, ...notificationRecieveConfig },
|
||||
|
|
|
@ -8,6 +8,9 @@ import pg from 'pg';
|
|||
import { DataSource, Logger } from 'typeorm';
|
||||
import * as highlight from 'cli-highlight';
|
||||
import { entities as charts } from '@/core/chart/entities.js';
|
||||
import { Config } from '@/config.js';
|
||||
import MisskeyLogger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
|
@ -42,7 +45,6 @@ import { MiNote } from '@/models/Note.js';
|
|||
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
|
||||
import { MiNoteReaction } from '@/models/NoteReaction.js';
|
||||
import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js';
|
||||
import { MiNoteUnread } from '@/models/NoteUnread.js';
|
||||
import { MiPage } from '@/models/Page.js';
|
||||
import { MiPageLike } from '@/models/PageLike.js';
|
||||
import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js';
|
||||
|
@ -76,13 +78,14 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
|||
import { MiFlash } from '@/models/Flash.js';
|
||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
|
||||
import { Config } from '@/config.js';
|
||||
import MisskeyLogger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiSystemAccount } from './models/SystemAccount.js';
|
||||
import { MiChatApproval } from '@/models/ChatApproval.js';
|
||||
import { MiSystemAccount } from '@/models/SystemAccount.js';
|
||||
|
||||
pg.types.setTypeParser(20, Number);
|
||||
|
||||
|
@ -195,7 +198,6 @@ export const entities = [
|
|||
MiNoteFavorite,
|
||||
MiNoteReaction,
|
||||
MiNoteThreadMuting,
|
||||
MiNoteUnread,
|
||||
MiPage,
|
||||
MiPageLike,
|
||||
MiGalleryPost,
|
||||
|
@ -236,6 +238,11 @@ export const entities = [
|
|||
MiFlash,
|
||||
MiFlashLike,
|
||||
MiUserMemo,
|
||||
MiChatMessage,
|
||||
MiChatRoom,
|
||||
MiChatRoomMembership,
|
||||
MiChatRoomInvitation,
|
||||
MiChatApproval,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
...charts,
|
||||
|
|
|
@ -44,6 +44,8 @@ import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
|
|||
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
|
||||
import { UserListChannelService } from './api/stream/channels/user-list.js';
|
||||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||
import { ChatUserChannelService } from './api/stream/channels/chat-user.js';
|
||||
import { ChatRoomChannelService } from './api/stream/channels/chat-room.js';
|
||||
import { ReversiChannelService } from './api/stream/channels/reversi.js';
|
||||
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
|
||||
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
|
||||
|
@ -84,6 +86,8 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
|||
GlobalTimelineChannelService,
|
||||
HashtagChannelService,
|
||||
RoleTimelineChannelService,
|
||||
ChatUserChannelService,
|
||||
ChatRoomChannelService,
|
||||
ReversiChannelService,
|
||||
ReversiGameChannelService,
|
||||
HomeTimelineChannelService,
|
||||
|
|
|
@ -391,10 +391,10 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (ep.meta.requireRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
||||
if (ep.meta.requiredRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||
const policies = await this.roleService.getUserPolicies(user!.id);
|
||||
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||
if (!policies[ep.meta.requiredRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||
throw new ApiError({
|
||||
message: 'You are not assigned to a required role.',
|
||||
code: 'ROLE_PERMISSION_DENIED',
|
||||
|
|
|
@ -9,7 +9,6 @@ import * as Redis from 'ioredis';
|
|||
import * as WebSocket from 'ws';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository, MiAccessToken } from '@/models/_.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
|
@ -35,7 +34,6 @@ export class StreamingApiServerService {
|
|||
private usersRepository: UsersRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
private noteReadService: NoteReadService,
|
||||
private authenticateService: AuthenticateService,
|
||||
private channelsService: ChannelsService,
|
||||
private notificationService: NotificationService,
|
||||
|
@ -96,7 +94,6 @@ export class StreamingApiServerService {
|
|||
|
||||
const stream = new MainStreamConnection(
|
||||
this.channelsService,
|
||||
this.noteReadService,
|
||||
this.notificationService,
|
||||
this.cacheService,
|
||||
this.channelFollowingService,
|
||||
|
|
|
@ -263,7 +263,6 @@ export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.
|
|||
export * as 'i/page-likes' from './endpoints/i/page-likes.js';
|
||||
export * as 'i/pages' from './endpoints/i/pages.js';
|
||||
export * as 'i/pin' from './endpoints/i/pin.js';
|
||||
export * as 'i/read-all-unread-notes' from './endpoints/i/read-all-unread-notes.js';
|
||||
export * as 'i/read-announcement' from './endpoints/i/read-announcement.js';
|
||||
export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js';
|
||||
export * as 'i/registry/get' from './endpoints/i/registry/get.js';
|
||||
|
@ -397,4 +396,28 @@ export * as 'users/search' from './endpoints/users/search.js';
|
|||
export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js';
|
||||
export * as 'users/show' from './endpoints/users/show.js';
|
||||
export * as 'users/update-memo' from './endpoints/users/update-memo.js';
|
||||
export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js';
|
||||
export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js';
|
||||
export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js';
|
||||
export * as 'chat/messages/show' from './endpoints/chat/messages/show.js';
|
||||
export * as 'chat/messages/react' from './endpoints/chat/messages/react.js';
|
||||
export * as 'chat/messages/unreact' from './endpoints/chat/messages/unreact.js';
|
||||
export * as 'chat/messages/user-timeline' from './endpoints/chat/messages/user-timeline.js';
|
||||
export * as 'chat/messages/room-timeline' from './endpoints/chat/messages/room-timeline.js';
|
||||
export * as 'chat/messages/search' from './endpoints/chat/messages/search.js';
|
||||
export * as 'chat/rooms/create' from './endpoints/chat/rooms/create.js';
|
||||
export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js';
|
||||
export * as 'chat/rooms/join' from './endpoints/chat/rooms/join.js';
|
||||
export * as 'chat/rooms/leave' from './endpoints/chat/rooms/leave.js';
|
||||
export * as 'chat/rooms/mute' from './endpoints/chat/rooms/mute.js';
|
||||
export * as 'chat/rooms/show' from './endpoints/chat/rooms/show.js';
|
||||
export * as 'chat/rooms/owned' from './endpoints/chat/rooms/owned.js';
|
||||
export * as 'chat/rooms/joining' from './endpoints/chat/rooms/joining.js';
|
||||
export * as 'chat/rooms/update' from './endpoints/chat/rooms/update.js';
|
||||
export * as 'chat/rooms/members' from './endpoints/chat/rooms/members.js';
|
||||
export * as 'chat/rooms/invitations/create' from './endpoints/chat/rooms/invitations/create.js';
|
||||
export * as 'chat/rooms/invitations/ignore' from './endpoints/chat/rooms/invitations/ignore.js';
|
||||
export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js';
|
||||
export * as 'chat/rooms/invitations/outbox' from './endpoints/chat/rooms/invitations/outbox.js';
|
||||
export * as 'chat/history' from './endpoints/chat/history.js';
|
||||
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';
|
||||
|
|
|
@ -39,7 +39,7 @@ interface IEndpointMetaBase {
|
|||
*/
|
||||
readonly requireAdmin?: boolean;
|
||||
|
||||
readonly requireRolePolicy?: KeyOf<'RolePolicies'>;
|
||||
readonly requiredRolePolicy?: KeyOf<'RolePolicies'>;
|
||||
|
||||
/**
|
||||
* 引っ越し済みのユーザーによるリクエストを禁止するか
|
||||
|
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
|
||||
res: {
|
||||
|
|
|
@ -13,7 +13,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
errors: {
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'read:admin:avatar-decorations',
|
||||
|
||||
res: {
|
||||
|
|
|
@ -13,7 +13,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
|
||||
errors: {
|
||||
|
|
|
@ -11,7 +11,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue