Compare commits
85 Commits
2024.2.0-b
...
2024.2.0-b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
66714d94fc | ||
![]() |
580cec33a9 | ||
![]() |
6c67b2e40e | ||
![]() |
430290c084 | ||
![]() |
c38f5ee528 | ||
![]() |
9e1145df81 | ||
![]() |
e5876440cb | ||
![]() |
d8bdbd53ed | ||
![]() |
3499814498 | ||
![]() |
85809a240e | ||
![]() |
07dc99d197 | ||
![]() |
30b48df9d9 | ||
![]() |
b0a38c0cae | ||
![]() |
ada2c69c7d | ||
![]() |
6915fde1cf | ||
![]() |
0641454c23 | ||
![]() |
5079a4b7dd | ||
![]() |
d5860d0685 | ||
![]() |
2db5b61616 | ||
![]() |
8aea3603a6 | ||
![]() |
eb078d67ab | ||
![]() |
eef46ed3c9 | ||
![]() |
e90dea4be9 | ||
![]() |
6a41afaaee | ||
![]() |
a6a91fec3a | ||
![]() |
9ac2c36d76 | ||
![]() |
e21cecefa1 | ||
![]() |
4535f9b41b | ||
![]() |
b62d9f3920 | ||
![]() |
fe7036a1a8 | ||
![]() |
cdac3988b5 | ||
![]() |
9753cce4aa | ||
![]() |
30f4023c36 | ||
![]() |
15727088be | ||
![]() |
b7270c6238 | ||
![]() |
bb330533c1 | ||
![]() |
5342692b1e | ||
![]() |
ef8eaf8e89 | ||
![]() |
4553d6426b | ||
![]() |
b33cfc2876 | ||
![]() |
4de14fb5cf | ||
![]() |
60156a40b2 | ||
![]() |
5719a929ad | ||
![]() |
2b6bf074c6 | ||
![]() |
37d87854c2 | ||
![]() |
d27b3525cd | ||
![]() |
7beb4ed131 | ||
![]() |
177c35e321 | ||
![]() |
ca9be872a8 | ||
![]() |
a97d4fa4ef | ||
![]() |
908e0f3b8b | ||
![]() |
b68446b289 | ||
![]() |
608e7c1546 | ||
![]() |
a3ba315dc6 | ||
![]() |
df5f14ca7a | ||
![]() |
d060bb44e1 | ||
![]() |
645f5e8633 | ||
![]() |
547be1973d | ||
![]() |
65557d5f27 | ||
![]() |
cc420c245f | ||
![]() |
443d1b2f5c | ||
![]() |
1f8d275094 | ||
![]() |
2efcb27043 | ||
![]() |
298bc34eaf | ||
![]() |
62f6f6af02 | ||
![]() |
e8ba0b3f54 | ||
![]() |
f48f7149f8 | ||
![]() |
d2ccce6366 | ||
![]() |
af2d81a990 | ||
![]() |
58ac8bc8e9 | ||
![]() |
2ee5507d06 | ||
![]() |
31a39776f5 | ||
![]() |
5e307e472d | ||
![]() |
e0ad066382 | ||
![]() |
99fe03bd4d | ||
![]() |
850d38414e | ||
![]() |
d380ed36de | ||
![]() |
5c8888d6a8 | ||
![]() |
4af3640bd3 | ||
![]() |
94e282b612 | ||
![]() |
259992c65f | ||
![]() |
67f6157d42 | ||
![]() |
0cfeb42786 | ||
![]() |
a431dde537 | ||
![]() |
4f95b8d9d2 |
@@ -160,14 +160,14 @@ id: 'aidx'
|
||||
# Job concurrency per worker
|
||||
#deliverJobConcurrency: 128
|
||||
#inboxJobConcurrency: 16
|
||||
#relashionshipJobConcurrency: 16
|
||||
# What's relashionshipJob?:
|
||||
#relationshipJobConcurrency: 16
|
||||
# What's relationshipJob?:
|
||||
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
|
||||
|
||||
# Job rate limiter
|
||||
#deliverJobPerSec: 128
|
||||
#inboxJobPerSec: 32
|
||||
#relashionshipJobPerSec: 64
|
||||
#relationshipJobPerSec: 64
|
||||
|
||||
# Job attempts
|
||||
#deliverJobMaxAttempts: 12
|
||||
|
29
.github/workflows/check-misskey-js-autogen.yml
vendored
29
.github/workflows/check-misskey-js-autogen.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Check Misskey JS autogen
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
@@ -15,13 +15,14 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
api_json_names: "api-base.json api-head.json"
|
||||
api_json_name: "api-head.json"
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
@@ -87,22 +88,27 @@ jobs:
|
||||
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d . ';'
|
||||
ls -la
|
||||
|
||||
- name: get head checksum
|
||||
run: |-
|
||||
checksum=$(realpath head_checksum)
|
||||
|
||||
cd packages/misskey-js/src
|
||||
find autogen -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
|
||||
cd ../../..
|
||||
|
||||
- name: build autogen
|
||||
run: |-
|
||||
for name in $(echo $api_json_names)
|
||||
do
|
||||
checksum=$(mktemp)
|
||||
mv $name packages/misskey-js/generator/api.json
|
||||
checksum=$(realpath ${api_json_name}_checksum)
|
||||
mv $api_json_name packages/misskey-js/generator/api.json
|
||||
|
||||
cd packages/misskey-js/generator
|
||||
pnpm run generate
|
||||
find built -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
|
||||
cd ../../..
|
||||
cp $checksum ${name}_checksum
|
||||
done
|
||||
cd built
|
||||
find autogen -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
|
||||
cd ../../../..
|
||||
|
||||
- name: check update for type definitions
|
||||
run: diff $(echo -n ${api_json_names} | awk -v RS=" " '{ printf "%s_checksum ", $0 }')
|
||||
run: diff head_checksum ${api_json_name}_checksum
|
||||
|
||||
- name: send message
|
||||
if: failure()
|
||||
@@ -125,3 +131,4 @@ jobs:
|
||||
comment_tag: check-misskey-js-autogen
|
||||
mode: delete
|
||||
message: "Thank you!"
|
||||
create_if_not_exists: false
|
||||
|
28
.github/workflows/check-misskey-js-version.yml
vendored
Normal file
28
.github/workflows/check-misskey-js-version.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Check Misskey JS version
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- packages/misskey-js/package.json
|
||||
- package.json
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- packages/misskey-js/package.json
|
||||
- package.json
|
||||
|
||||
jobs:
|
||||
check-version:
|
||||
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
|
||||
name: Check version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Check version
|
||||
run: |
|
||||
if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then
|
||||
echo "Version mismatch!"
|
||||
exit 1
|
||||
fi
|
23
.github/workflows/deploy-test-environment.yml
vendored
Normal file
23
.github/workflows/deploy-test-environment.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: deploy-test-environment
|
||||
|
||||
on:
|
||||
#push:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
repository:
|
||||
description: 'Repository to deploy (optional)'
|
||||
type: string
|
||||
required: false
|
||||
branch_or_hash:
|
||||
description: 'Branch or Commit hash to deploy (optional)'
|
||||
type: string
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
deploy-test-environment:
|
||||
uses: joinmisskey/misskey-tga/.github/workflows/deploy-test-environment.yml@main
|
||||
with:
|
||||
repository: ${{ github.event.inputs.repository }}
|
||||
branch_or_hash: ${{ github.event.inputs.branch_or_hash }}
|
||||
secrets:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -92,4 +92,6 @@ jobs:
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm --filter misskey-js run build
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter misskey-reversi run build:tsc
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run typecheck
|
||||
|
2
.github/workflows/ok-to-test.yml
vendored
2
.github/workflows/ok-to-test.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
|
||||
|
||||
- name: Slash Command Dispatch
|
||||
uses: peter-evans/slash-command-dispatch@v3
|
||||
uses: peter-evans/slash-command-dispatch@v4
|
||||
env:
|
||||
TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
with:
|
||||
|
45
.github/workflows/on-release-created.yml
vendored
Normal file
45
.github/workflows/on-release-created.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: On Release Created (Publish misskey-js)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-misskey-js:
|
||||
name: Publish misskey-js
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- name: Publish package
|
||||
run: |
|
||||
corepack enable
|
||||
pnpm i --frozen-lockfile
|
||||
pnpm build
|
||||
pnpm --filter misskey-js publish --access public --no-git-checks --provenance
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
4
.github/workflows/test-backend.yml
vendored
4
.github/workflows/test-backend.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm --filter backend test-and-coverage
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/backend/coverage/coverage-final.json
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm --filter backend test-and-coverage:e2e
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/backend/coverage/coverage-final.json
|
||||
|
2
.github/workflows/test-frontend.yml
vendored
2
.github/workflows/test-frontend.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm --filter frontend test-and-coverage
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/frontend/coverage/coverage-final.json
|
||||
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
CI: true
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/misskey-js/coverage/coverage-final.json
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@@ -21,6 +21,9 @@
|
||||
- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
|
||||
- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
|
||||
- Feat: Add support for TrueMail
|
||||
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
|
||||
* すべてのリモートユーザーのリアクション一覧を見えないようにします
|
||||
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
|
||||
|
||||
### Client
|
||||
- Feat: 新しいゲームを追加
|
||||
@@ -45,11 +48,20 @@
|
||||
- Enhance: ノート作成画面のファイル添付メニューから直接ファイルを削除できるように
|
||||
- Enhance: MFMの属性でオートコンプリートが使用できるように #12735
|
||||
- Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
|
||||
- Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
|
||||
- Fix: ネイティブモードの絵文字がモノクロにならないように
|
||||
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
|
||||
- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
|
||||
- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正
|
||||
- Fix: Renoteのキーボードショートカットが機能していなかった問題を修正
|
||||
- Fix: 投稿フォームでアンケートの日時指定をした状態で再読み込みをすると期日が復元されない問題を修正
|
||||
- Fix: アンケートを設定したノートを「削除して編集」をするとアンケートの期日が引き継がれず、リセットされてしまう問題を修正
|
||||
- Fix: デッキのプロファイル作成時に名前を空にできる問題を修正
|
||||
- Fix: テーマ作成時に名称が空欄でも作成できてしまう問題を修正
|
||||
- Fix: プラグインで`Plugin:register_note_post_interruptor`を使用すると、ノートが投稿できなくなる問題を修正
|
||||
- Enhance: ページ遷移時にPlayerを閉じるように
|
||||
- Fix: iOSで大きな画像を変換してアップロードできない問題を修正
|
||||
- Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
||||
@@ -62,6 +74,8 @@
|
||||
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更
|
||||
- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
|
||||
- Fix: properly handle cc followers
|
||||
- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
|
||||
- Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122
|
||||
|
||||
### Service Worker
|
||||
- Enhance: オフライン表示のデザインを改善・多言語対応
|
||||
|
@@ -161,11 +161,13 @@ describe('After user signed in', () => {
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
cy.get('[data-cy-user-setup-continue]').should('be.visible');
|
||||
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
|
||||
cy.get('[data-cy-user-setup-continue]', { timeout: 12000 }).should('be.visible');
|
||||
});
|
||||
|
||||
it('account setup wizard', () => {
|
||||
cy.get('[data-cy-user-setup-continue]').click();
|
||||
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
|
||||
cy.get('[data-cy-user-setup-continue]', { timeout: 12000 }).click();
|
||||
|
||||
cy.get('[data-cy-user-setup-user-name] input').type('ありす');
|
||||
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
|
||||
@@ -202,7 +204,8 @@ describe('After user setup', () => {
|
||||
cy.login('alice', 'alice1234');
|
||||
|
||||
// アカウント初期設定ウィザード
|
||||
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click();
|
||||
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
|
||||
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click();
|
||||
cy.get('[data-cy-modal-dialog-ok]').click();
|
||||
});
|
||||
|
||||
|
30
cypress/e2e/router.cy.js
Normal file
30
cypress/e2e/router.cy.js
Normal file
@@ -0,0 +1,30 @@
|
||||
describe('Router transition', () => {
|
||||
describe('Redirect', () => {
|
||||
// サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い)
|
||||
before(() => {
|
||||
cy.resetState();
|
||||
|
||||
// インスタンス初期セットアップ
|
||||
cy.registerUser('admin', 'pass', true);
|
||||
|
||||
// ユーザー作成
|
||||
cy.registerUser('alice', 'alice1234');
|
||||
|
||||
cy.login('alice', 'alice1234');
|
||||
|
||||
// アカウント初期設定ウィザード
|
||||
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
|
||||
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click();
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy-modal-dialog-ok]').click();
|
||||
});
|
||||
|
||||
it('redirect to user profile', () => {
|
||||
// テストのためだけに用意されたリダイレクト用ルートに飛ぶ
|
||||
cy.visit('/redirect-test');
|
||||
|
||||
// プロフィールページのURLであることを確認する
|
||||
cy.url().should('include', '/@alice')
|
||||
});
|
||||
});
|
||||
});
|
@@ -1567,3 +1567,4 @@ _moderationLogTypes:
|
||||
createInvitation: "ولِّد دعوة"
|
||||
_reversi:
|
||||
total: "المجموع"
|
||||
|
||||
|
@@ -1346,3 +1346,4 @@ _moderationLogTypes:
|
||||
resetPassword: "পাসওয়ার্ড রিসেট করুন"
|
||||
_reversi:
|
||||
total: "মোট"
|
||||
|
||||
|
@@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats"
|
||||
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
|
||||
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
|
||||
attachCancel: "Eliminar el fitxer adjunt"
|
||||
deleteFile: "Esborrar l'arxiu "
|
||||
markAsSensitive: "Marcar com a NSFW"
|
||||
unmarkAsSensitive: "Deixar de marcar com a sensible"
|
||||
enterFileName: "Defineix nom del fitxer"
|
||||
@@ -1039,6 +1040,12 @@ rolesAssignedToMe: "Rols assignats "
|
||||
resetPasswordConfirm: "Vols canviar la teva contrasenya?"
|
||||
sensitiveWords: "Paraules sensibles"
|
||||
sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
|
||||
sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
|
||||
hiddenTags: "Etiquetes ocultes"
|
||||
hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
|
||||
notesSearchNotAvailable: "La cerca de notes no es troba disponible."
|
||||
license: "Llicència"
|
||||
unfavoriteConfirm: "Esborrar dels favorits?"
|
||||
myClips: "Els meus retalls"
|
||||
drivecleaner: "Netejador de Disc"
|
||||
retryAllQueuesNow: "Prova de nou d'executar totes les cues"
|
||||
@@ -1048,6 +1055,14 @@ enableChartsForRemoteUser: "Generar gràfiques d'usuaris remots"
|
||||
enableChartsForFederatedInstances: "Generar gràfiques d'instàncies remotes"
|
||||
showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota"
|
||||
reactionsDisplaySize: "Mida de les reaccions"
|
||||
limitWidthOfReaction: "Limitar l'amplada màxima de la reacció i mostrar-les en una mida reduïda "
|
||||
noteIdOrUrl: "ID o URL de la nota"
|
||||
video: "Vídeo"
|
||||
videos: "Vídeos "
|
||||
audio: "So"
|
||||
audioFiles: "So"
|
||||
dataSaver: "Economitzador de dades"
|
||||
accountMigration: "Migració del compte"
|
||||
accountMoved: "Aquest usuari té un compte nou:"
|
||||
accountMovedShort: "Aquest compte ha sigut migrat"
|
||||
operationForbidden: "Operació no permesa "
|
||||
@@ -1098,9 +1113,50 @@ branding: "Marca"
|
||||
enableServerMachineStats: "Publicar estadístiques del maquinari del servidor"
|
||||
enableIdenticonGeneration: "Activar la generació d'icones d'identificació "
|
||||
turnOffToImprovePerformance: "Desactivant aquesta opció es pot millorar el rendiment."
|
||||
createInviteCode: "Crear codi d'invitació "
|
||||
createWithOptions: "Crear invitació amb opcions"
|
||||
createCount: "Comptador d'invitacions "
|
||||
inviteCodeCreated: "Invitació creada"
|
||||
inviteLimitExceeded: "Has sobrepassat el límit d'invitacions que pots crear."
|
||||
createLimitRemaining: "Et queden {limit} invitacions restants"
|
||||
inviteLimitResetCycle: "Cada {time} {limit} invitacions."
|
||||
expirationDate: "Data de venciment"
|
||||
noExpirationDate: "Sense data de venciment"
|
||||
inviteCodeUsedAt: "Codi d'invitació fet servir el"
|
||||
registeredUserUsingInviteCode: "Codi d'invitació fet servir per l'usuari "
|
||||
waitingForMailAuth: "Esperant la verificació per correu electrònic "
|
||||
inviteCodeCreator: "Invitació creada per"
|
||||
usedAt: "Utilitzada el"
|
||||
unused: "Sense utilitzar"
|
||||
used: "Utilitzada"
|
||||
expired: "Caducat"
|
||||
doYouAgree: "Estàs d'acord?"
|
||||
beSureToReadThisAsItIsImportant: "Llegeix això perquè és molt important."
|
||||
iHaveReadXCarefullyAndAgree: "He llegit {x} i estic d'acord."
|
||||
dialog: "Diàleg "
|
||||
icon: "Icona"
|
||||
forYou: "Per a tu"
|
||||
currentAnnouncements: "Informes actuals"
|
||||
pastAnnouncements: "Informes passats"
|
||||
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
||||
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
||||
replies: "Respondre"
|
||||
renotes: "Impulsa"
|
||||
loadReplies: "Mostrar les respostes"
|
||||
loadConversation: "Mostrar la conversació "
|
||||
pinnedList: "Llista fixada"
|
||||
keepScreenOn: "Mantenir la pantalla encesa"
|
||||
verifiedLink: "La propietat de l'enllaç ha sigut verificada"
|
||||
notifyNotes: "Notificar quan hi hagi notes noves"
|
||||
unnotifyNotes: "Deixar de notificar quan hi hagi notes noves"
|
||||
authentication: "Autenticació "
|
||||
authenticationRequiredToContinue: "Si us plau autentificat per continuar"
|
||||
dateAndTime: "Data i hora"
|
||||
showRenotes: "Mostrar impulsos"
|
||||
edited: "Editat"
|
||||
notificationRecieveConfig: "Paràmetres de notificacions"
|
||||
mutualFollow: "Seguidor mutu"
|
||||
fileAttachedOnly: "Només notes amb adjunts"
|
||||
externalServices: "Serveis externs"
|
||||
impressum: "Impressum"
|
||||
impressumUrl: "Adreça URL impressum"
|
||||
@@ -1153,6 +1209,149 @@ _initialAccountSetting:
|
||||
privacySetting: "Configuració de seguretat"
|
||||
theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard."
|
||||
youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard."
|
||||
_initialTutorial:
|
||||
_reaction:
|
||||
letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
|
||||
reactToContinue: "Afegeix una reacció per continuar."
|
||||
reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
|
||||
reactDone: "Pots desfer una reacció fent clic al botó '-'."
|
||||
_timeline:
|
||||
title: "El concepte de les línies de temps"
|
||||
description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)"
|
||||
home: "Pots veure notes dels comptes que segueixes"
|
||||
local: "Pots veure les notes dels usuaris del servidor."
|
||||
social: "Es mostren les notes de les línies de temps d'Inici i Local."
|
||||
global: "Pots veure les notes de tots els servidors connectats."
|
||||
description2: "Pots canviar la línia de temps en qualsevol moment fent servir la barra de la pantalla superior."
|
||||
description3: "A més hi ha línies de temps per llistes i per canals. Si vols saber més {link}."
|
||||
_postNote:
|
||||
title: "Configuració de la publicació de les notes"
|
||||
description1: "Quan públiques una nota a Misskey hi ha diferents opcions disponibles. El formulari de publicació es veu així"
|
||||
_visibility:
|
||||
description: "Pots limitar qui pot veure les teves notes."
|
||||
public: "La teva nota serà visible per a tots els usuaris."
|
||||
home: "Publicar només a línia de temps d'Inici. La gent que visiti el teu perfil o mitjançant les remotes també la podran veure."
|
||||
followers: "Només visible per a seguidors. Només els teus seguidors la podran veure i ningú més. Ningú més podrà fer renotes."
|
||||
direct: "Només visible per a alguns seguidors, el destinatari rebre una notificació. Es pot fer servir com una alternativa als missatges directes."
|
||||
doNotSendConfidencialOnDirect1: "Tingues cura quan enviïs informació sensible."
|
||||
doNotSendConfidencialOnDirect2: "Els administradors del servidor poden veure tot el que escrius. Ves compte quan enviïs informació sensible en enviar notes directes a altres usuaris en servidors de poca confiança."
|
||||
localOnly: "Publicar amb aquesta opció activada farà que la nota no federi amb altres servidors. Els usuaris d'altres servidors no podran veure la nota directament, sense importar les opcions de visualització."
|
||||
_cw:
|
||||
title: "Avís de Contingut (CW)"
|
||||
description: "En comptes del cos de la nota es mostrarà el que s'escrigui al camp de 'comentaris'. Fent clic a 'Llegir més' es mostrarà el cos."
|
||||
_exampleNote:
|
||||
cw: "Això et farà venir gana!"
|
||||
note: "Acabo de menjar-me un donut de xocolata 🍩😋"
|
||||
useCases: "Això es fa servir per seguir normes del servidor sobre certes notes o per ocultar contingut sensible O revelador."
|
||||
_howToMakeAttachmentsSensitive:
|
||||
title: "Com marcar adjunts com a contingut sensible?"
|
||||
description: "Per adjunts que sigui requerit per les normes del servidor o que puguin contenir material sensible, s'ha d'afegir l'opció 'sensible'."
|
||||
tryThisFile: "Prova de marcar la imatge adjunta en aquest formulari com a sensible!"
|
||||
_exampleNote:
|
||||
note: "Oops! L'he fet bona en obrir la tapa de Nocilla..."
|
||||
method: "Per marcar un adjunt com a sensible, fes clic a la miniatura de l'adjunt, obre el menú i fes clic a 'Marcar com a sensible'."
|
||||
sensitiveSucceeded: "Quan adjuntis fitxers si us plau marca la sensibilitat seguint les normes del servidor."
|
||||
doItToContinue: "Marca el fitxer adjunt com a sensible per poder continuar."
|
||||
_done:
|
||||
title: "Has completat el tutorial 🎉"
|
||||
description: "Les funcions explicades aquí és una petita mostra. Per una explicació més detallada de com fer servir MissKey consulta {link}."
|
||||
_timelineDescription:
|
||||
home: "A la línia de temps d'Inici pots veure les notes dels usuaris que segueixes."
|
||||
local: "A la línia de temps Local pots veure les notes de tots els usuaris d'aquest servidor."
|
||||
social: "La línia de temps Social mostren les notes de les línies de temps d'Inici i Local."
|
||||
global: "A la línia de temps Global pots veure les notes de tots els servidors connectats."
|
||||
_serverRules:
|
||||
description: "Un conjunt de regles que seran mostrades abans de registrar-se. Es recomanable configurar un resum dels termes d'ús."
|
||||
_serverSettings:
|
||||
iconUrl: "URL de la icona"
|
||||
appIconDescription: "Especifica la icona que es mostrarà quan el {host} es mostri en una aplicació."
|
||||
appIconUsageExample: "Per exemple com a PWA, o quan es mostri com un favorit a la pàgina d'inici del telèfon mòbil"
|
||||
appIconStyleRecommendation: "Com la icona pot ser retallada com un cercle o un quadrat, es recomana fer servir una icona amb un marge acolorit que l'envolti."
|
||||
appIconResolutionMustBe: "La resolució mínima és {resolution}."
|
||||
manifestJsonOverride: "Sobreescriure manifest.json"
|
||||
shortName: "Nom curt"
|
||||
shortNameDescription: "Una abreviatura del nom de la instància que es poguí mostrar en cas que el nom oficial sigui massa llarg"
|
||||
fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
|
||||
_achievements:
|
||||
_types:
|
||||
_notes100000:
|
||||
flavor: "Segur que tens moltes coses a dir?"
|
||||
_login3:
|
||||
title: "Principiant I"
|
||||
description: "Vas iniciar sessió fa tres dies"
|
||||
flavor: "Des d'avui diguem Misskist"
|
||||
_login7:
|
||||
title: "Principiant II"
|
||||
description: "Vas iniciar sessió fa set dies"
|
||||
flavor: "Ja saps com va funcionant tot?"
|
||||
_login15:
|
||||
title: "Principiant III"
|
||||
description: "Vas iniciar sessió fa quinze dies"
|
||||
_login30:
|
||||
title: "Misskist I"
|
||||
description: "Vas iniciar sessió fa trenta dies"
|
||||
_login60:
|
||||
title: "Misskist II"
|
||||
description: "Vas iniciar sessió fa seixanta dies"
|
||||
_login100:
|
||||
title: "Misskist III"
|
||||
description: "Vas iniciar sessió fa cent dies"
|
||||
flavor: "Misskist violent"
|
||||
_login200:
|
||||
title: "Regular I"
|
||||
description: "Vas iniciar sessió fa dos-cents dies"
|
||||
_login300:
|
||||
title: "Regular II"
|
||||
description: "Vas iniciar sessió fa tres-cents dies"
|
||||
_login400:
|
||||
title: "Regular III"
|
||||
description: "Vas iniciar sessió fa quatre-cents dies"
|
||||
_login500:
|
||||
title: "Expert I"
|
||||
description: "Vas iniciar sessió fa cinc-cents dies"
|
||||
flavor: "Amics, he dit massa vegades que soc un amant de les notes"
|
||||
_login600:
|
||||
title: "Expert II"
|
||||
description: "Vas iniciar sessió fa sis-cents dies"
|
||||
_login700:
|
||||
title: "Expert III"
|
||||
description: "Vas iniciar sessió fa set-cents dies"
|
||||
_login800:
|
||||
title: "Mestre de les Notes I"
|
||||
description: "Vas iniciar sessió fa vuit-cents dies "
|
||||
_login900:
|
||||
title: "Mestre de les Notes II"
|
||||
description: "Vas iniciar sessió fa nou-cents dies"
|
||||
_login1000:
|
||||
title: "Mestre de les Notes III"
|
||||
description: "Vas iniciar sessió fa mil dies"
|
||||
flavor: "Gràcies per fer servir MissKey!"
|
||||
_noteClipped1:
|
||||
title: "He de retallar-te!"
|
||||
description: "Retalla la teva primera nota"
|
||||
_noteFavorited1:
|
||||
title: "Quan miro les estrelles"
|
||||
description: "La primera vegada que vaig registrar el meu favorit"
|
||||
_myNoteFavorited1:
|
||||
title: "Vull una estrella"
|
||||
description: "La meva nota va ser registrada com favorita per una de les altres persones"
|
||||
_profileFilled:
|
||||
title: "Estic a punt"
|
||||
description: "Vaig fer la configuració de perfil"
|
||||
_markedAsCat:
|
||||
title: "Soc un gat"
|
||||
description: "He establert el meu compte com si fos un Gat"
|
||||
flavor: "Encara no tinc nom"
|
||||
_following1:
|
||||
title: "És el meu primer seguiment"
|
||||
description: "És la primera vegada que et segueixo"
|
||||
_following10:
|
||||
title: "Segueix-me... Segueix-me..."
|
||||
_open3windows:
|
||||
title: "Multi finestres"
|
||||
description: "I va obrir més de tres finestres"
|
||||
_driveFolderCircularReference:
|
||||
title: "Consulteu la secció de bucle"
|
||||
_role:
|
||||
assignTarget: "Assignar "
|
||||
priority: "Prioritat"
|
||||
@@ -1274,5 +1473,7 @@ _webhookSettings:
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspèn"
|
||||
resetPassword: "Restableix la contrasenya"
|
||||
createInvitation: "Crear codi d'invitació "
|
||||
_reversi:
|
||||
total: "Total"
|
||||
|
||||
|
@@ -2022,3 +2022,4 @@ _moderationLogTypes:
|
||||
createInvitation: "Vygenerovat pozvánku"
|
||||
_reversi:
|
||||
total: "Celkem"
|
||||
|
||||
|
@@ -1,2 +1,3 @@
|
||||
---
|
||||
_lang_: "Dansk"
|
||||
|
||||
|
@@ -122,7 +122,10 @@ add: "Hinzufügen"
|
||||
reaction: "Reaktionen"
|
||||
reactions: "Reaktionen"
|
||||
emojiPicker: "Emoji auswählen"
|
||||
pinnedEmojisForReactionSettingDescription: "Wähle die Emojis aus, um sie an zu pinnen"
|
||||
pinnedEmojisForReactionSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie beim Reagieren als Erstes anzuzeigen."
|
||||
pinnedEmojisSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie in der Emoji-Auswahl als Erstes anzuzeigen"
|
||||
overwriteFromPinnedEmojisForReaction: "Überschreiben mit den Reaktions-Einstellungen"
|
||||
overwriteFromPinnedEmojis: "Überschreiben mit den allgemeinen Einstellungen"
|
||||
reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen"
|
||||
rememberNoteVisibility: "Notizsichtbarkeit merken"
|
||||
attachCancel: "Anhang entfernen"
|
||||
@@ -181,7 +184,7 @@ searchWith: "Suchen: {q}"
|
||||
youHaveNoLists: "Du hast keine Listen"
|
||||
followConfirm: "Möchtest du {name} wirklich folgen?"
|
||||
proxyAccount: "Proxy-Benutzerkonto"
|
||||
proxyAccountDescription: "Ein Proxy-Benutzerkonto ist ein Benutzerkonto, das sich für Nutzer unter bestimmten Konditionen wie ein Follower aus einer fremden Instanz verhält. Zum Beispiel wird die Aktivität eines Nutzers aus einer fremden Instanz nicht an diese Instanz übermittelt, falls es keinen Benutzer dieser Instanz gibt, der diesem Nutzer aus fremder Instanz folgt. In diesem Fall folgt stattdessen das Proxy-Benutzerkonto."
|
||||
proxyAccountDescription: "Ein Proxy-Konto ist ein Benutzerkonto, das unter bestimmten Bedingungen als Follower für Benutzer fremder Instanzen fungiert. Wenn zum Beispiel ein Benutzer einen Benutzer einer fremden Instanz zu einer Liste hinzufügt, werden die Aktivitäten des entfernten Benutzers nicht an die Instanz übermittelt, wenn kein lokaler Benutzer diesem Benutzer folgt; stattdessen folgt das Proxy-Konto."
|
||||
host: "Hostname"
|
||||
selectUser: "Benutzer auswählen"
|
||||
recipient: "Empfänger"
|
||||
@@ -263,6 +266,7 @@ removed: "Erfolgreich gelöscht"
|
||||
removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?"
|
||||
deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?"
|
||||
resetAreYouSure: "Wirklich zurücksetzen?"
|
||||
areYouSure: "Bist du sicher?"
|
||||
saved: "Erfolgreich gespeichert"
|
||||
messaging: "Chat"
|
||||
upload: "Hochladen"
|
||||
@@ -357,7 +361,7 @@ enableLocalTimeline: "Lokale Chronik aktivieren"
|
||||
enableGlobalTimeline: "Globale Chronik aktivieren"
|
||||
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind."
|
||||
registration: "Registrieren"
|
||||
enableRegistration: "Registration neuer Benutzer erlauben"
|
||||
enableRegistration: "Registrierung neuer Benutzer erlauben"
|
||||
invite: "Einladen"
|
||||
driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto"
|
||||
driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen"
|
||||
@@ -375,8 +379,11 @@ hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "hCaptcha aktivieren"
|
||||
hcaptchaSiteKey: "Site key"
|
||||
hcaptchaSecretKey: "Secret key"
|
||||
mcaptcha: "mCaptcha"
|
||||
enableMcaptcha: "mCaptcha aktivieren"
|
||||
mcaptchaSiteKey: "Site key"
|
||||
mcaptchaSecretKey: "Secret key"
|
||||
mcaptchaInstanceUrl: "mCaptcha Instanz-URL"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "reCAPTCHA aktivieren"
|
||||
recaptchaSiteKey: "Site key"
|
||||
@@ -434,7 +441,7 @@ lastUsed: "Zuletzt benutzt"
|
||||
lastUsedAt: "Zuletzt verwendet: {t}"
|
||||
unregister: "Deaktivieren"
|
||||
passwordLessLogin: "Passwortloses Anmelden"
|
||||
passwordLessLoginDescription: "Ermöglicht passwortfreies Einloggen, nur via Security-Token oder Passkey"
|
||||
passwordLessLoginDescription: "Ermöglicht passwortloses Einloggen mit einem Security-Token oder Passkey"
|
||||
resetPassword: "Passwort zurücksetzen"
|
||||
newPasswordIs: "Das neue Passwort ist „{password}“"
|
||||
reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
|
||||
@@ -1171,6 +1178,9 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen.
|
||||
cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden."
|
||||
doReaction: "Reagieren"
|
||||
code: "Code"
|
||||
decorate: "Dekorieren"
|
||||
addMfmFunction: "MFM hinzufügen"
|
||||
sfx: "Soundeffekte"
|
||||
lastNDays: "Letzten {n} Tage"
|
||||
_announcement:
|
||||
forExistingUsers: "Nur für existierende Nutzer"
|
||||
@@ -1181,6 +1191,7 @@ _announcement:
|
||||
tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren."
|
||||
readConfirmTitle: "Als gelesen markieren?"
|
||||
readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen."
|
||||
shouldNotBeUsedToPresentPermanentInfo: "Es wird empfohlen, Ankündigungen für aktuelle und zeitlich begrenzte Neuigkeiten zu nutzen, statt für Informationen, die langfristig relevant sind."
|
||||
dialogAnnouncementUxWarn: "Bei der Verwendung von mehr als zwei Meldungen im Dialog-Format wird um Vorsicht geboten, da dies negative Auswirkungen auf die UX haben kann."
|
||||
silence: "Keine Benachrichtigung"
|
||||
silenceDescription: "Wenn aktiviert, gibt diese Meldung keine Nachricht aus und muss nicht als \"gelesen\" markiert werden."
|
||||
@@ -1210,6 +1221,24 @@ _initialTutorial:
|
||||
description: "Hier kannst du sehen, wie Misskey funktioniert"
|
||||
_note:
|
||||
title: "Was sind Notizen?"
|
||||
description: "Beiträge auf Misskey heißen \"Notizen\". Notizen werden chronologisch in der Chronik angeordnet und in Echtzeit aktualisiert."
|
||||
reply: "Klicke auf diesen Button, um auf eine Nachricht zu antworten. Es ist auch möglich, auf Antworten zu antworten und die Unterhaltung wie einen Thread fortzusetzen."
|
||||
_reaction:
|
||||
title: "Was sind Reaktionen?"
|
||||
reactToContinue: "Füge eine Reaktion hinzu, um fortzufahren."
|
||||
reactNotification: "Du erhältst Echtzeit-Benachrichtigungen, wenn jemand auf deine Notiz reagiert."
|
||||
_postNote:
|
||||
_visibility:
|
||||
description: "Du kannst einschränken, wer deine Notiz sehen kann."
|
||||
public: "Deine Notiz wird für alle Nutzer sichtbar sein."
|
||||
doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!"
|
||||
_cw:
|
||||
title: "Inhaltswarnung"
|
||||
_done:
|
||||
title: "Du hast das Tutorial abgeschlossen! 🎉"
|
||||
_timelineDescription:
|
||||
local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server."
|
||||
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."
|
||||
_serverSettings:
|
||||
@@ -1222,6 +1251,8 @@ _serverSettings:
|
||||
shortName: "Abkürzung"
|
||||
shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist."
|
||||
fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden."
|
||||
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. "
|
||||
_accountMigration:
|
||||
moveFrom: "Von einem anderen Konto zu diesem migrieren"
|
||||
moveFromSub: "Alias für ein anderes Konto erstellen"
|
||||
@@ -1479,6 +1510,8 @@ _achievements:
|
||||
_smashTestNotificationButton:
|
||||
title: "Testüberfluss"
|
||||
description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
|
||||
_tutorialCompleted:
|
||||
description: "Tutorial abgeschlossen"
|
||||
_role:
|
||||
new: "Rolle erstellen"
|
||||
edit: "Rolle bearbeiten"
|
||||
@@ -1489,7 +1522,9 @@ _role:
|
||||
assignTarget: "Zuweisungsart"
|
||||
descriptionOfAssignTarget: "<b>Manuell</b> bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\n<b>Konditional</b> bedeutet, dass die Liste der Benutzer einer Rolle durch eine Bedingung automatisch verwaltet wird."
|
||||
manual: "Manuell"
|
||||
manualRoles: "Manuelle Rollen"
|
||||
conditional: "Konditional"
|
||||
conditionalRoles: "Bedingte Rolle"
|
||||
condition: "Bedingung"
|
||||
isConditionalRole: "Dies ist eine konditionale Rolle."
|
||||
isPublic: "Öffentliche Rolle"
|
||||
@@ -1531,13 +1566,14 @@ _role:
|
||||
webhookMax: "Maximale Anzahl an Webhooks"
|
||||
clipMax: "Maximale Anzahl an Clips"
|
||||
noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips"
|
||||
userListMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
|
||||
userEachUserListsMax: "Maximale Anzahl an Benutzerlisten"
|
||||
userListMax: "Maximale Anzahl an Benutzerlisten"
|
||||
userEachUserListsMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
|
||||
rateLimitFactor: "Versuchsanzahl"
|
||||
descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
|
||||
canHideAds: "Kann Werbung ausblenden"
|
||||
canSearchNotes: "Nutzung der Notizsuchfunktion"
|
||||
canUseTranslator: "Verwendung des Übersetzers"
|
||||
avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können"
|
||||
_condition:
|
||||
isLocal: "Lokaler Benutzer"
|
||||
isRemote: "Benutzer fremder Instanz"
|
||||
@@ -1566,6 +1602,7 @@ _emailUnavailable:
|
||||
disposable: "Wegwerf-Email-Adressen können nicht verwendet werden"
|
||||
mx: "Dieser Email-Server ist ungültig"
|
||||
smtp: "Dieser Email-Server antwortet nicht"
|
||||
banned: "Du kannst dich mit dieser E-Mail-Adresse nicht registrieren"
|
||||
_ffVisibility:
|
||||
public: "Öffentlich"
|
||||
followers: "Nur für Follower sichtbar"
|
||||
@@ -1894,6 +1931,7 @@ _widgets:
|
||||
_userList:
|
||||
chooseList: "Liste auswählen"
|
||||
clicker: "Klickzähler"
|
||||
birthdayFollowings: "Nutzer, die heute Geburtstag haben"
|
||||
_cw:
|
||||
hide: "Inhalt verbergen"
|
||||
show: "Inhalt anzeigen"
|
||||
@@ -2243,4 +2281,9 @@ _externalResourceInstaller:
|
||||
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."
|
||||
_reversi:
|
||||
blackOrWhite: "Schwarz/Weiß"
|
||||
rules: "Regeln"
|
||||
black: "Schwarz"
|
||||
white: "Weiß"
|
||||
total: "Gesamt"
|
||||
|
||||
|
@@ -398,3 +398,4 @@ _moderationLogTypes:
|
||||
suspend: "Αποβολή"
|
||||
_reversi:
|
||||
total: "Σύνολο"
|
||||
|
||||
|
@@ -122,7 +122,11 @@ add: "Add"
|
||||
reaction: "Reactions"
|
||||
reactions: "Reactions"
|
||||
emojiPicker: "Emoji picker"
|
||||
pinnedEmojisForReactionSettingDescription: "Set the emojis which should be pinned and displayed immediately when reacting."
|
||||
pinnedEmojisSettingDescription: "Set the emojis to be pinned and displayed when viewing emoji picker"
|
||||
emojiPickerDisplay: "Emoji picker display"
|
||||
overwriteFromPinnedEmojisForReaction: "Override from reaction settings"
|
||||
overwriteFromPinnedEmojis: "Override from general settings"
|
||||
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
|
||||
rememberNoteVisibility: "Remember note visibility settings"
|
||||
attachCancel: "Remove attachment"
|
||||
@@ -376,8 +380,11 @@ hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "Enable hCaptcha"
|
||||
hcaptchaSiteKey: "Site key"
|
||||
hcaptchaSecretKey: "Secret key"
|
||||
mcaptcha: "mCaptcha"
|
||||
enableMcaptcha: "Enable mCaptcha"
|
||||
mcaptchaSiteKey: "Site key"
|
||||
mcaptchaSecretKey: "Secret key"
|
||||
mcaptchaInstanceUrl: "mCaptcha instance URL"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "Enable reCAPTCHA"
|
||||
recaptchaSiteKey: "Site key"
|
||||
@@ -625,6 +632,7 @@ medium: "Medium"
|
||||
small: "Small"
|
||||
generateAccessToken: "Generate access token"
|
||||
permission: "Permissions"
|
||||
adminPermission: "Admin Permissions"
|
||||
enableAll: "Enable all"
|
||||
disableAll: "Disable all"
|
||||
tokenRequested: "Grant access to account"
|
||||
@@ -641,7 +649,7 @@ smtpHost: "Host"
|
||||
smtpPort: "Port"
|
||||
smtpUser: "Username"
|
||||
smtpPass: "Password"
|
||||
emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verification"
|
||||
emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP authentication"
|
||||
smtpSecure: "Use implicit SSL/TLS for SMTP connections"
|
||||
smtpSecureInfo: "Turn this off when using STARTTLS"
|
||||
testEmail: "Test email delivery"
|
||||
@@ -668,6 +676,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b
|
||||
other: "Other"
|
||||
regenerateLoginToken: "Regenerate login token"
|
||||
regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
|
||||
theKeywordWhenSearchingForCustomEmoji: "This is the keyword when searching for custom emojis."
|
||||
setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
|
||||
fileIdOrUrl: "File ID or URL"
|
||||
behavior: "Behavior"
|
||||
@@ -1050,6 +1059,8 @@ limitWidthOfReaction: "Limits the maximum width of reactions and display them in
|
||||
noteIdOrUrl: "Note ID or URL"
|
||||
video: "Video"
|
||||
videos: "Videos"
|
||||
audio: "Audio"
|
||||
audioFiles: "Audio"
|
||||
dataSaver: "Data Saver"
|
||||
accountMigration: "Account Migration"
|
||||
accountMoved: "This user has moved to a new account:"
|
||||
@@ -1162,6 +1173,7 @@ tosAndPrivacyPolicy: "Terms of Service and Privacy Policy"
|
||||
avatarDecorations: "Avatar decorations"
|
||||
attach: "Attach"
|
||||
detach: "Remove"
|
||||
detachAll: "Remove All"
|
||||
angle: "Angle"
|
||||
flip: "Flip"
|
||||
showAvatarDecorations: "Show avatar decorations"
|
||||
@@ -1175,11 +1187,31 @@ cwNotationRequired: "If \"Hide content\" is enabled, a description must be provi
|
||||
doReaction: "Add reaction"
|
||||
code: "Code"
|
||||
reloadRequiredToApplySettings: "Reloading is required to apply the settings."
|
||||
remainingN: "Remaining: {n}"
|
||||
overwriteContentConfirm: "Are you sure you want to overwrite the current content?"
|
||||
seasonalScreenEffect: "Seasonal Screen Effect"
|
||||
decorate: "Decorate"
|
||||
addMfmFunction: "Add MFM"
|
||||
enableQuickAddMfmFunction: "Show advanced MFM picker"
|
||||
bubbleGame: "Bubble Game"
|
||||
sfx: "Sound Effects"
|
||||
soundWillBePlayed: "Sound will be played"
|
||||
showReplay: "View Replay"
|
||||
replay: "Replay"
|
||||
replaying: "Showing replay"
|
||||
ranking: "Ranking"
|
||||
lastNDays: "Last {n} days"
|
||||
backToTitle: "Go back to title"
|
||||
hemisphere: "Where are you located"
|
||||
withSensitive: "Include notes with sensitive files"
|
||||
userSaysSomethingSensitive: "Post by {name} contains sensitive content"
|
||||
enableHorizontalSwipe: "Swipe to switch tabs"
|
||||
_bubbleGame:
|
||||
howToPlay: "How to play"
|
||||
_howToPlay:
|
||||
section1: "Adjust the position and drop the object into the box."
|
||||
section2: "When two objects of the same type touch each other, they will change into a different object and you score points."
|
||||
section3: "The game is over when objects overflow from the box. Aim for a high score by fusing objects together while you avoid overflowing the box!"
|
||||
_announcement:
|
||||
forExistingUsers: "Existing users only"
|
||||
forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
|
||||
@@ -1189,7 +1221,7 @@ _announcement:
|
||||
tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete."
|
||||
readConfirmTitle: "Mark as read?"
|
||||
readConfirmText: "This will mark the contents of \"{title}\" as read."
|
||||
shouldNotBeUsedToPresentPermanentInfo: "As it may significantly impact the user experience for new users, it is recommended to use notifications in the flow information rather than stock information."
|
||||
shouldNotBeUsedToPresentPermanentInfo: "It's best to use announcements to publish fresh and time-bound information, not for information that will be relevant in the long term."
|
||||
dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully."
|
||||
silence: "No notification"
|
||||
silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it."
|
||||
@@ -1552,8 +1584,11 @@ _achievements:
|
||||
description: "Tutorial completed"
|
||||
_bubbleGameExplodingHead:
|
||||
title: "🤯"
|
||||
description: "The biggest object in the bubble game"
|
||||
_bubbleGameDoubleExplodingHead:
|
||||
title: "Double🤯"
|
||||
description: "Two of the biggest objects in the bubble game at the same time"
|
||||
flavor: "You can fill a lunch box like this 🤯 🤯 a bit."
|
||||
_role:
|
||||
new: "New role"
|
||||
edit: "Edit role"
|
||||
@@ -1615,6 +1650,7 @@ _role:
|
||||
canHideAds: "Can hide ads"
|
||||
canSearchNotes: "Usage of note search"
|
||||
canUseTranslator: "Translator usage"
|
||||
avatarDecorationLimit: "Maximum number of avatar decorations that can be applied"
|
||||
_condition:
|
||||
isLocal: "Local user"
|
||||
isRemote: "Remote user"
|
||||
@@ -1643,6 +1679,7 @@ _emailUnavailable:
|
||||
disposable: "Disposable email addresses may not be used"
|
||||
mx: "This email server is invalid"
|
||||
smtp: "This email server is not responding"
|
||||
banned: "You cannot register with this email address"
|
||||
_ffVisibility:
|
||||
public: "Public"
|
||||
followers: "Visible to followers only"
|
||||
@@ -2051,6 +2088,7 @@ _profile:
|
||||
changeAvatar: "Change avatar"
|
||||
changeBanner: "Change banner"
|
||||
verifiedLinkDescription: "By entering an URL that contains a link to your profile here, an ownership verification icon can be displayed next to the field."
|
||||
avatarDecorationMax: "You can add up to {max} decorations."
|
||||
_exportOrImport:
|
||||
allNotes: "All notes"
|
||||
favoritedNotes: "Favorite notes"
|
||||
@@ -2344,13 +2382,63 @@ _externalResourceInstaller:
|
||||
_dataSaver:
|
||||
_media:
|
||||
title: "Loading Media"
|
||||
description: "Prevents images/videos from being loaded automatically. Hidden images/videos will be loaded when tapped."
|
||||
_avatar:
|
||||
title: "Avatar image"
|
||||
description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic."
|
||||
_urlPreview:
|
||||
title: "URL preview thumbnails"
|
||||
description: "URL preview thumbnail images will no longer be loaded."
|
||||
_code:
|
||||
title: "Code highlighting"
|
||||
description: "If code highlighting notations are used in MFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data."
|
||||
_hemisphere:
|
||||
N: "Northern Hemisphere"
|
||||
S: "Southern Hemisphere"
|
||||
caption: "Used in some client settings to determine season."
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Game settings"
|
||||
chooseBoard: "Choose a board"
|
||||
blackOrWhite: "Black/White"
|
||||
blackIs: "{name} is playing Black"
|
||||
rules: "Rules"
|
||||
thisGameIsStartedSoon: "The game will begin shortly"
|
||||
waitingForOther: "Waiting for opponent's turn"
|
||||
waitingForMe: "Waiting for your turn"
|
||||
waitingBoth: "Get ready"
|
||||
ready: "Ready"
|
||||
cancelReady: "Not ready"
|
||||
opponentTurn: "Opponent's turn"
|
||||
myTurn: "Your turn"
|
||||
turnOf: "It's {name}'s turn"
|
||||
pastTurnOf: "{name}'s turn"
|
||||
surrender: "Surrender"
|
||||
surrendered: "Surrendered"
|
||||
timeout: "Out of time"
|
||||
drawn: "Draw"
|
||||
won: "{name} wins"
|
||||
black: "Black"
|
||||
white: "White"
|
||||
total: "Total"
|
||||
turnCount: "Turn {count}"
|
||||
myGames: "My rounds"
|
||||
allGames: "All rounds"
|
||||
ended: "Ended"
|
||||
playing: "Currently playing"
|
||||
isLlotheo: "The one with fewer stones wins (Llotheo)"
|
||||
loopedMap: "Looping map"
|
||||
canPutEverywhere: "Tiles are placeable everywhere"
|
||||
timeLimitForEachTurn: "Time limit for turn"
|
||||
freeMatch: "Free Match"
|
||||
lookingForPlayer: "Finding opponent..."
|
||||
gameCanceled: "The game has been cancelled."
|
||||
shareToTlTheGameWhenStart: "Share Game to timeline when started"
|
||||
iStartedAGame: "The game has begun! #MisskeyReversi"
|
||||
opponentHasSettingsChanged: "The opponent has changed their settings."
|
||||
allowIrregularRules: "Irregular rules (completely free)"
|
||||
disallowIrregularRules: "No irregular rules"
|
||||
_offlineScreen:
|
||||
title: "Offline - cannot connect to the server"
|
||||
header: "Unable to connect to the server"
|
||||
|
||||
|
@@ -2428,3 +2428,4 @@ _dataSaver:
|
||||
description: "Si se usa resaltado de código en MFM, etc., no se cargará hasta pulsar en ello. El resaltado de sintaxis requiere la descarga de archivos de definición para cada lenguaje de programación. Debido a esto, al deshabilitar la carga automática de estos archivos reducirás el consumo de datos."
|
||||
_reversi:
|
||||
total: "Total"
|
||||
|
||||
|
@@ -399,7 +399,7 @@ antennaKeywords: "Mots clés à recevoir"
|
||||
antennaExcludeKeywords: "Mots clés à exclure"
|
||||
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
|
||||
notifyAntenna: "Me notifier pour les nouvelles notes"
|
||||
withFileAntenna: "Notes ayant des attachements uniquement"
|
||||
withFileAntenna: "Notes ayant des fichiers joints uniquement"
|
||||
enableServiceworker: "Activer ServiceWorker"
|
||||
antennaUsersDescription: "Saisissez un seul nom d’utilisateur·rice par ligne"
|
||||
caseSensitive: "Sensible à la casse"
|
||||
@@ -2085,3 +2085,4 @@ _dataSaver:
|
||||
description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la MFM, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données."
|
||||
_reversi:
|
||||
total: "Total"
|
||||
|
||||
|
@@ -3,3 +3,4 @@ _lang_: "japanski"
|
||||
ok: "OK"
|
||||
gotIt: "Razumijem"
|
||||
cancel: "otkazati"
|
||||
|
||||
|
@@ -16,3 +16,4 @@ _2fa:
|
||||
renewTOTPCancel: "Sispann"
|
||||
_widgets:
|
||||
profile: "pwofil"
|
||||
|
||||
|
@@ -102,3 +102,4 @@ _deck:
|
||||
_columns:
|
||||
notifications: "Értesítések"
|
||||
tl: "Idővonal"
|
||||
|
||||
|
@@ -2321,3 +2321,4 @@ _dataSaver:
|
||||
description: "Jika notasi penyorotan kode digunakan di MFM, dll. Fungsi tersebut tidak akan dimuat apabila tidak diketuk. Penyorotan sintaks membutuhkan pengunduhan berkas definisi penyorotan untuk setiap bahasa pemrograman. Oleh sebab itu, menonaktifkan pemuatan otomatis dari berkas ini dilakukan untuk mengurangi jumlah komunikasi data."
|
||||
_reversi:
|
||||
total: "Jumlah"
|
||||
|
||||
|
22
locales/index.d.ts
vendored
22
locales/index.d.ts
vendored
@@ -4894,7 +4894,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"readConfirmText": ParameterizedString<"title">;
|
||||
/**
|
||||
* 特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。
|
||||
* 特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。
|
||||
*/
|
||||
"shouldNotBeUsedToPresentPermanentInfo": string;
|
||||
/**
|
||||
@@ -9583,6 +9583,26 @@ export interface Locale extends ILocale {
|
||||
* 対局がキャンセルされました
|
||||
*/
|
||||
"gameCanceled": string;
|
||||
/**
|
||||
* 開始時に対局をタイムラインに投稿
|
||||
*/
|
||||
"shareToTlTheGameWhenStart": string;
|
||||
/**
|
||||
* 対局を開始しました! #MisskeyReversi
|
||||
*/
|
||||
"iStartedAGame": string;
|
||||
/**
|
||||
* 相手が設定を変更しました
|
||||
*/
|
||||
"opponentHasSettingsChanged": string;
|
||||
/**
|
||||
* 変則許可 (完全フリー)
|
||||
*/
|
||||
"allowIrregularRules": string;
|
||||
/**
|
||||
* 変則なし
|
||||
*/
|
||||
"disallowIrregularRules": string;
|
||||
};
|
||||
"_offlineScreen": {
|
||||
/**
|
||||
|
@@ -2356,3 +2356,4 @@ _dataSaver:
|
||||
description: "Impedire che il codice sorgente sia automaticamente evidenziato. Evidenziare il codice richiede il caricamento di un file per ogni linguaggio. Puoi evidenziare soltanto il codice che intendi leggere e ridurre il traffico inutilizzato."
|
||||
_reversi:
|
||||
total: "Totale"
|
||||
|
||||
|
@@ -1223,7 +1223,7 @@ _announcement:
|
||||
tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。"
|
||||
readConfirmTitle: "既読にしますか?"
|
||||
readConfirmText: "「{title}」の内容を読み、既読にします。"
|
||||
shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。"
|
||||
shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。"
|
||||
dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。"
|
||||
silence: "非通知"
|
||||
silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。"
|
||||
@@ -2553,6 +2553,11 @@ _reversi:
|
||||
freeMatch: "フリーマッチ"
|
||||
lookingForPlayer: "対戦相手を探しています"
|
||||
gameCanceled: "対局がキャンセルされました"
|
||||
shareToTlTheGameWhenStart: "開始時に対局をタイムラインに投稿"
|
||||
iStartedAGame: "対局を開始しました! #MisskeyReversi"
|
||||
opponentHasSettingsChanged: "相手が設定を変更しました"
|
||||
allowIrregularRules: "変則許可 (完全フリー)"
|
||||
disallowIrregularRules: "変則なし"
|
||||
|
||||
_offlineScreen:
|
||||
title: "オフライン - サーバーに接続できません"
|
||||
|
@@ -380,8 +380,11 @@ hcaptcha: "hCaptcha(キャプチャ)"
|
||||
enableHcaptcha: "hCaptcha(キャプチャ)をつけとく"
|
||||
hcaptchaSiteKey: "サイトキー"
|
||||
hcaptchaSecretKey: "シークレットキー"
|
||||
mcaptcha: "mCaptcha"
|
||||
enableMcaptcha: "hCaptcha(キャプチャ)をつけとく"
|
||||
mcaptchaSiteKey: "サイトキー"
|
||||
mcaptchaSecretKey: "シークレットキー"
|
||||
mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "reCAPTCHA(リキャプチャ)を有効にする"
|
||||
recaptchaSiteKey: "サイトキー"
|
||||
@@ -629,6 +632,7 @@ medium: "中"
|
||||
small: "小"
|
||||
generateAccessToken: "アクセストークンの発行"
|
||||
permission: "権限"
|
||||
adminPermission: "管理者権限"
|
||||
enableAll: "全部使えるようにする"
|
||||
disableAll: "全部使えへんようにする"
|
||||
tokenRequested: "アカウントへのアクセス許してやったらどうや"
|
||||
@@ -1055,6 +1059,8 @@ limitWidthOfReaction: "ツッコミの最大横幅を制限して、ちっさく
|
||||
noteIdOrUrl: "ノートIDかURL"
|
||||
video: "動画"
|
||||
videos: "動画"
|
||||
audio: "音声"
|
||||
audioFiles: "音声"
|
||||
dataSaver: "データケチケチ"
|
||||
accountMigration: "アカウントのお引っ越し"
|
||||
accountMoved: "このユーザーはさらのアカウントに引っ越したで:"
|
||||
@@ -1187,7 +1193,25 @@ seasonalScreenEffect: "季節にあった画面の動き"
|
||||
decorate: "デコる"
|
||||
addMfmFunction: "装飾つける"
|
||||
enableQuickAddMfmFunction: "ややこしいMFMのピッカーを出す"
|
||||
bubbleGame: "バブルゲーム"
|
||||
sfx: "効果音"
|
||||
soundWillBePlayed: "サウンドが再生されるで"
|
||||
showReplay: "リプレイ見る"
|
||||
replay: "リプレイ"
|
||||
replaying: "リプレイ中"
|
||||
ranking: "ランキング"
|
||||
lastNDays: "直近{n}日"
|
||||
backToTitle: "タイトルへ"
|
||||
hemisphere: "住んでる地域"
|
||||
withSensitive: "センシティブなファイルを含むノートを表示"
|
||||
userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
|
||||
enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
||||
_bubbleGame:
|
||||
howToPlay: "遊び方"
|
||||
_howToPlay:
|
||||
section1: "位置を調整してハコにモノを落とすで。"
|
||||
section2: "同じもんがくっついたら別のやつになって、スコアがもらえるで。"
|
||||
section3: "モノがハコからあふれたらゲームオーバーや。ハコからあふれんようにしながらモノを融合させてハイスコアを目指しいや!"
|
||||
_announcement:
|
||||
forExistingUsers: "もうおるユーザーのみ"
|
||||
forExistingUsersDescription: "オンにしたらこのお知らせができた時点でおる人らにだけお知らせが行くで。切ったらこの知らせが行ったあとにアカウント作った人にもちゃんとお知らせが行くで。"
|
||||
@@ -1558,6 +1582,13 @@ _achievements:
|
||||
_tutorialCompleted:
|
||||
title: "Misskeyひよっこ講座 修了証"
|
||||
description: "チュートリアル全部やった"
|
||||
_bubbleGameExplodingHead:
|
||||
title: "🤯"
|
||||
description: "バブルゲームで最も大きいモノを出した"
|
||||
_bubbleGameDoubleExplodingHead:
|
||||
title: "ダブル🤯"
|
||||
description: "バブルゲームで最も大きいモノを2つ同時に出した"
|
||||
flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて"
|
||||
_role:
|
||||
new: "ロールの作成"
|
||||
edit: "ロールの編集"
|
||||
@@ -2002,9 +2033,9 @@ _auth:
|
||||
_antennaSources:
|
||||
all: "みんなのノート"
|
||||
homeTimeline: "フォローしとるユーザーのノート"
|
||||
users: "選らんだ一人か複数のユーザーのノート"
|
||||
users: "選んだ一人か複数のユーザーのノート"
|
||||
userList: "選んだリストのユーザーのノート"
|
||||
userBlacklist: "選んだ1人か複数のユーザーのノート"
|
||||
userBlacklist: "選んだ一人か複数のユーザーを除いた全てのノート"
|
||||
_weekday:
|
||||
sunday: "日曜日"
|
||||
monday: "月曜日"
|
||||
@@ -2410,5 +2441,53 @@ _dataSaver:
|
||||
_code:
|
||||
title: "コードハイライト"
|
||||
description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。"
|
||||
_hemisphere:
|
||||
N: "北半球"
|
||||
S: "南半球"
|
||||
caption: "一部のクライアント設定で、季節を判定するのに使用するで。"
|
||||
_reversi:
|
||||
reversi: "リバーシ"
|
||||
gameSettings: "対局の設定"
|
||||
chooseBoard: "ボードを選択"
|
||||
blackOrWhite: "先行/後攻"
|
||||
blackIs: "{name}が黒(先行)"
|
||||
rules: "ルール"
|
||||
thisGameIsStartedSoon: "対局、そろそろ開始されるで。"
|
||||
waitingForOther: "相手の準備が完了するのを待ってんで。"
|
||||
waitingForMe: "あんさんの準備が完了すんのを待ってんで"
|
||||
waitingBoth: "準備してなー"
|
||||
ready: "準備完了"
|
||||
cancelReady: "準備を再開"
|
||||
opponentTurn: "相手のターンやで"
|
||||
myTurn: "あんさんのターンや"
|
||||
turnOf: "{name}のターンやで"
|
||||
pastTurnOf: "{name}のターン"
|
||||
surrender: "投了"
|
||||
surrendered: "投了により"
|
||||
timeout: "時間切れ"
|
||||
drawn: "引き分け"
|
||||
won: "{name}の勝ち"
|
||||
black: "黒"
|
||||
white: "白"
|
||||
total: "合計"
|
||||
turnCount: "{count}ターン目"
|
||||
myGames: "自分の対局"
|
||||
allGames: "みんなの対局"
|
||||
ended: "終了"
|
||||
playing: "対局中"
|
||||
isLlotheo: "石の少ない方が勝ち(ロセオ)"
|
||||
loopedMap: "ループマップ"
|
||||
canPutEverywhere: "どこでも置けるモード"
|
||||
timeLimitForEachTurn: "1ターンの時間制限"
|
||||
freeMatch: "フリーマッチ"
|
||||
lookingForPlayer: "対戦相手を探してるで"
|
||||
gameCanceled: "対局がキャンセルされたわ"
|
||||
shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで"
|
||||
iStartedAGame: "対局し始めたで! #MisskeyReversi"
|
||||
opponentHasSettingsChanged: "相手が設定変えたで"
|
||||
allowIrregularRules: "変則許可 (完全フリー)"
|
||||
disallowIrregularRules: "変則なし"
|
||||
_offlineScreen:
|
||||
title: "オフライン - サーバーに接続できひんで"
|
||||
header: "サーバーに接続できへんわ"
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
---
|
||||
_lang_: "la .lojban."
|
||||
headlineMisskey: "lo se tcana noi jorne fi loi notci"
|
||||
|
||||
|
@@ -104,3 +104,4 @@ _deck:
|
||||
_columns:
|
||||
notifications: "Ilɣuyen"
|
||||
list: "Tibdarin"
|
||||
|
||||
|
@@ -84,3 +84,4 @@ _deck:
|
||||
notifications: "ಅಧಿಸೂಚನೆಗಳು"
|
||||
tl: "ಸಮಯಸಾಲು"
|
||||
mentions: "ಹೆಸರಿಸಿದ"
|
||||
|
||||
|
@@ -515,7 +515,7 @@ objectStoragePrefixDesc: "요 Prefix 디렉토리 안에다가 파일이 들어
|
||||
objectStorageEndpoint: "Endpoint"
|
||||
objectStorageEndpointDesc: "AWS S3을 쓸라멘 요는 비워두고, 아이멘은 그 서비스 가이드에 맞게 endpoint를 넣어 주이소. '<host>' 내지 '<host>:<port>'처럼 넣십니다."
|
||||
objectStorageRegion: "Region"
|
||||
objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 써먹을 서비스에 region 개념 같은 게 읎다! 카면은 대신에 'us-east-1'을 옇어 놓으이소. AWS 설정 파일이나 환경 변수를 갖다 끌어다 쓸 거면은 요는 비워 두이소."
|
||||
objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 만약에 내 서비스엔 region 같은 개념이 읎다, 카면은 대신에 'us-east-1'라고 해 두이소. AWS 설정 파일이나 환경 변수를 끌어다 쓰겠다믄 요는 비워 두이소."
|
||||
objectStorageUseSSL: "SSL 쓰기"
|
||||
objectStorageUseSSLDesc: "API 호출할 때 HTTPS 안 쓸거면은 꺼 두이소"
|
||||
objectStorageUseProxy: "연결에 프락시 사용"
|
||||
@@ -538,7 +538,7 @@ volume: "음량"
|
||||
masterVolume: "대빵 음량"
|
||||
notUseSound: "음소거하기"
|
||||
useSoundOnlyWhenActive: "Misskey가 활성화되어 있을 때만 소리 내기"
|
||||
details: "좀 더"
|
||||
details: "자세히"
|
||||
chooseEmoji: "이모지 선택"
|
||||
unableToProcess: "작업 다 몬 했십니다"
|
||||
recentUsed: "최근 쓴 놈"
|
||||
@@ -726,3 +726,4 @@ _moderationLogTypes:
|
||||
resolveAbuseReport: "신고 해겔하기"
|
||||
_reversi:
|
||||
total: "합계"
|
||||
|
||||
|
@@ -380,9 +380,11 @@ hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "hCaptcha 활성화"
|
||||
hcaptchaSiteKey: "사이트 키"
|
||||
hcaptchaSecretKey: "시크릿 키"
|
||||
mcaptcha: "mCaptcha"
|
||||
enableMcaptcha: "mCaptcha 활성화"
|
||||
mcaptchaSiteKey: "사이트 키"
|
||||
mcaptchaSecretKey: "시크릿 키"
|
||||
mcaptchaInstanceUrl: "mCaptcha 인스턴스 URL"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "reCAPTCHA 활성화"
|
||||
recaptchaSiteKey: "사이트 키"
|
||||
@@ -630,6 +632,7 @@ medium: "보통"
|
||||
small: "작게"
|
||||
generateAccessToken: "액세스 토큰 생성"
|
||||
permission: "권한"
|
||||
adminPermission: "관리자 권한"
|
||||
enableAll: "전체 선택"
|
||||
disableAll: "전체 해제"
|
||||
tokenRequested: "계정 접근 허용"
|
||||
@@ -673,6 +676,7 @@ useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용됩니
|
||||
other: "기타"
|
||||
regenerateLoginToken: "로그인 토큰을 재생성"
|
||||
regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
|
||||
theKeywordWhenSearchingForCustomEmoji: "맞춤 이모티콘을 검색할 때 키워드가 됩니다."
|
||||
setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
||||
fileIdOrUrl: "파일 ID 또는 URL"
|
||||
behavior: "동작"
|
||||
@@ -1055,6 +1059,8 @@ limitWidthOfReaction: "리액션의 최대 폭을 제한하고 작게 표시하
|
||||
noteIdOrUrl: "노트 ID 및 URL"
|
||||
video: "동영상"
|
||||
videos: "동영상"
|
||||
audio: "소리"
|
||||
audioFiles: "소리"
|
||||
dataSaver: "데이터 절약 모드"
|
||||
accountMigration: "계정 이동"
|
||||
accountMoved: "이 사용자는 다음 계정으로 이사했습니다:"
|
||||
@@ -1187,10 +1193,25 @@ seasonalScreenEffect: "계절에 따른 효과 보이기"
|
||||
decorate: "장식하기"
|
||||
addMfmFunction: "장식 추가하기"
|
||||
enableQuickAddMfmFunction: "상급자용 MFM 선택기 표시하기"
|
||||
bubbleGame: "버블 게임"
|
||||
sfx: "효과음"
|
||||
soundWillBePlayed: "소리가 재생됩니다"
|
||||
showReplay: "리플레이 보기"
|
||||
replay: "리플레이"
|
||||
replaying: "리플레이 중"
|
||||
ranking: "랭킹"
|
||||
lastNDays: "최근 {n}일"
|
||||
backToTitle: "타이틀로 가기"
|
||||
hemisphere: "거주 지역"
|
||||
withSensitive: "민감한 파일이 포함된 노트 보기"
|
||||
userSaysSomethingSensitive: "{name}의 민감한 파일이 포함된 게시물"
|
||||
enableHorizontalSwipe: "스와이프하여 탭 전환"
|
||||
_bubbleGame:
|
||||
howToPlay: "설명"
|
||||
_howToPlay:
|
||||
section1: "위치를 조정하여 상자에 물건을 떨어뜨립니다."
|
||||
section2: "같은 종류의 물건이 붙으면 다른 물건으로 바뀌면서 점수를 얻게 됩니다."
|
||||
section3: "상자에서 물건이 넘치면 게임 오버입니다. 상자에서 물건이 넘치지 않도록 하면서 물건을 융합하여 높은 점수를 획득하세요!"
|
||||
_announcement:
|
||||
forExistingUsers: "기존 유저에게만 알림"
|
||||
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
|
||||
@@ -1561,6 +1582,13 @@ _achievements:
|
||||
_tutorialCompleted:
|
||||
title: "Misskey 입문자 과정 수료증"
|
||||
description: "튜토리얼을 완료했습니다"
|
||||
_bubbleGameExplodingHead:
|
||||
title: "🤯"
|
||||
description: "버블 게임에서 가장 큰 물건을 내놓았다"
|
||||
_bubbleGameDoubleExplodingHead:
|
||||
title: "더블 🤯"
|
||||
description: "버블게임에서 가장 큰 물건 2개를 동시에 내놓았다."
|
||||
flavor: "이 정도만 도시락통에 🤯 🤯 조금만 더"
|
||||
_role:
|
||||
new: "새 역할 생성"
|
||||
edit: "역할 수정"
|
||||
@@ -2413,5 +2441,53 @@ _dataSaver:
|
||||
_code:
|
||||
title: "문자열 강조"
|
||||
description: "MFM 등으로 문자열 강조 기법을 사용할 때 누르기 전에는 불러오지 않습니다. 문자열 강조에서는 강조할 언어마다 그 정의 파일을 불러와야 하지만 이를 자동으로 불러오지 않으므로 데이터 사용량을 줄일 수 있습니다."
|
||||
_hemisphere:
|
||||
N: "북반구"
|
||||
S: "남반구"
|
||||
caption: "일부 클라이언트 설정에서 계절을 판단하기 위해 사용합니다."
|
||||
_reversi:
|
||||
reversi: "리버시"
|
||||
gameSettings: "대국 설정"
|
||||
chooseBoard: "보드 선택"
|
||||
blackOrWhite: "선공/후공"
|
||||
blackIs: "{name}님이 흑(선공)"
|
||||
rules: "규칙"
|
||||
thisGameIsStartedSoon: "대국이 곧 시작됩니다"
|
||||
waitingForOther: "상대방의 준비가 완료되기를 기다리고 있습니다."
|
||||
waitingForMe: "당신의 준비가 완료되기를 기다리고 있습니다."
|
||||
waitingBoth: "준비하세요"
|
||||
ready: "준비 완료"
|
||||
cancelReady: "준비 다시 시작"
|
||||
opponentTurn: "상대의 차례입니다"
|
||||
myTurn: "당신의 차례입니다"
|
||||
turnOf: "{name}의 차례입니다"
|
||||
pastTurnOf: "{name}의 차례"
|
||||
surrender: "기권"
|
||||
surrendered: "기권에 의해"
|
||||
timeout: "시간 초과"
|
||||
drawn: "무승부"
|
||||
won: "{name}의 승리"
|
||||
black: "흑"
|
||||
white: "백"
|
||||
total: "합계"
|
||||
turnCount: "{count}턴 째"
|
||||
myGames: "내 대국"
|
||||
allGames: "모두의 대국"
|
||||
ended: "종료"
|
||||
playing: "대국 중"
|
||||
isLlotheo: "돌이 적은 사람이 승리 (로세오)"
|
||||
loopedMap: "루프 지도"
|
||||
canPutEverywhere: "어디에도 둘 수 있는 모드"
|
||||
timeLimitForEachTurn: "1턴의 시간 제한"
|
||||
freeMatch: "프리매치"
|
||||
lookingForPlayer: "상대를 찾고 있습니다"
|
||||
gameCanceled: "대국이 취소되었습니다"
|
||||
shareToTlTheGameWhenStart: "대국 시작 시 타임라인에 대국을 게시"
|
||||
iStartedAGame: "대국이 시작되었습니다! #MisskeyReversi"
|
||||
opponentHasSettingsChanged: "상대방이 설정을 변경했습니다"
|
||||
allowIrregularRules: "규칙변경 허가 (완전 자유)"
|
||||
disallowIrregularRules: "규칙변경 없음"
|
||||
_offlineScreen:
|
||||
title: "오프라인 - 서버에 접속할 수 없습니다"
|
||||
header: "서버에 접속할 수 없습니다"
|
||||
|
||||
|
@@ -466,3 +466,4 @@ _webhookSettings:
|
||||
name: "ຊື່"
|
||||
_moderationLogTypes:
|
||||
suspend: "ລະງັບ"
|
||||
|
||||
|
@@ -497,3 +497,4 @@ _webhookSettings:
|
||||
_moderationLogTypes:
|
||||
suspend: "Opschorten"
|
||||
resetPassword: "Wachtwoord terugzetten"
|
||||
|
||||
|
@@ -720,3 +720,4 @@ _webhookSettings:
|
||||
name: "Navn"
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspender"
|
||||
|
||||
|
@@ -1399,3 +1399,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Zresetuj hasło"
|
||||
_reversi:
|
||||
total: "Łącznie"
|
||||
|
||||
|
@@ -1500,3 +1500,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Redefinir senha"
|
||||
_reversi:
|
||||
total: "Total"
|
||||
|
||||
|
@@ -729,3 +729,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Resetează parola"
|
||||
_reversi:
|
||||
total: "Total"
|
||||
|
||||
|
@@ -1972,3 +1972,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Сброс пароля:"
|
||||
_reversi:
|
||||
total: "Всего"
|
||||
|
||||
|
@@ -1 +1,2 @@
|
||||
---
|
||||
|
||||
|
@@ -1447,3 +1447,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Resetovať heslo"
|
||||
_reversi:
|
||||
total: "Celkom"
|
||||
|
||||
|
@@ -576,3 +576,4 @@ _webhookSettings:
|
||||
_moderationLogTypes:
|
||||
suspend: "Suspendera"
|
||||
resetPassword: "Återställ Lösenord"
|
||||
|
||||
|
@@ -2440,3 +2440,4 @@ _dataSaver:
|
||||
description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน MFM ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้"
|
||||
_reversi:
|
||||
total: "รวมทั้งหมด"
|
||||
|
||||
|
@@ -455,3 +455,4 @@ _deck:
|
||||
_moderationLogTypes:
|
||||
suspend: "askıya al"
|
||||
resetPassword: "Şifre sıfırlama"
|
||||
|
||||
|
@@ -17,3 +17,4 @@ _2fa:
|
||||
renewTOTPCancel: "ئۇنى توختىتىڭ"
|
||||
_widgets:
|
||||
profile: "profile"
|
||||
|
||||
|
@@ -1622,3 +1622,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Скинути пароль"
|
||||
_reversi:
|
||||
total: "Всього"
|
||||
|
||||
|
@@ -1090,3 +1090,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Parolni tiklash"
|
||||
_reversi:
|
||||
total: "Jami"
|
||||
|
||||
|
@@ -1852,3 +1852,4 @@ _moderationLogTypes:
|
||||
resetPassword: "Đặt lại mật khẩu"
|
||||
_reversi:
|
||||
total: "Tổng cộng"
|
||||
|
||||
|
@@ -1185,6 +1185,7 @@ useGroupedNotifications: "分组显示通知"
|
||||
signupPendingError: "确认电子邮件时出现错误。链接可能已过期。"
|
||||
cwNotationRequired: "在启用「隐藏内容」时必须输入注释"
|
||||
doReaction: "回应"
|
||||
code: "代码"
|
||||
reloadRequiredToApplySettings: "需要重新载入来使设置生效"
|
||||
remainingN: "剩余:{n}"
|
||||
overwriteContentConfirm: "将覆盖现有内容。确定吗?"
|
||||
@@ -1200,9 +1201,16 @@ replaying: "重播中"
|
||||
ranking: "排行榜"
|
||||
lastNDays: "最近 {n} 天"
|
||||
backToTitle: "返回标题"
|
||||
hemisphere: "居住地区"
|
||||
withSensitive: "显示包含敏感媒体的帖子"
|
||||
userSaysSomethingSensitive: "含 {name} 敏感文件的帖子"
|
||||
enableHorizontalSwipe: "滑动切换标签页"
|
||||
_bubbleGame:
|
||||
howToPlay: "游戏说明"
|
||||
_howToPlay:
|
||||
section1: "对准位置将Emoji投入盒子。"
|
||||
section2: "相同的Emoji相互接触合成后会得到新的Emoji,以此获得分数。"
|
||||
section3: "如果Emoji从箱子中溢出游戏将会结束。在防止Emoji溢出的同时,不断合成新的Emoji,来获取更高的分数吧!"
|
||||
_announcement:
|
||||
forExistingUsers: "仅限现有用户"
|
||||
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
|
||||
@@ -1575,6 +1583,10 @@ _achievements:
|
||||
description: "完成了教学"
|
||||
_bubbleGameExplodingHead:
|
||||
title: "🤯"
|
||||
description: "你合成出了游戏里最大的Emoji"
|
||||
_bubbleGameDoubleExplodingHead:
|
||||
title: "两个🤯"
|
||||
description: "你合成出了2个游戏里最大的Emoji"
|
||||
_role:
|
||||
new: "创建角色"
|
||||
edit: "编辑角色"
|
||||
@@ -2427,9 +2439,48 @@ _dataSaver:
|
||||
_code:
|
||||
title: "代码高亮"
|
||||
description: "如果使用了代码高亮标记,例如在 MFM 中,则在点击之前不会加载。 代码高亮要求加载每种高亮语言的定义文件,由于这些文件不再自动加载,因此有望减少数据传输量。"
|
||||
_hemisphere:
|
||||
N: "北半球"
|
||||
S: "南半球"
|
||||
caption: "在某些客户端设置中用来确定季节"
|
||||
_reversi:
|
||||
reversi: "黑白棋"
|
||||
gameSettings: "对局设置"
|
||||
blackOrWhite: "先手/后手"
|
||||
blackIs: "{name}执黑(先手)"
|
||||
rules: "规则"
|
||||
thisGameIsStartedSoon: "对局即将开始"
|
||||
waitingForOther: "等待对手准备"
|
||||
waitingForMe: "等待你的准备"
|
||||
waitingBoth: "请准备"
|
||||
ready: "准备就绪"
|
||||
cancelReady: "重新准备"
|
||||
opponentTurn: "对手的回合"
|
||||
myTurn: "你的回合"
|
||||
turnOf: "{name}的回合"
|
||||
pastTurnOf: "{name}的回合"
|
||||
timeout: "超时"
|
||||
drawn: "平局"
|
||||
won: "{name}获胜"
|
||||
black: "黑"
|
||||
white: "白"
|
||||
total: "总计"
|
||||
turnCount: "第{count}回合"
|
||||
myGames: "我的对局"
|
||||
allGames: "所有对局"
|
||||
ended: "结束"
|
||||
playing: "对局中"
|
||||
canPutEverywhere: "无限制放置模式"
|
||||
timeLimitForEachTurn: "1回合的时间限制"
|
||||
freeMatch: "自由匹配"
|
||||
lookingForPlayer: "正在寻找对手"
|
||||
gameCanceled: "对局被取消了"
|
||||
shareToTlTheGameWhenStart: "开始时在时间线发布对局"
|
||||
iStartedAGame: "对局开始!#MisskeyReversi"
|
||||
opponentHasSettingsChanged: "对手更改了设定"
|
||||
allowIrregularRules: "允许非常规规则(完全自由)"
|
||||
disallowIrregularRules: "禁止非常规规则"
|
||||
_offlineScreen:
|
||||
title: "离线——无法连接到服务器"
|
||||
header: "无法连接到服务器"
|
||||
|
||||
|
@@ -1202,6 +1202,9 @@ replaying: "重播中"
|
||||
ranking: "排行榜"
|
||||
lastNDays: "過去 {n} 天"
|
||||
backToTitle: "回到遊戲標題頁"
|
||||
hemisphere: "您居住的地區"
|
||||
withSensitive: "顯示包含敏感檔案的貼文"
|
||||
userSaysSomethingSensitive: "包含 {name} 敏感檔案的貼文"
|
||||
enableHorizontalSwipe: "滑動切換時間軸"
|
||||
_bubbleGame:
|
||||
howToPlay: "玩法說明"
|
||||
@@ -1218,7 +1221,7 @@ _announcement:
|
||||
tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。"
|
||||
readConfirmTitle: "標記為已讀嗎?"
|
||||
readConfirmText: "閱讀「{title}」的內容並標記為已讀。"
|
||||
shouldNotBeUsedToPresentPermanentInfo: "由於可能會破壞使用者體驗,尤其是對於新使用者而言,我們建議使用公告來發布有時效性的資訊而不是固定不變的資訊。"
|
||||
shouldNotBeUsedToPresentPermanentInfo: "為了避免損害新用戶的使用體驗,建議使用公告來發布即時性的訊息,而不是用於固定不變的資訊。"
|
||||
dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。"
|
||||
silence: "不發送通知"
|
||||
silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。"
|
||||
@@ -2438,5 +2441,53 @@ _dataSaver:
|
||||
_code:
|
||||
title: "程式碼突出顯示"
|
||||
description: "如果使用了 MFM 的程式碼突顯標記,則在點擊之前不會載入。程式碼突顯要求加載每種程式語言的突顯定義檔案,但由於這些檔案不再自動載入,因此有望減少資料流量。"
|
||||
_hemisphere:
|
||||
N: "北半球"
|
||||
S: "南半球"
|
||||
caption: "在某些客戶端的設定中,用於判斷季節。"
|
||||
_reversi:
|
||||
reversi: "黑白棋"
|
||||
gameSettings: "對弈設定"
|
||||
chooseBoard: "選擇棋盤"
|
||||
blackOrWhite: "先手/後手"
|
||||
blackIs: "{name} 為黑棋(先攻)"
|
||||
rules: "規則"
|
||||
thisGameIsStartedSoon: "對弈即將開始"
|
||||
waitingForOther: "等待對手準備就緒"
|
||||
waitingForMe: "等待您準備就緒"
|
||||
waitingBoth: "請準備"
|
||||
ready: "準備就緒"
|
||||
cancelReady: "重新準備"
|
||||
opponentTurn: "對手的回合"
|
||||
myTurn: "您的回合"
|
||||
turnOf: "{name} 的回合"
|
||||
pastTurnOf: "{name} 的回合"
|
||||
surrender: "認輸"
|
||||
surrendered: "對手認輸"
|
||||
timeout: "時間到"
|
||||
drawn: "平手"
|
||||
won: "{name} 獲勝"
|
||||
black: "黑"
|
||||
white: "白"
|
||||
total: "合計"
|
||||
turnCount: "{count} 回合"
|
||||
myGames: "我的對弈"
|
||||
allGames: "所有對弈"
|
||||
ended: "已結束"
|
||||
playing: "正在對弈"
|
||||
isLlotheo: "子較少的一方為勝(顛倒規則)"
|
||||
loopedMap: "循環棋盤"
|
||||
canPutEverywhere: "隨意置放模式"
|
||||
timeLimitForEachTurn: "每回合的時間限制"
|
||||
freeMatch: "自由對戰"
|
||||
lookingForPlayer: "正在搜尋對手"
|
||||
gameCanceled: "對弈已被取消"
|
||||
shareToTlTheGameWhenStart: "在遊戲開始時將對弈資訊發布到時間軸"
|
||||
iStartedAGame: "對弈開始了! #MisskeyReversi"
|
||||
opponentHasSettingsChanged: "對手更改了設定"
|
||||
allowIrregularRules: "允許異常規則(完全自由)"
|
||||
disallowIrregularRules: "不允許異常規則"
|
||||
_offlineScreen:
|
||||
title: "離線-無法連接伺服器"
|
||||
header: "無法連接伺服器"
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2024.2.0-beta.1",
|
||||
"version": "2024.2.0-beta.9",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -56,8 +56,8 @@
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||
"@typescript-eslint/parser": "6.19.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.18.1",
|
||||
"@typescript-eslint/parser": "6.18.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.6.3",
|
||||
"eslint": "8.56.0",
|
||||
|
@@ -3,6 +3,6 @@ import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config);
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
|
||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
16
packages/backend/migration/1706081514499-reversi-6.js
Normal file
16
packages/backend/migration/1706081514499-reversi-6.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Reversi61706081514499 {
|
||||
name = 'Reversi61706081514499'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "reversi_game" ADD "noIrregularRules" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "noIrregularRules"`);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class FixMetaDisableRegistration1706791962000 {
|
||||
name = 'FixMetaDisableRegistration1706791962000'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`alter table meta alter column "disableRegistration" set default true;`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`alter table meta alter column "disableRegistration" set default false;`);
|
||||
}
|
||||
}
|
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./built/boot/entry.js",
|
||||
"start:test": "NODE_ENV=test node ./built/boot/entry.js",
|
||||
"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
|
||||
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
||||
"check:connect": "node ./check_connect.js",
|
||||
@@ -31,7 +31,7 @@
|
||||
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
||||
"test-and-coverage": "pnpm jest-and-coverage",
|
||||
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
||||
"generate-api-json": "node ./generate_api_json.js"
|
||||
"generate-api-json": "pnpm build && node ./generate_api_json.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
@@ -67,9 +67,9 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.412.0",
|
||||
"@aws-sdk/lib-storage": "3.412.0",
|
||||
"@bull-board/api": "5.10.2",
|
||||
"@bull-board/fastify": "5.10.2",
|
||||
"@bull-board/ui": "5.10.2",
|
||||
"@bull-board/api": "5.14.0",
|
||||
"@bull-board/fastify": "5.14.0",
|
||||
"@bull-board/ui": "5.14.0",
|
||||
"@discordapp/twemoji": "15.0.2",
|
||||
"@fastify/accepts": "4.3.0",
|
||||
"@fastify/cookie": "9.3.1",
|
||||
@@ -85,11 +85,11 @@
|
||||
"@nestjs/core": "10.2.10",
|
||||
"@nestjs/testing": "10.2.10",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@simplewebauthn/server": "9.0.0",
|
||||
"@simplewebauthn/server": "9.0.1",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.1.10",
|
||||
"@swc/cli": "0.1.63",
|
||||
"@swc/core": "1.3.105",
|
||||
"@swc/core": "1.3.107",
|
||||
"@twemoji/parser": "15.0.0",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
@@ -98,7 +98,7 @@
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "5.1.4",
|
||||
"bullmq": "5.1.5",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.1",
|
||||
"chalk": "5.3.0",
|
||||
@@ -107,7 +107,6 @@
|
||||
"cli-highlight": "2.1.11",
|
||||
"color-convert": "2.0.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"crc-32": "^1.2.2",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "4.25.2",
|
||||
@@ -116,7 +115,7 @@
|
||||
"file-type": "19.0.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "14.0.0",
|
||||
"got": "14.1.0",
|
||||
"happy-dom": "10.0.3",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.1",
|
||||
@@ -148,7 +147,7 @@
|
||||
"otpauth": "9.2.2",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.11.3",
|
||||
"pkce-challenge": "4.0.1",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"pug": "3.0.2",
|
||||
@@ -169,12 +168,12 @@
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.21.23",
|
||||
"systeminformation": "5.21.24",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.19",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.3.3",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
@@ -185,7 +184,7 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@nestjs/platform-express": "10.3.0",
|
||||
"@nestjs/platform-express": "10.3.1",
|
||||
"@simplewebauthn/typescript-types": "8.3.4",
|
||||
"@swc/jest": "0.2.31",
|
||||
"@types/accepts": "1.3.7",
|
||||
@@ -204,13 +203,13 @@
|
||||
"@types/jsrsasign": "10.5.12",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.11.5",
|
||||
"@types/node": "20.11.10",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/oauth": "0.9.4",
|
||||
"@types/oauth2orize": "1.11.3",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.10.9",
|
||||
"@types/pg": "8.11.0",
|
||||
"@types/pug": "2.0.10",
|
||||
"@types/punycode": "2.1.3",
|
||||
"@types/qrcode": "1.5.5",
|
||||
@@ -227,8 +226,8 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||
"@typescript-eslint/parser": "6.19.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.18.1",
|
||||
"@typescript-eslint/parser": "6.18.1",
|
||||
"aws-sdk-client-mock": "3.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.56.0",
|
||||
|
@@ -74,10 +74,10 @@ type Source = {
|
||||
|
||||
deliverJobConcurrency?: number;
|
||||
inboxJobConcurrency?: number;
|
||||
relashionshipJobConcurrency?: number;
|
||||
relationshipJobConcurrency?: number;
|
||||
deliverJobPerSec?: number;
|
||||
inboxJobPerSec?: number;
|
||||
relashionshipJobPerSec?: number;
|
||||
relationshipJobPerSec?: number;
|
||||
deliverJobMaxAttempts?: number;
|
||||
inboxJobMaxAttempts?: number;
|
||||
|
||||
@@ -135,10 +135,10 @@ export type Config = {
|
||||
outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
|
||||
deliverJobConcurrency: number | undefined;
|
||||
inboxJobConcurrency: number | undefined;
|
||||
relashionshipJobConcurrency: number | undefined;
|
||||
relationshipJobConcurrency: number | undefined;
|
||||
deliverJobPerSec: number | undefined;
|
||||
inboxJobPerSec: number | undefined;
|
||||
relashionshipJobPerSec: number | undefined;
|
||||
relationshipJobPerSec: number | undefined;
|
||||
deliverJobMaxAttempts: number | undefined;
|
||||
inboxJobMaxAttempts: number | undefined;
|
||||
proxyRemoteFiles: boolean | undefined;
|
||||
@@ -241,10 +241,10 @@ export function loadConfig(): Config {
|
||||
outgoingAddressFamily: config.outgoingAddressFamily,
|
||||
deliverJobConcurrency: config.deliverJobConcurrency,
|
||||
inboxJobConcurrency: config.inboxJobConcurrency,
|
||||
relashionshipJobConcurrency: config.relashionshipJobConcurrency,
|
||||
relationshipJobConcurrency: config.relationshipJobConcurrency,
|
||||
deliverJobPerSec: config.deliverJobPerSec,
|
||||
inboxJobPerSec: config.inboxJobPerSec,
|
||||
relashionshipJobPerSec: config.relashionshipJobPerSec,
|
||||
relationshipJobPerSec: config.relationshipJobPerSec,
|
||||
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
||||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||
|
@@ -96,7 +96,7 @@ export class AccountMoveService {
|
||||
await this.apDeliverManagerService.deliverToFollowers(src, moveAct);
|
||||
|
||||
// Publish meUpdated event
|
||||
const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true });
|
||||
const iObj = await this.userEntityService.pack(src.id, src, { schema: 'MeDetailed', includeSecrets: true });
|
||||
this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
|
||||
|
||||
// Unfollow after 24 hours
|
||||
|
@@ -55,23 +55,29 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'antennaCreated':
|
||||
this.antennas.push({
|
||||
this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
userList: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
break;
|
||||
case 'antennaUpdated': {
|
||||
const idx = this.antennas.findIndex(a => a.id === body.id);
|
||||
if (idx >= 0) {
|
||||
this.antennas[idx] = {
|
||||
this.antennas[idx] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
userList: null, // joinなカラムは通常取ってこないので
|
||||
};
|
||||
} else {
|
||||
// サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり
|
||||
this.antennas.push({
|
||||
this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
userList: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -54,15 +54,15 @@ export interface MainEventTypes {
|
||||
reply: Packed<'Note'>;
|
||||
renote: Packed<'Note'>;
|
||||
follow: Packed<'UserDetailedNotMe'>;
|
||||
followed: Packed<'User'>;
|
||||
unfollow: Packed<'User'>;
|
||||
meUpdated: Packed<'User'>;
|
||||
followed: Packed<'UserLite'>;
|
||||
unfollow: Packed<'UserDetailedNotMe'>;
|
||||
meUpdated: Packed<'MeDetailed'>;
|
||||
pageEvent: {
|
||||
pageId: MiPage['id'];
|
||||
event: string;
|
||||
var: any;
|
||||
userId: MiUser['id'];
|
||||
user: Packed<'User'>;
|
||||
user: Packed<'UserDetailed'>;
|
||||
};
|
||||
urlUploadFinished: {
|
||||
marker?: string | null;
|
||||
@@ -92,7 +92,7 @@ export interface MainEventTypes {
|
||||
};
|
||||
driveFileCreated: Packed<'DriveFile'>;
|
||||
readAntenna: MiAntenna;
|
||||
receiveFollowRequest: Packed<'User'>;
|
||||
receiveFollowRequest: Packed<'UserLite'>;
|
||||
announcementCreated: {
|
||||
announcement: Packed<'Announcement'>;
|
||||
};
|
||||
@@ -140,8 +140,8 @@ type NoteStreamEventTypes = {
|
||||
};
|
||||
|
||||
export interface UserListEventTypes {
|
||||
userAdded: Packed<'User'>;
|
||||
userRemoved: Packed<'User'>;
|
||||
userAdded: Packed<'UserLite'>;
|
||||
userRemoved: Packed<'UserLite'>;
|
||||
}
|
||||
|
||||
export interface AntennaEventTypes {
|
||||
|
@@ -51,7 +51,10 @@ export class MetaService implements OnApplicationShutdown {
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'metaUpdated': {
|
||||
this.cache = body;
|
||||
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
proxyAccount: null, // joinなカラムは通常取ってこないので
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@@ -5,10 +5,9 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import CRC32 from 'crc-32';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import * as Reversi from 'misskey-reversi';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { IsNull, LessThan, MoreThan } from 'typeorm';
|
||||
import type {
|
||||
MiReversiGame,
|
||||
ReversiGamesRepository,
|
||||
@@ -25,7 +24,7 @@ import { Serialized } from '@/types.js';
|
||||
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
|
||||
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec
|
||||
const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
|
||||
|
||||
@Injectable()
|
||||
export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
@@ -86,60 +85,82 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
map: game.map,
|
||||
bw: game.bw,
|
||||
crc32: game.crc32,
|
||||
noIrregularRules: game.noIrregularRules,
|
||||
} satisfies Partial<MiReversiGame>;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
|
||||
public async matchSpecificUser(me: MiUser, targetUser: MiUser, multiple = false): Promise<MiReversiGame | null> {
|
||||
if (targetUser.id === me.id) {
|
||||
throw new Error('You cannot match yourself.');
|
||||
}
|
||||
|
||||
if (!multiple) {
|
||||
// 既にマッチしている対局が無いか探す(3分以内)
|
||||
const games = await this.reversiGamesRepository.find({
|
||||
where: [
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false },
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false },
|
||||
],
|
||||
relations: ['user1', 'user2'],
|
||||
order: { id: 'DESC' },
|
||||
});
|
||||
if (games.length > 0) {
|
||||
return games[0];
|
||||
}
|
||||
}
|
||||
|
||||
//#region 相手から既に招待されてないか確認
|
||||
const invitations = await this.redisClient.zrange(
|
||||
`reversi:matchSpecific:${me.id}`,
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
Date.now() - INVITATION_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
|
||||
if (invitations.includes(targetUser.id)) {
|
||||
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
|
||||
|
||||
const game = await this.reversiGamesRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
user1Id: targetUser.id,
|
||||
user2Id: me.id,
|
||||
user1Ready: false,
|
||||
user2Ready: false,
|
||||
isStarted: false,
|
||||
isEnded: false,
|
||||
logs: [],
|
||||
map: Reversi.maps.eighteight.data,
|
||||
bw: 'random',
|
||||
isLlotheo: false,
|
||||
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
this.cacheGame(game);
|
||||
|
||||
const packed = await this.reversiGameEntityService.packDetail(game, { id: targetUser.id });
|
||||
this.globalEventService.publishReversiStream(targetUser.id, 'matched', { game: packed });
|
||||
|
||||
return game;
|
||||
} else {
|
||||
this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
|
||||
|
||||
this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
|
||||
user: await this.userEntityService.pack(me, targetUser),
|
||||
const game = await this.matched(targetUser.id, me.id, {
|
||||
noIrregularRules: false,
|
||||
});
|
||||
|
||||
return null;
|
||||
return game;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
|
||||
redisPipeline.expire(`reversi:matchSpecific:${targetUser.id}`, 120, 'NX');
|
||||
await redisPipeline.exec();
|
||||
|
||||
this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
|
||||
user: await this.userEntityService.pack(me, targetUser),
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async matchAnyUser(me: MiUser): Promise<MiReversiGame | null> {
|
||||
public async matchAnyUser(me: MiUser, options: { noIrregularRules: boolean }, multiple = false): Promise<MiReversiGame | null> {
|
||||
if (!multiple) {
|
||||
// 既にマッチしている対局が無いか探す(3分以内)
|
||||
const games = await this.reversiGamesRepository.find({
|
||||
where: [
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, isStarted: false },
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user2Id: me.id, isStarted: false },
|
||||
],
|
||||
relations: ['user1', 'user2'],
|
||||
order: { id: 'DESC' },
|
||||
});
|
||||
if (games.length > 0) {
|
||||
return games[0];
|
||||
}
|
||||
}
|
||||
|
||||
//#region まず自分宛ての招待を探す
|
||||
const invitations = await this.redisClient.zrange(
|
||||
`reversi:matchSpecific:${me.id}`,
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
Date.now() - INVITATION_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
|
||||
@@ -147,23 +168,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
|
||||
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
|
||||
|
||||
const game = await this.reversiGamesRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
user1Id: invitorId,
|
||||
user2Id: me.id,
|
||||
user1Ready: false,
|
||||
user2Ready: false,
|
||||
isStarted: false,
|
||||
isEnded: false,
|
||||
logs: [],
|
||||
map: Reversi.maps.eighteight.data,
|
||||
bw: 'random',
|
||||
isLlotheo: false,
|
||||
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
this.cacheGame(game);
|
||||
|
||||
const packed = await this.reversiGameEntityService.packDetail(game, { id: invitorId });
|
||||
this.globalEventService.publishReversiStream(invitorId, 'matched', { game: packed });
|
||||
const game = await this.matched(invitorId, me.id, {
|
||||
noIrregularRules: false,
|
||||
});
|
||||
|
||||
return game;
|
||||
}
|
||||
@@ -171,39 +178,35 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
|
||||
const matchings = await this.redisClient.zrange(
|
||||
'reversi:matchAny',
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
0,
|
||||
2, // 自分自身のIDが入っている場合もあるので2つ取得
|
||||
'REV');
|
||||
|
||||
const userIds = matchings.filter(id => id !== me.id);
|
||||
const items = matchings.filter(id => !id.startsWith(me.id));
|
||||
|
||||
if (userIds.length > 0) {
|
||||
// pick random
|
||||
const matchedUserId = userIds[Math.floor(Math.random() * userIds.length)];
|
||||
if (items.length > 0) {
|
||||
const [matchedUserId, option] = items[0].split(':');
|
||||
|
||||
await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
|
||||
await this.redisClient.zrem('reversi:matchAny',
|
||||
me.id,
|
||||
matchedUserId,
|
||||
me.id + ':noIrregularRules',
|
||||
matchedUserId + ':noIrregularRules');
|
||||
|
||||
const game = await this.reversiGamesRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
user1Id: matchedUserId,
|
||||
user2Id: me.id,
|
||||
user1Ready: false,
|
||||
user2Ready: false,
|
||||
isStarted: false,
|
||||
isEnded: false,
|
||||
logs: [],
|
||||
map: Reversi.maps.eighteight.data,
|
||||
bw: 'random',
|
||||
isLlotheo: false,
|
||||
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
this.cacheGame(game);
|
||||
|
||||
const packed = await this.reversiGameEntityService.packDetail(game, { id: matchedUserId });
|
||||
this.globalEventService.publishReversiStream(matchedUserId, 'matched', { game: packed });
|
||||
const game = await this.matched(matchedUserId, me.id, {
|
||||
noIrregularRules: options.noIrregularRules || option === 'noIrregularRules',
|
||||
});
|
||||
|
||||
return game;
|
||||
} else {
|
||||
await this.redisClient.zadd('reversi:matchAny', Date.now(), me.id);
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
if (options.noIrregularRules) {
|
||||
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id + ':noIrregularRules');
|
||||
} else {
|
||||
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id);
|
||||
}
|
||||
redisPipeline.expire('reversi:matchAny', 15, 'NX');
|
||||
await redisPipeline.exec();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -215,7 +218,15 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
|
||||
@bindThis
|
||||
public async matchAnyUserCancel(user: MiUser) {
|
||||
await this.redisClient.zrem('reversi:matchAny', user.id);
|
||||
await this.redisClient.zrem('reversi:matchAny', user.id, user.id + ':noIrregularRules');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async cleanOutdatedGames() {
|
||||
await this.reversiGamesRepository.delete({
|
||||
id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 10)),
|
||||
isStarted: false,
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -268,6 +279,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
|
||||
const game = await this.reversiGamesRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
user1Id: parentId,
|
||||
user2Id: childId,
|
||||
user1Ready: false,
|
||||
user2Ready: false,
|
||||
isStarted: false,
|
||||
isEnded: false,
|
||||
logs: [],
|
||||
map: Reversi.maps.eighteight.data,
|
||||
bw: 'random',
|
||||
isLlotheo: false,
|
||||
noIrregularRules: options.noIrregularRules,
|
||||
}).then(x => this.reversiGamesRepository.findOneOrFail({
|
||||
where: { id: x.identifiers[0].id },
|
||||
relations: ['user1', 'user2'],
|
||||
}));
|
||||
this.cacheGame(game);
|
||||
|
||||
const packed = await this.reversiGameEntityService.packDetail(game);
|
||||
this.globalEventService.publishReversiStream(parentId, 'matched', { game: packed });
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async startGame(game: MiReversiGame) {
|
||||
let bw: number;
|
||||
@@ -277,7 +315,13 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
bw = parseInt(game.bw, 10);
|
||||
}
|
||||
|
||||
const crc32 = CRC32.str(JSON.stringify(game.logs)).toString();
|
||||
const engine = new Reversi.Game(game.map, {
|
||||
isLlotheo: game.isLlotheo,
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
});
|
||||
|
||||
const crc32 = engine.calcCrc32().toString();
|
||||
|
||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
||||
.set({
|
||||
@@ -292,15 +336,12 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
.returning('*')
|
||||
.execute()
|
||||
.then((response) => response.raw[0]);
|
||||
// キャッシュ効率化のためにユーザー情報は再利用
|
||||
updatedGame.user1 = game.user1;
|
||||
updatedGame.user2 = game.user2;
|
||||
this.cacheGame(updatedGame);
|
||||
|
||||
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
||||
const engine = new Reversi.Game(updatedGame.map, {
|
||||
isLlotheo: updatedGame.isLlotheo,
|
||||
canPutEverywhere: updatedGame.canPutEverywhere,
|
||||
loopedBoard: updatedGame.loopedBoard,
|
||||
});
|
||||
|
||||
if (engine.isEnded) {
|
||||
let winnerId;
|
||||
if (engine.winner === true) {
|
||||
@@ -339,6 +380,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
.returning('*')
|
||||
.execute()
|
||||
.then((response) => response.raw[0]);
|
||||
// キャッシュ効率化のためにユーザー情報は再利用
|
||||
updatedGame.user1 = game.user1;
|
||||
updatedGame.user2 = game.user2;
|
||||
this.cacheGame(updatedGame);
|
||||
|
||||
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
||||
@@ -351,7 +395,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
public async getInvitations(user: MiUser): Promise<MiUser['id'][]> {
|
||||
const invitations = await this.redisClient.zrange(
|
||||
`reversi:matchSpecific:${user.id}`,
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
Date.now() - INVITATION_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
return invitations;
|
||||
@@ -422,7 +466,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
|
||||
const serializeLogs = Reversi.Serializer.serializeLogs(logs);
|
||||
|
||||
const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString();
|
||||
const crc32 = engine.calcCrc32().toString();
|
||||
|
||||
const updatedGame = {
|
||||
...game,
|
||||
@@ -508,14 +552,36 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> {
|
||||
const cached = await this.redisClient.get(`reversi:game:cache:${id}`);
|
||||
if (cached != null) {
|
||||
// TODO: この辺りのデシリアライズ処理をどこか別のサービスに切り出したい
|
||||
const parsed = JSON.parse(cached) as Serialized<MiReversiGame>;
|
||||
return {
|
||||
...parsed,
|
||||
startedAt: parsed.startedAt != null ? new Date(parsed.startedAt) : null,
|
||||
endedAt: parsed.endedAt != null ? new Date(parsed.endedAt) : null,
|
||||
user1: parsed.user1 != null ? {
|
||||
...parsed.user1,
|
||||
avatar: null,
|
||||
banner: null,
|
||||
updatedAt: parsed.user1.updatedAt != null ? new Date(parsed.user1.updatedAt) : null,
|
||||
lastActiveDate: parsed.user1.lastActiveDate != null ? new Date(parsed.user1.lastActiveDate) : null,
|
||||
lastFetchedAt: parsed.user1.lastFetchedAt != null ? new Date(parsed.user1.lastFetchedAt) : null,
|
||||
movedAt: parsed.user1.movedAt != null ? new Date(parsed.user1.movedAt) : null,
|
||||
} : null,
|
||||
user2: parsed.user2 != null ? {
|
||||
...parsed.user2,
|
||||
avatar: null,
|
||||
banner: null,
|
||||
updatedAt: parsed.user2.updatedAt != null ? new Date(parsed.user2.updatedAt) : null,
|
||||
lastActiveDate: parsed.user2.lastActiveDate != null ? new Date(parsed.user2.lastActiveDate) : null,
|
||||
lastFetchedAt: parsed.user2.lastFetchedAt != null ? new Date(parsed.user2.lastFetchedAt) : null,
|
||||
movedAt: parsed.user2.movedAt != null ? new Date(parsed.user2.movedAt) : null,
|
||||
} : null,
|
||||
};
|
||||
} else {
|
||||
const game = await this.reversiGamesRepository.findOneBy({ id });
|
||||
const game = await this.reversiGamesRepository.findOne({
|
||||
where: { id },
|
||||
relations: ['user1', 'user2'],
|
||||
});
|
||||
if (game == null) return null;
|
||||
|
||||
this.cacheGame(game);
|
||||
@@ -530,7 +596,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||
if (game == null) throw new Error('game not found');
|
||||
|
||||
if (crc32.toString() !== game.crc32) {
|
||||
return await this.reversiGameEntityService.packDetail(game);
|
||||
return game;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@@ -177,9 +177,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||
case 'userRoleAssigned': {
|
||||
const cached = this.roleAssignmentByUserIdCache.get(body.userId);
|
||||
if (cached) {
|
||||
cached.push({
|
||||
cached.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
expiresAt: body.expiresAt ? new Date(body.expiresAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
role: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@@ -109,13 +109,13 @@ export class UserBlockingService implements OnModuleInit {
|
||||
|
||||
if (this.userEntityService.isLocalUser(followee)) {
|
||||
this.userEntityService.pack(followee, followee, {
|
||||
detail: true,
|
||||
schema: 'MeDetailed',
|
||||
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(follower) && !silent) {
|
||||
this.userEntityService.pack(followee, follower, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}).then(async packed => {
|
||||
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
|
||||
|
||||
|
@@ -293,9 +293,9 @@ export class UserFollowingService implements OnModuleInit {
|
||||
if (this.userEntityService.isLocalUser(follower) && !silent) {
|
||||
// Publish follow event
|
||||
this.userEntityService.pack(followee.id, follower, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}).then(async packed => {
|
||||
this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>);
|
||||
this.globalEventService.publishMainStream(follower.id, 'follow', packed);
|
||||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
|
||||
for (const webhook of webhooks) {
|
||||
@@ -360,7 +360,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
if (!silent && this.userEntityService.isLocalUser(follower)) {
|
||||
// Publish unfollow event
|
||||
this.userEntityService.pack(followee.id, follower, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}).then(async packed => {
|
||||
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
|
||||
|
||||
@@ -500,7 +500,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed));
|
||||
|
||||
this.userEntityService.pack(followee.id, followee, {
|
||||
detail: true,
|
||||
schema: 'MeDetailed',
|
||||
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
|
||||
|
||||
// 通知を作成
|
||||
@@ -548,7 +548,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
});
|
||||
|
||||
this.userEntityService.pack(followee.id, followee, {
|
||||
detail: true,
|
||||
schema: 'MeDetailed',
|
||||
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
}
|
||||
|
||||
this.userEntityService.pack(followee.id, followee, {
|
||||
detail: true,
|
||||
schema: 'MeDetailed',
|
||||
}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
|
||||
}
|
||||
|
||||
@@ -696,7 +696,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||
@bindThis
|
||||
private async publishUnfollow(followee: Both, follower: Local): Promise<void> {
|
||||
const packedFollowee = await this.userEntityService.pack(followee.id, follower, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
});
|
||||
|
||||
this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee);
|
||||
|
@@ -49,9 +49,10 @@ export class WebhookService implements OnApplicationShutdown {
|
||||
switch (type) {
|
||||
case 'webhookCreated':
|
||||
if (body.active) {
|
||||
this.webhooks.push({
|
||||
this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -59,14 +60,16 @@ export class WebhookService implements OnApplicationShutdown {
|
||||
if (body.active) {
|
||||
const i = this.webhooks.findIndex(a => a.id === body.id);
|
||||
if (i > -1) {
|
||||
this.webhooks[i] = {
|
||||
this.webhooks[i] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
};
|
||||
} else {
|
||||
this.webhooks.push({
|
||||
this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@@ -94,6 +94,29 @@ type ToJsonSchema<S> = {
|
||||
};
|
||||
|
||||
export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> {
|
||||
const unflatten = (str: string, parent: Record<string, any>) => {
|
||||
const keys = str.split('.');
|
||||
const key = keys.shift();
|
||||
const nextKey = keys[0];
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
if (parent.properties[key] == null) {
|
||||
parent.properties[key] = nextKey ? {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} : {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'number',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (nextKey) unflatten(keys.join('.'), parent.properties[key] as Record<string, any>);
|
||||
};
|
||||
|
||||
const jsonSchema = {
|
||||
type: 'object',
|
||||
properties: {} as Record<string, unknown>,
|
||||
@@ -101,10 +124,7 @@ export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatt
|
||||
};
|
||||
|
||||
for (const k in schema) {
|
||||
jsonSchema.properties[k] = {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
};
|
||||
unflatten(k, jsonSchema);
|
||||
}
|
||||
|
||||
return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>;
|
||||
|
@@ -38,13 +38,13 @@ export class AbuseUserReportEntityService {
|
||||
targetUserId: report.targetUserId,
|
||||
assigneeId: report.assigneeId,
|
||||
reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}),
|
||||
targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}),
|
||||
assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}) : null,
|
||||
forwarded: report.forwarded,
|
||||
});
|
||||
|
@@ -37,7 +37,7 @@ export class BlockingEntityService {
|
||||
createdAt: this.idService.parse(blocking.id).date.toISOString(),
|
||||
blockeeId: blocking.blockeeId,
|
||||
blockee: this.userEntityService.pack(blocking.blockeeId, me, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ export class FlashEntityService {
|
||||
createdAt: this.idService.parse(flash.id).date.toISOString(),
|
||||
updatedAt: flash.updatedAt.toISOString(),
|
||||
userId: flash.userId,
|
||||
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意
|
||||
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
||||
title: flash.title,
|
||||
summary: flash.summary,
|
||||
script: flash.script,
|
||||
|
@@ -89,10 +89,10 @@ export class FollowingEntityService {
|
||||
followeeId: following.followeeId,
|
||||
followerId: following.followerId,
|
||||
followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}) : undefined,
|
||||
follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}) : undefined,
|
||||
});
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ export class ModerationLogEntityService {
|
||||
info: log.info,
|
||||
userId: log.userId,
|
||||
user: this.userEntityService.pack(log.user ?? log.userId, null, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ export class MutingEntityService {
|
||||
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||
muteeId: muting.muteeId,
|
||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@@ -164,7 +164,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
|
||||
return {
|
||||
multiple: poll.multiple,
|
||||
expiresAt: poll.expiresAt,
|
||||
expiresAt: poll.expiresAt?.toISOString() ?? null,
|
||||
choices,
|
||||
};
|
||||
}
|
||||
@@ -324,9 +324,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
id: note.id,
|
||||
createdAt: this.idService.parse(note.id).date.toISOString(),
|
||||
userId: note.userId,
|
||||
user: this.userEntityService.pack(note.user ?? note.userId, me, {
|
||||
detail: false,
|
||||
}),
|
||||
user: this.userEntityService.pack(note.user ?? note.userId, me),
|
||||
text: text,
|
||||
cw: note.cw,
|
||||
visibility: note.visibility,
|
||||
|
@@ -62,7 +62,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
},
|
||||
hint?: {
|
||||
packedNotes: Map<MiNote['id'], Packed<'Note'>>;
|
||||
packedUsers: Map<MiUser['id'], Packed<'User'>>;
|
||||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
||||
},
|
||||
): Promise<Packed<'Notification'>> {
|
||||
const notification = src;
|
||||
@@ -76,9 +76,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
const userIfNeed = 'notifierId' in notification ? (
|
||||
hint?.packedUsers != null
|
||||
? hint.packedUsers.get(notification.notifierId)
|
||||
: this.userEntityService.pack(notification.notifierId, { id: meId }, {
|
||||
detail: false,
|
||||
})
|
||||
: this.userEntityService.pack(notification.notifierId, { id: meId })
|
||||
) : undefined;
|
||||
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
|
||||
|
||||
@@ -131,9 +129,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
const users = userIds.length > 0 ? await this.usersRepository.find({
|
||||
where: { id: In(userIds) },
|
||||
}) : [];
|
||||
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
|
||||
detail: false,
|
||||
});
|
||||
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId });
|
||||
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
|
||||
|
||||
// 既に解決されたフォローリクエストの通知を除外
|
||||
@@ -161,7 +157,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
},
|
||||
hint?: {
|
||||
packedNotes: Map<MiNote['id'], Packed<'Note'>>;
|
||||
packedUsers: Map<MiUser['id'], Packed<'User'>>;
|
||||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
||||
},
|
||||
): Promise<Packed<'Notification'>> {
|
||||
const notification = src;
|
||||
@@ -175,18 +171,14 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
const userIfNeed = 'notifierId' in notification ? (
|
||||
hint?.packedUsers != null
|
||||
? hint.packedUsers.get(notification.notifierId)
|
||||
: this.userEntityService.pack(notification.notifierId, { id: meId }, {
|
||||
detail: false,
|
||||
})
|
||||
: this.userEntityService.pack(notification.notifierId, { id: meId })
|
||||
) : undefined;
|
||||
|
||||
if (notification.type === 'reaction:grouped') {
|
||||
const reactions = await Promise.all(notification.reactions.map(async reaction => {
|
||||
const user = hint?.packedUsers != null
|
||||
? hint.packedUsers.get(reaction.userId)!
|
||||
: await this.userEntityService.pack(reaction.userId, { id: meId }, {
|
||||
detail: false,
|
||||
});
|
||||
: await this.userEntityService.pack(reaction.userId, { id: meId });
|
||||
return {
|
||||
user,
|
||||
reaction: reaction.reaction,
|
||||
@@ -206,9 +198,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
return packedUser;
|
||||
}
|
||||
|
||||
return this.userEntityService.pack(userId, { id: meId }, {
|
||||
detail: false,
|
||||
});
|
||||
return this.userEntityService.pack(userId, { id: meId });
|
||||
}));
|
||||
return await awaitAll({
|
||||
id: notification.id,
|
||||
@@ -275,9 +265,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
const users = userIds.length > 0 ? await this.usersRepository.find({
|
||||
where: { id: In(userIds) },
|
||||
}) : [];
|
||||
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
|
||||
detail: false,
|
||||
});
|
||||
const packedUsersArray = await this.userEntityService.packMany(users, { id: meId });
|
||||
const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
|
||||
|
||||
// 既に解決されたフォローリクエストの通知を除外
|
||||
|
@@ -90,7 +90,7 @@ export class PageEntityService {
|
||||
createdAt: this.idService.parse(page.id).date.toISOString(),
|
||||
updatedAt: page.updatedAt.toISOString(),
|
||||
userId: page.userId,
|
||||
user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意
|
||||
user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
||||
content: page.content,
|
||||
variables: page.variables,
|
||||
title: page.title,
|
||||
|
@@ -38,7 +38,7 @@ export class RenoteMutingEntityService {
|
||||
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
||||
muteeId: muting.muteeId,
|
||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
||||
detail: true,
|
||||
schema: 'UserDetailedNotMe',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ import type { ReversiGamesRepository } 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 type { MiUser } from '@/models/User.js';
|
||||
import type { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
@@ -29,10 +28,14 @@ export class ReversiGameEntityService {
|
||||
@bindThis
|
||||
public async packDetail(
|
||||
src: MiReversiGame['id'] | MiReversiGame,
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
): Promise<Packed<'ReversiGameDetailed'>> {
|
||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const users = await Promise.all([
|
||||
this.userEntityService.pack(game.user1 ?? game.user1Id),
|
||||
this.userEntityService.pack(game.user2 ?? game.user2Id),
|
||||
]);
|
||||
|
||||
return await awaitAll({
|
||||
id: game.id,
|
||||
createdAt: this.idService.parse(game.id).date.toISOString(),
|
||||
@@ -46,10 +49,10 @@ export class ReversiGameEntityService {
|
||||
user2Ready: game.user2Ready,
|
||||
user1Id: game.user1Id,
|
||||
user2Id: game.user2Id,
|
||||
user1: this.userEntityService.pack(game.user1Id, me),
|
||||
user2: this.userEntityService.pack(game.user2Id, me),
|
||||
user1: users[0],
|
||||
user2: users[1],
|
||||
winnerId: game.winnerId,
|
||||
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
|
||||
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
||||
surrenderedUserId: game.surrenderedUserId,
|
||||
timeoutUserId: game.timeoutUserId,
|
||||
black: game.black,
|
||||
@@ -58,6 +61,7 @@ export class ReversiGameEntityService {
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
timeLimitForEachTurn: game.timeLimitForEachTurn,
|
||||
noIrregularRules: game.noIrregularRules,
|
||||
logs: game.logs,
|
||||
map: game.map,
|
||||
});
|
||||
@@ -66,18 +70,21 @@ export class ReversiGameEntityService {
|
||||
@bindThis
|
||||
public packDetailMany(
|
||||
xs: MiReversiGame[],
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
) {
|
||||
return Promise.all(xs.map(x => this.packDetail(x, me)));
|
||||
return Promise.all(xs.map(x => this.packDetail(x)));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packLite(
|
||||
src: MiReversiGame['id'] | MiReversiGame,
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
): Promise<Packed<'ReversiGameLite'>> {
|
||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const users = await Promise.all([
|
||||
this.userEntityService.pack(game.user1 ?? game.user1Id),
|
||||
this.userEntityService.pack(game.user2 ?? game.user2Id),
|
||||
]);
|
||||
|
||||
return await awaitAll({
|
||||
id: game.id,
|
||||
createdAt: this.idService.parse(game.id).date.toISOString(),
|
||||
@@ -85,16 +92,12 @@ export class ReversiGameEntityService {
|
||||
endedAt: game.endedAt && game.endedAt.toISOString(),
|
||||
isStarted: game.isStarted,
|
||||
isEnded: game.isEnded,
|
||||
form1: game.form1,
|
||||
form2: game.form2,
|
||||
user1Ready: game.user1Ready,
|
||||
user2Ready: game.user2Ready,
|
||||
user1Id: game.user1Id,
|
||||
user2Id: game.user2Id,
|
||||
user1: this.userEntityService.pack(game.user1Id, me),
|
||||
user2: this.userEntityService.pack(game.user2Id, me),
|
||||
user1: users[0],
|
||||
user2: users[1],
|
||||
winnerId: game.winnerId,
|
||||
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
|
||||
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
||||
surrenderedUserId: game.surrenderedUserId,
|
||||
timeoutUserId: game.timeoutUserId,
|
||||
black: game.black,
|
||||
@@ -103,15 +106,15 @@ export class ReversiGameEntityService {
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
timeLimitForEachTurn: game.timeLimitForEachTurn,
|
||||
noIrregularRules: game.noIrregularRules,
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packLiteMany(
|
||||
xs: MiReversiGame[],
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
) {
|
||||
return Promise.all(xs.map(x => this.packLite(x, me)));
|
||||
return Promise.all(xs.map(x => this.packLite(x)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -30,14 +30,6 @@ import type { NoteEntityService } from './NoteEntityService.js';
|
||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||
import type { PageEntityService } from './PageEntityService.js';
|
||||
|
||||
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
|
||||
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
|
||||
Detailed extends true ?
|
||||
ExpectsMe extends true ? Packed<'MeDetailed'> :
|
||||
ExpectsMe extends false ? Packed<'UserDetailedNotMe'> :
|
||||
Packed<'UserDetailed'> :
|
||||
Packed<'UserLite'>;
|
||||
|
||||
const Ajv = _Ajv.default;
|
||||
const ajv = new Ajv();
|
||||
|
||||
@@ -303,33 +295,34 @@ export class UserEntityService implements OnModuleInit {
|
||||
return `${this.config.url}/users/${userId}`;
|
||||
}
|
||||
|
||||
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
|
||||
public async pack<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
|
||||
src: MiUser['id'] | MiUser,
|
||||
me?: { id: MiUser['id']; } | null | undefined,
|
||||
options?: {
|
||||
detail?: D,
|
||||
schema?: S,
|
||||
includeSecrets?: boolean,
|
||||
userProfile?: MiUserProfile,
|
||||
},
|
||||
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
|
||||
): Promise<Packed<S>> {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
schema: 'UserLite',
|
||||
includeSecrets: false,
|
||||
}, options);
|
||||
|
||||
const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const isDetailed = opts.schema !== 'UserLite';
|
||||
const meId = me ? me.id : null;
|
||||
const isMe = meId === user.id;
|
||||
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
|
||||
|
||||
const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
|
||||
const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin')
|
||||
const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null;
|
||||
const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin')
|
||||
.where('pin.userId = :userId', { userId: user.id })
|
||||
.innerJoinAndSelect('pin.note', 'note')
|
||||
.orderBy('pin.id', 'DESC')
|
||||
.getMany() : [];
|
||||
const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
|
||||
const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
|
||||
|
||||
const followingCount = profile == null ? null :
|
||||
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
|
||||
@@ -341,15 +334,15 @@ export class UserEntityService implements OnModuleInit {
|
||||
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
|
||||
null;
|
||||
|
||||
const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
|
||||
const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null;
|
||||
const unreadAnnouncements = isMe && opts.detail ?
|
||||
const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null;
|
||||
const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null;
|
||||
const unreadAnnouncements = isMe && isDetailed ?
|
||||
(await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({
|
||||
createdAt: this.idService.parse(announcement.id).date.toISOString(),
|
||||
...announcement,
|
||||
})) : null;
|
||||
|
||||
const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null;
|
||||
const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null;
|
||||
|
||||
const packed = {
|
||||
id: user.id,
|
||||
@@ -385,7 +378,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
displayOrder: r.displayOrder,
|
||||
}))) : undefined,
|
||||
|
||||
...(opts.detail ? {
|
||||
...(isDetailed ? {
|
||||
url: profile!.url,
|
||||
uri: user.uri,
|
||||
movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null,
|
||||
@@ -416,7 +409,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
}),
|
||||
pinnedPageId: profile!.pinnedPageId,
|
||||
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
|
||||
publicReactions: profile!.publicReactions,
|
||||
publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
|
||||
followersVisibility: profile!.followersVisibility,
|
||||
followingVisibility: profile!.followingVisibility,
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
@@ -443,7 +436,7 @@ export class UserEntityService implements OnModuleInit {
|
||||
moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
|
||||
} : {}),
|
||||
|
||||
...(opts.detail && isMe ? {
|
||||
...(isDetailed && isMe ? {
|
||||
avatarId: user.avatarId,
|
||||
bannerId: user.bannerId,
|
||||
isModerator: isModerator,
|
||||
@@ -515,19 +508,19 @@ export class UserEntityService implements OnModuleInit {
|
||||
notify: relation.following?.notify ?? 'none',
|
||||
withReplies: relation.following?.withReplies ?? false,
|
||||
} : {}),
|
||||
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
|
||||
} as Promiseable<Packed<S>>;
|
||||
|
||||
return await awaitAll(packed);
|
||||
}
|
||||
|
||||
public packMany<D extends boolean = false>(
|
||||
public packMany<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
|
||||
users: (MiUser['id'] | MiUser)[],
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
options?: {
|
||||
detail?: D,
|
||||
schema?: S,
|
||||
includeSecrets?: boolean,
|
||||
},
|
||||
): Promise<IsUserDetailed<D>[]> {
|
||||
): Promise<Packed<S>[]> {
|
||||
return Promise.all(users.map(u => this.pack(u, me, options)));
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
// structredCloneが遅いため
|
||||
// SEE: http://var.blog.jp/archives/86038606.html
|
||||
|
||||
type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[];
|
||||
type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[];
|
||||
|
||||
export function deepClone<T extends Cloneable>(x: T): T {
|
||||
if (typeof x === 'object') {
|
||||
@@ -14,7 +14,7 @@ export function deepClone<T extends Cloneable>(x: T): T {
|
||||
if (Array.isArray(x)) return x.map(deepClone) as T;
|
||||
const obj = {} as Record<string, Cloneable>;
|
||||
for (const [k, v] of Object.entries(x)) {
|
||||
obj[k] = deepClone(v);
|
||||
obj[k] = v === undefined ? undefined : deepClone(v);
|
||||
}
|
||||
return obj as T;
|
||||
} else {
|
||||
|
@@ -25,7 +25,7 @@ import { packedBlockingSchema } from '@/models/json-schema/blocking.js';
|
||||
import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js';
|
||||
import { packedHashtagSchema } from '@/models/json-schema/hashtag.js';
|
||||
import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js';
|
||||
import { packedPageSchema } from '@/models/json-schema/page.js';
|
||||
import { packedPageSchema, packedPageBlockSchema } from '@/models/json-schema/page.js';
|
||||
import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js';
|
||||
import { packedChannelSchema } from '@/models/json-schema/channel.js';
|
||||
import { packedAntennaSchema } from '@/models/json-schema/antenna.js';
|
||||
@@ -37,7 +37,7 @@ import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/jso
|
||||
import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
||||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
|
||||
import { packedRoleLiteSchema, packedRoleSchema, packedRolePoliciesSchema } from '@/models/json-schema/role.js';
|
||||
import { packedAdSchema } from '@/models/json-schema/ad.js';
|
||||
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
|
||||
|
||||
@@ -67,6 +67,7 @@ export const refs = {
|
||||
Hashtag: packedHashtagSchema,
|
||||
InviteCode: packedInviteCodeSchema,
|
||||
Page: packedPageSchema,
|
||||
PageBlock: packedPageBlockSchema,
|
||||
Channel: packedChannelSchema,
|
||||
QueueCount: packedQueueCountSchema,
|
||||
Antenna: packedAntennaSchema,
|
||||
@@ -79,12 +80,16 @@ export const refs = {
|
||||
Signin: packedSigninSchema,
|
||||
RoleLite: packedRoleLiteSchema,
|
||||
Role: packedRoleSchema,
|
||||
RolePolicies: packedRolePoliciesSchema,
|
||||
ReversiGameLite: packedReversiGameLiteSchema,
|
||||
ReversiGameDetailed: packedReversiGameDetailedSchema,
|
||||
};
|
||||
|
||||
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
|
||||
|
||||
export type KeyOf<x extends keyof typeof refs> = PropertiesToUnion<typeof refs[x]>;
|
||||
type PropertiesToUnion<p extends Schema> = p['properties'] extends NonNullable<Obj> ? keyof p['properties'] : never;
|
||||
|
||||
type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
|
||||
type StringDefToType<T extends TypeStringef> =
|
||||
T extends 'null' ? null :
|
||||
@@ -114,6 +119,7 @@ export interface Schema extends OfSchema {
|
||||
readonly example?: any;
|
||||
readonly format?: string;
|
||||
readonly ref?: keyof typeof refs;
|
||||
readonly selfRef?: boolean;
|
||||
readonly enum?: ReadonlyArray<string | null>;
|
||||
readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
|
||||
readonly maxLength?: number;
|
||||
|
@@ -38,7 +38,7 @@ export class MiAnnouncement {
|
||||
length: 256, nullable: false,
|
||||
default: 'info',
|
||||
})
|
||||
public icon: string;
|
||||
public icon: 'info' | 'warning' | 'error' | 'success';
|
||||
|
||||
// normal ... お知らせページ掲載
|
||||
// banner ... お知らせページ掲載 + バナー表示
|
||||
@@ -47,7 +47,7 @@ export class MiAnnouncement {
|
||||
length: 256, nullable: false,
|
||||
default: 'normal',
|
||||
})
|
||||
public display: string;
|
||||
public display: 'normal' | 'banner' | 'dialog';
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
|
@@ -106,6 +106,11 @@ export class MiReversiGame {
|
||||
})
|
||||
public bw: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public noIrregularRules: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
@@ -37,10 +37,12 @@ export const packedAnnouncementSchema = {
|
||||
icon: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['info', 'warning', 'error', 'success'],
|
||||
},
|
||||
display: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['dialog', 'normal', 'banner'],
|
||||
},
|
||||
needConfirmationToRead: {
|
||||
type: 'boolean',
|
||||
|
@@ -25,7 +25,7 @@ export const packedBlockingSchema = {
|
||||
blockee: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -30,12 +30,12 @@ export const packedFollowingSchema = {
|
||||
followee: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
follower: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -30,7 +30,7 @@ export const packedMutingSchema = {
|
||||
mutee: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -69,6 +69,7 @@ export const packedNoteSchema = {
|
||||
visibility: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['public', 'home', 'followers', 'specified'],
|
||||
},
|
||||
mentions: {
|
||||
type: 'array',
|
||||
@@ -117,6 +118,48 @@ export const packedNoteSchema = {
|
||||
poll: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
properties: {
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
multiple: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
choices: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
isVoted: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
votes: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
emojis: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
additionalProperties: {
|
||||
anyOf: [{
|
||||
type: 'string',
|
||||
}],
|
||||
},
|
||||
},
|
||||
channelId: {
|
||||
type: 'string',
|
||||
@@ -162,9 +205,23 @@ export const packedNoteSchema = {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
reactionEmojis: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
additionalProperties: {
|
||||
anyOf: [{
|
||||
type: 'string',
|
||||
}],
|
||||
},
|
||||
},
|
||||
reactions: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
additionalProperties: {
|
||||
anyOf: [{
|
||||
type: 'number',
|
||||
}],
|
||||
},
|
||||
},
|
||||
renoteCount: {
|
||||
type: 'number',
|
||||
@@ -196,7 +253,7 @@ export const packedNoteSchema = {
|
||||
},
|
||||
|
||||
myReaction: {
|
||||
type: 'object',
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
},
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { notificationTypes } from '@/types.js';
|
||||
|
||||
export const packedNotificationSchema = {
|
||||
const baseSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
@@ -23,68 +23,368 @@ export const packedNotificationSchema = {
|
||||
optional: false, nullable: false,
|
||||
enum: [...notificationTypes, 'reaction:grouped', 'renote:grouped'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
achievement: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
header: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
optional: true, nullable: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
required: ['user', 'reaction'],
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedNotificationSchema = {
|
||||
type: 'object',
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['note'],
|
||||
},
|
||||
},
|
||||
users: {
|
||||
type: 'array',
|
||||
optional: true, nullable: true,
|
||||
items: {
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['mention'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['reply'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['renote'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['quote'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['reaction'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['pollEnded'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['follow'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['receiveFollowRequest'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['followRequestAccepted'],
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['roleAssigned'],
|
||||
},
|
||||
role: {
|
||||
type: 'object',
|
||||
ref: 'Role',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['achievementEarned'],
|
||||
},
|
||||
achievement: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['app'],
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
header: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['reaction:grouped'],
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
reaction: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
required: ['user', 'reaction'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['renote:grouped'],
|
||||
},
|
||||
note: {
|
||||
type: 'object',
|
||||
ref: 'Note',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
users: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...baseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['test'],
|
||||
},
|
||||
},
|
||||
}],
|
||||
} as const;
|
||||
|
@@ -3,6 +3,108 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
const blockBaseSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const textBlockSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...blockBaseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['text'],
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const sectionBlockSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...blockBaseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['section'],
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
children: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'PageBlock',
|
||||
selfRef: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const imageBlockSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...blockBaseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['image'],
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const noteBlockSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...blockBaseSchema.properties,
|
||||
type: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['note'],
|
||||
},
|
||||
detailed: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
note: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedPageBlockSchema = {
|
||||
type: 'object',
|
||||
oneOf: [
|
||||
textBlockSchema,
|
||||
sectionBlockSchema,
|
||||
imageBlockSchema,
|
||||
noteBlockSchema,
|
||||
],
|
||||
} as const;
|
||||
|
||||
export const packedPageSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -38,6 +140,7 @@ export const packedPageSchema = {
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'PageBlock',
|
||||
},
|
||||
},
|
||||
variables: {
|
||||
|
@@ -25,7 +25,7 @@ export const packedRenoteMutingSchema = {
|
||||
mutee: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -34,22 +34,6 @@ export const packedReversiGameLiteSchema = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
form1: {
|
||||
type: 'any',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
form2: {
|
||||
type: 'any',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
user1Ready: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user2Ready: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user1Id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
@@ -98,6 +82,10 @@ export const packedReversiGameLiteSchema = {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noIrregularRules: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isLlotheo: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
@@ -149,11 +137,11 @@ export const packedReversiGameDetailedSchema = {
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
form1: {
|
||||
type: 'any',
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
form2: {
|
||||
type: 'any',
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
user1Ready: {
|
||||
@@ -212,6 +200,10 @@ export const packedReversiGameDetailedSchema = {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noIrregularRules: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isLlotheo: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -1,26 +1,103 @@
|
||||
const rolePolicyValue = {
|
||||
export const packedRolePoliciesSchema = {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
value: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
],
|
||||
gtlAvailable: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
priority: {
|
||||
ltlAvailable: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canPublicNote: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canInvite: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
inviteLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
useDefault: {
|
||||
inviteLimitCycle: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
inviteExpirationTime: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canManageCustomEmojis: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canManageAvatarDecorations: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canSearchNotes: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canUseTranslator: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
canHideAds: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
driveCapacityMb: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
pinLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
antennaLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
wordMuteLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
webhookLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
clipLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noteEachClipsLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userListLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userEachUserListsLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
rateLimitFactor: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
avatarDecorationLimit: {
|
||||
type: 'integer',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -121,31 +198,28 @@ export const packedRoleSchema = {
|
||||
policies: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
pinLimit: rolePolicyValue,
|
||||
canInvite: rolePolicyValue,
|
||||
clipLimit: rolePolicyValue,
|
||||
canHideAds: rolePolicyValue,
|
||||
inviteLimit: rolePolicyValue,
|
||||
antennaLimit: rolePolicyValue,
|
||||
gtlAvailable: rolePolicyValue,
|
||||
ltlAvailable: rolePolicyValue,
|
||||
webhookLimit: rolePolicyValue,
|
||||
canPublicNote: rolePolicyValue,
|
||||
userListLimit: rolePolicyValue,
|
||||
wordMuteLimit: rolePolicyValue,
|
||||
alwaysMarkNsfw: rolePolicyValue,
|
||||
canSearchNotes: rolePolicyValue,
|
||||
driveCapacityMb: rolePolicyValue,
|
||||
rateLimitFactor: rolePolicyValue,
|
||||
inviteLimitCycle: rolePolicyValue,
|
||||
noteEachClipsLimit: rolePolicyValue,
|
||||
inviteExpirationTime: rolePolicyValue,
|
||||
canManageCustomEmojis: rolePolicyValue,
|
||||
userEachUserListsLimit: rolePolicyValue,
|
||||
canManageAvatarDecorations: rolePolicyValue,
|
||||
canUseTranslator: rolePolicyValue,
|
||||
avatarDecorationLimit: rolePolicyValue,
|
||||
additionalProperties: {
|
||||
anyOf: [{
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'integer',
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
},
|
||||
priority: {
|
||||
type: 'integer',
|
||||
},
|
||||
useDefault: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
usersCount: {
|
||||
|
@@ -590,104 +590,7 @@ export const packedMeDetailedOnlySchema = {
|
||||
policies: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
gtlAvailable: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
ltlAvailable: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canPublicNote: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canInvite: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
inviteLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
inviteLimitCycle: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
inviteExpirationTime: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canManageCustomEmojis: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canManageAvatarDecorations: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canSearchNotes: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canUseTranslator: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canHideAds: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
driveCapacityMb: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
pinLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
antennaLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
wordMuteLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
webhookLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
clipLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
noteEachClipsLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
userListLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
userEachUserListsLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
rateLimitFactor: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
avatarDecorationLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
ref: 'RolePolicies',
|
||||
},
|
||||
//#region secrets
|
||||
email: {
|
||||
@@ -782,13 +685,5 @@ export const packedUserSchema = {
|
||||
type: 'object',
|
||||
ref: 'UserDetailed',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
ref: 'MeDetailed',
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
@@ -283,9 +283,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
||||
autorun: false,
|
||||
concurrency: this.config.relashionshipJobConcurrency ?? 16,
|
||||
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.relashionshipJobPerSec ?? 64,
|
||||
max: this.config.relationshipJobPerSec ?? 64,
|
||||
duration: 1000,
|
||||
},
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ReversiService } from '@/core/ReversiService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@@ -32,6 +33,7 @@ export class CleanProcessorService {
|
||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private reversiService: ReversiService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('clean');
|
||||
@@ -65,6 +67,8 @@ export class CleanProcessorService {
|
||||
});
|
||||
}
|
||||
|
||||
this.reversiService.cleanOutdatedGames();
|
||||
|
||||
this.logger.succ('Cleaned.');
|
||||
}
|
||||
}
|
||||
|
@@ -204,7 +204,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||
});
|
||||
|
||||
this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, {
|
||||
detail: true,
|
||||
schema: 'MeDetailed',
|
||||
includeSecrets: true,
|
||||
}));
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user