Compare commits
75 Commits
2023.9.0-b
...
fetch-outb
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a926e84b14 | ||
![]() |
814e28459e | ||
![]() |
d2831c612f | ||
![]() |
1eebf3c921 | ||
![]() |
a692acec1e | ||
![]() |
6cf466e5d1 | ||
![]() |
cf7c6558ae | ||
![]() |
efa66ae89a | ||
![]() |
6124772a5f | ||
![]() |
d28fe24d0b | ||
![]() |
ba28f90fd1 | ||
![]() |
0d505f8131 | ||
![]() |
d869481db9 | ||
![]() |
3456680e1d | ||
![]() |
42c7aad251 | ||
![]() |
f9fc743c05 | ||
![]() |
df636659e2 | ||
![]() |
25e030a707 | ||
![]() |
cd6428715e | ||
![]() |
98e40e666c | ||
![]() |
74faa01db8 | ||
![]() |
7bd0a5b7cb | ||
![]() |
c0838c473f | ||
![]() |
85078601c2 | ||
![]() |
b434beb5e2 | ||
![]() |
295665a177 | ||
![]() |
fd7d7318a7 | ||
![]() |
054ba3fea5 | ||
![]() |
8749716700 | ||
![]() |
55d392818c | ||
![]() |
19b10ca803 | ||
![]() |
cd7ab326cd | ||
![]() |
ff9a65e8fa | ||
![]() |
bc52d7a4fb | ||
![]() |
af7e129b1e | ||
![]() |
90b058e226 | ||
![]() |
c2383fac16 | ||
![]() |
b449f3e7a8 | ||
![]() |
32f5949935 | ||
![]() |
4c3935bf80 | ||
![]() |
dc1a91a5b2 | ||
![]() |
fd70a700f8 | ||
![]() |
d8dc10829c | ||
![]() |
b1efc298f7 | ||
![]() |
98462ccbaf | ||
![]() |
ca00a08e6e | ||
![]() |
54c6fb762a | ||
![]() |
22d966e92d | ||
![]() |
bf303238f0 | ||
![]() |
b0eae49eaa | ||
![]() |
888cd2eb9f | ||
![]() |
ff4b3d2d9e | ||
![]() |
60fd848182 | ||
![]() |
76def0032e | ||
![]() |
da0804eb17 | ||
![]() |
6b26ce3768 | ||
![]() |
5a0d7d41e6 | ||
![]() |
08e2b6ee32 | ||
![]() |
ca0c673b44 | ||
![]() |
70bb9a4d1f | ||
![]() |
b93046c071 | ||
![]() |
f34f0dfcb6 | ||
![]() |
26040c2bb0 | ||
![]() |
bdbad4605b | ||
![]() |
ec62fe02b1 | ||
![]() |
a74af07992 | ||
![]() |
aa78c29e8c | ||
![]() |
45d0b46e7a | ||
![]() |
6087d02047 | ||
![]() |
7bf318ae98 | ||
![]() |
71d74676f0 | ||
![]() |
5077df2973 | ||
![]() |
a1388a8444 | ||
![]() |
630e97bd06 | ||
![]() |
2e1de4fca9 |
@@ -114,6 +114,7 @@ redis:
|
||||
|
||||
# Available methods:
|
||||
# aid ... Short, Millisecond accuracy
|
||||
# aidx ... Millisecond accuracy
|
||||
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||
# ulid ... Millisecond accuracy
|
||||
# objectid ... This is left for backward compatibility
|
||||
@@ -121,7 +122,7 @@ redis:
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# ID SETTINGS AFTER THAT!
|
||||
|
||||
id: 'aid'
|
||||
id: 'aidx'
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Other configuration └─────────────────────────────────────
|
||||
|
@@ -125,6 +125,7 @@ redis:
|
||||
|
||||
# Available methods:
|
||||
# aid ... Short, Millisecond accuracy
|
||||
# aidx ... Millisecond accuracy
|
||||
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||
# ulid ... Millisecond accuracy
|
||||
# objectid ... This is left for backward compatibility
|
||||
@@ -132,7 +133,7 @@ redis:
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# ID SETTINGS AFTER THAT!
|
||||
|
||||
id: 'aid'
|
||||
id: 'aidx'
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Other configuration └─────────────────────────────────────
|
||||
|
@@ -114,6 +114,7 @@ redis:
|
||||
|
||||
# Available methods:
|
||||
# aid ... Short, Millisecond accuracy
|
||||
# aidx ... Millisecond accuracy
|
||||
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||
# ulid ... Millisecond accuracy
|
||||
# objectid ... This is left for backward compatibility
|
||||
@@ -121,7 +122,7 @@ redis:
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# ID SETTINGS AFTER THAT!
|
||||
|
||||
id: 'aid'
|
||||
id: 'aidx'
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Other configuration └─────────────────────────────────────
|
||||
|
2
.github/misskey/test.yml
vendored
2
.github/misskey/test.yml
vendored
@@ -12,4 +12,4 @@ db:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 56312
|
||||
id: aid
|
||||
id: aidx
|
||||
|
9
.github/reviewer-lottery.yml
vendored
9
.github/reviewer-lottery.yml
vendored
@@ -1,9 +0,0 @@
|
||||
groups:
|
||||
- name: devs
|
||||
reviewers: 2
|
||||
internal_reviewers: 1
|
||||
usernames:
|
||||
- syuilo
|
||||
- acid-chicken
|
||||
- EbiseLutica
|
||||
- tamaina
|
8
.github/workflows/docker-develop.yml
vendored
8
.github/workflows/docker-develop.yml
vendored
@@ -16,21 +16,21 @@ jobs:
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.10.0
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: misskey/misskey
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
|
8
.github/workflows/docker.yml
vendored
8
.github/workflows/docker.yml
vendored
@@ -15,12 +15,12 @@ jobs:
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.10.0
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: misskey/misskey
|
||||
tags: |
|
||||
@@ -31,12 +31,12 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
|
2
.github/workflows/ok-to-test.yml
vendored
2
.github/workflows/ok-to-test.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
# See app.yml for an example app manifest
|
||||
- name: Generate token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.DEPLOYBOT_APP_ID }}
|
||||
private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
|
||||
|
2
.github/workflows/test-backend.yml
vendored
2
.github/workflows/test-backend.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [20.5.1]
|
||||
|
||||
services:
|
||||
postgres:
|
||||
|
4
.github/workflows/test-frontend.yml
vendored
4
.github/workflows/test-frontend.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [20.5.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [20.5.1]
|
||||
browser: [chrome]
|
||||
|
||||
services:
|
||||
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [20.5.1]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [20.5.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.0.0
|
||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@@ -8,7 +8,7 @@
|
||||
-
|
||||
|
||||
### Server
|
||||
-
|
||||
- 最初照会したユーザーの最新ノートを受け取るように
|
||||
|
||||
-->
|
||||
|
||||
@@ -21,22 +21,35 @@
|
||||
- お知らせのバナー表示やダイアログ表示が可能に
|
||||
- お知らせのアイコンを設定可能に
|
||||
- チャンネルをセンシティブ指定できるようになりました
|
||||
- センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりました
|
||||
- センシティブチャンネルのノートはユーザープロフィールに表示されません
|
||||
- 二要素認証のバックアップコードが生成されるようになりました ref. https://github.com/MisskeyIO/misskey/pull/121
|
||||
- 二要素認証でパスキーをサポートするようになりました
|
||||
- 通知をテストできるようになりました
|
||||
- PWAのアイコンが設定できるようになりました
|
||||
- manifest.jsonをオーバーライド可能に
|
||||
|
||||
### Client
|
||||
- プロフィールにその人が作ったPlayの一覧出せるように
|
||||
- メニューのスイッチの動作を改善
|
||||
- 絵文字ピッカーの検索の表示件数を100件に増加
|
||||
- 投稿フォームのプレビューの表示状態を記憶するように
|
||||
- ノート詳細ページ読み込み時のパフォーマンスを改善
|
||||
- AiScriptからMisskeyサーバーAPIを呼び出す際の制限を撤廃
|
||||
- Playで直接投稿フォームを埋め込めるように(`Ui:C:postForm`)
|
||||
- Enhance: ユーザーメニューでスイッチでユーザーリストに追加・削除できるように
|
||||
- Enhance: 自分が押したリアクションのデザインを改善
|
||||
- Enhance: ノート検索にローカルのみ検索可能なオプションの追加
|
||||
- Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように
|
||||
- Enhance: Renote自体を通報できるように
|
||||
- Enhance: データセーバーモードの強化
|
||||
- Enhance: Renoteを管理者権限で削除可能に
|
||||
- `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました
|
||||
- Playの操作を行うAPI TokenをAPIコンソールから発行できるように
|
||||
- リアクションの表示サイズをより大きくできるように
|
||||
- ノート詳細ページ読み込み時のパフォーマンスを改善
|
||||
- タイムラインでリスト/アンテナ選択時のパフォーマンスを改善
|
||||
- 「Moderation note」、「Add moderation note」をローカライズできるように
|
||||
- 新しい実績を追加
|
||||
- Fix: サーバー情報画面(`/instance-info/{domain}`)でブロックができないのを修正
|
||||
- Fix: 未読のお知らせの「わかった」をクリック・タップしてもその場で「わかった」が消えない問題を修正
|
||||
- Fix: iOSで画面を回転させるとテキストサイズが変わる問題を修正
|
||||
@@ -46,10 +59,15 @@
|
||||
- Fix: 他のサーバーのユーザーへ「メッセージを送信」した時の初期テキストのメンションが間違っている問題を修正
|
||||
|
||||
### Server
|
||||
- Fix: ノート検索 `notes/search` にてhostを指定した際に検索結果に反映されるように
|
||||
- cacheRemoteFilesの初期値はfalseになりました
|
||||
- ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善
|
||||
- Webhookのペイロードにサーバーのurlが含まれるようになりました
|
||||
- Webhook設定でsecretを空に出来るように
|
||||
- 使われていないアンテナの自動停止を設定可能に
|
||||
- nodeinfo 2.1対応
|
||||
- 自分へのメンション一覧を取得する際のパフォーマンスを向上
|
||||
- Docker環境でjemallocを使用することでメモリ使用量を削減
|
||||
- Fix: ノート検索 `notes/search` にてhostを指定した際に検索結果に反映されるように
|
||||
- Fix: 一部のfeatured noteを照会できない問題を修正
|
||||
- Fix: muteがapiからのuser list timeline取得で機能しない問題を修正
|
||||
- Fix: ジョブキュー管理画面の認証を回避できる問題を修正
|
||||
|
@@ -436,3 +436,6 @@ marginはそのコンポーネントを使う側が設定する
|
||||
## その他
|
||||
### HTMLのクラス名で follow という単語は使わない
|
||||
広告ブロッカーで誤ってブロックされる
|
||||
|
||||
### indexというファイル名を使うな
|
||||
ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
|
||||
|
@@ -62,7 +62,7 @@ ARG GID="991"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ffmpeg tini curl \
|
||||
ffmpeg tini curl libjemalloc-dev libjemalloc2 \
|
||||
&& corepack enable \
|
||||
&& groupadd -g "${GID}" misskey \
|
||||
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \
|
||||
@@ -81,6 +81,7 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/bui
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
||||
COPY --chown=misskey:misskey . ./
|
||||
|
||||
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
|
||||
ENV NODE_ENV=production
|
||||
HEALTHCHECK --interval=5s --retries=20 CMD ["/bin/bash", "/misskey/healthcheck.sh"]
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
|
@@ -135,6 +135,7 @@ redis:
|
||||
|
||||
# Available methods:
|
||||
# aid ... Short, Millisecond accuracy
|
||||
# aidx ... Millisecond accuracy
|
||||
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||
# ulid ... Millisecond accuracy
|
||||
# objectid ... This is left for backward compatibility
|
||||
@@ -142,7 +143,7 @@ redis:
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# ID SETTINGS AFTER THAT!
|
||||
|
||||
id: "aid"
|
||||
id: "aidx"
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Other configuration └─────────────────────────────────────
|
||||
|
||||
@@ -186,6 +187,10 @@ id: "aid"
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
# Limit of notes to fetch from outbox with remote user first fetched (default: 5)
|
||||
# https://github.com/misskey-dev/misskey/pull/11130
|
||||
outboxNotesFetchLimit: 5
|
||||
|
||||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
@@ -50,7 +50,7 @@ services:
|
||||
|
||||
# meilisearch:
|
||||
# restart: always
|
||||
# image: getmeili/meilisearch:v1.1.1
|
||||
# image: getmeili/meilisearch:v1.3.4
|
||||
# environment:
|
||||
# - MEILI_NO_ANALYTICS=true
|
||||
# - MEILI_ENV=production
|
||||
|
65
gulpfile.mjs
65
gulpfile.mjs
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Gulp tasks
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import gulp from 'gulp';
|
||||
import replace from 'gulp-replace';
|
||||
import terser from 'gulp-terser';
|
||||
import cssnano from 'gulp-cssnano';
|
||||
|
||||
import locales from './locales/index.js';
|
||||
import meta from './package.json' assert { type: "json" };
|
||||
|
||||
gulp.task('copy:backend:views', () =>
|
||||
gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views'))
|
||||
);
|
||||
|
||||
gulp.task('copy:frontend:fonts', () =>
|
||||
gulp.src('./packages/frontend/node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/_frontend_dist_/fonts/'))
|
||||
);
|
||||
|
||||
gulp.task('copy:frontend:tabler-icons', () =>
|
||||
gulp.src('./packages/frontend/node_modules/@tabler/icons-webfont/**/*').pipe(gulp.dest('./built/_frontend_dist_/tabler-icons/'))
|
||||
);
|
||||
|
||||
gulp.task('copy:frontend:locales', cb => {
|
||||
fs.mkdirSync('./built/_frontend_dist_/locales', { recursive: true });
|
||||
|
||||
const v = { '_version_': meta.version };
|
||||
|
||||
for (const [lang, locale] of Object.entries(locales)) {
|
||||
fs.writeFileSync(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
|
||||
gulp.task('build:backend:script', () => {
|
||||
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
|
||||
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
|
||||
.pipe(terser({
|
||||
toplevel: true
|
||||
}))
|
||||
.pipe(gulp.dest('./packages/backend/built/server/web/'));
|
||||
});
|
||||
|
||||
gulp.task('build:backend:style', () => {
|
||||
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css'])
|
||||
.pipe(cssnano({
|
||||
zindex: false
|
||||
}))
|
||||
.pipe(gulp.dest('./packages/backend/built/server/web/'));
|
||||
});
|
||||
|
||||
gulp.task('build', gulp.parallel(
|
||||
'copy:frontend:locales', 'copy:backend:views', 'build:backend:script', 'build:backend:style', 'copy:frontend:fonts', 'copy:frontend:tabler-icons'
|
||||
));
|
||||
|
||||
gulp.task('default', gulp.task('build'));
|
||||
|
||||
gulp.task('watch', () => {
|
||||
gulp.watch([
|
||||
'./packages/*/src/**/*',
|
||||
], { ignoreInitial: false }, gulp.task('build'));
|
||||
});
|
@@ -1019,7 +1019,6 @@ retryAllQueuesConfirmText: "Tohle dočasně zvýší zatěž na server."
|
||||
enableChartsForRemoteUser: "Vygenerovat grafy dat vzdálených uživatelů"
|
||||
enableChartsForFederatedInstances: "Vygenerovat grafy dat vzdálených instancí"
|
||||
showClipButtonInNoteFooter: "Přidat \"Připnout\" do akčního menu poznámky"
|
||||
largeNoteReactions: "Zvětšit zobrazované reakce"
|
||||
noteIdOrUrl: "ID nebo URL poznámky"
|
||||
video: "Video"
|
||||
videos: "Videa"
|
||||
@@ -1687,7 +1686,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Váš prohlížeč nepodporuje bezpečnostní klíče."
|
||||
registerTOTPBeforeKey: "Nastavte aplikaci autentizátoru pro registraci bezpečnostního nebo přístupového klíče."
|
||||
securityKeyInfo: "Kromě ověřování otiskem prstu nebo PIN můžete nastavit také ověřování pomocí hardwarových bezpečnostních klíčů, které podporují FIDO2, a svůj účet tak dále zabezpečit."
|
||||
chromePasskeyNotSupported: "Chrome passkeys nejsou v současné době podporovány."
|
||||
registerSecurityKey: "Registrace bezpečnostního nebo přístupového klíče"
|
||||
securityKeyName: "Zadejte název klíče"
|
||||
tapSecurityKey: "Při registraci bezpečnostního nebo přístupového klíče postupujte podle svého prohlížeče."
|
||||
|
@@ -45,6 +45,7 @@ pin: "An dein Profil anheften"
|
||||
unpin: "Von deinem Profil lösen"
|
||||
copyContent: "Inhalt kopieren"
|
||||
copyLink: "Link kopieren"
|
||||
copyLinkRenote: "Renote-Link kopieren"
|
||||
delete: "Löschen"
|
||||
deleteAndEdit: "Löschen und Bearbeiten"
|
||||
deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen."
|
||||
@@ -320,7 +321,7 @@ copyUrl: "URL kopieren"
|
||||
rename: "Umbenennen"
|
||||
avatar: "Profilbild"
|
||||
banner: "Banner"
|
||||
displayOfSensitiveMedia: "Anzeige von sensiblen Medien"
|
||||
displayOfSensitiveMedia: "Darstellung sensibler Medien"
|
||||
whenServerDisconnected: "Bei Verbindungsverlust zum Server"
|
||||
disconnectedFromServer: "Die Verbindung zum Server wurde getrennt"
|
||||
reload: "Aktualisieren"
|
||||
@@ -416,9 +417,11 @@ totp: "Authentifizierungs-App"
|
||||
totpDescription: "Logge dich via Authentifizierungs-App mit Einmalpasswort ein"
|
||||
moderator: "Moderator"
|
||||
moderation: "Moderation"
|
||||
moderationNote: "Moderationsnotiz"
|
||||
addModerationNote: "Moderationsnotiz hinzufügen"
|
||||
nUsersMentioned: "Von {n} Benutzern erwähnt"
|
||||
securityKeyAndPasskey: "Security-Tokens und Passkeys"
|
||||
securityKey: "Sicherheitsschlüssel"
|
||||
securityKeyAndPasskey: "Hardware-Sicherheitsschlüssel und Passkeys"
|
||||
securityKey: "Hardware-Sicherheitsschlüssel"
|
||||
lastUsed: "Zuletzt benutzt"
|
||||
lastUsedAt: "Zuletzt verwendet: {t}"
|
||||
unregister: "Deaktivieren"
|
||||
@@ -633,7 +636,7 @@ regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltun
|
||||
instanceMute: "Instanzstummschaltungen"
|
||||
userSaysSomething: "{name} hat etwas gesagt"
|
||||
makeActive: "Aktivieren"
|
||||
display: "Anzeigen"
|
||||
display: "Anzeigeart"
|
||||
copy: "Kopieren"
|
||||
metrics: "Metriken"
|
||||
overview: "Übersicht"
|
||||
@@ -655,6 +658,7 @@ behavior: "Verhalten"
|
||||
sample: "Beispiel"
|
||||
abuseReports: "Meldungen"
|
||||
reportAbuse: "Melden"
|
||||
reportAbuseRenote: "Renote melden"
|
||||
reportAbuseOf: "{name} melden"
|
||||
fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an."
|
||||
abuseReported: "Deine Meldung wurde versendet. Vielen Dank."
|
||||
@@ -1021,7 +1025,7 @@ retryAllQueuesConfirmText: "Dies wird zu einer temporären Erhöhung der Serverl
|
||||
enableChartsForRemoteUser: "Diagramme für Nutzer fremder Instanzen erstellen"
|
||||
enableChartsForFederatedInstances: "Diagramme für fremde Instanzen erstellen"
|
||||
showClipButtonInNoteFooter: "\"Clip\" zum Notizmenu hinzufügen"
|
||||
largeNoteReactions: "Reaktionen vergrößert anzeigen"
|
||||
reactionsDisplaySize: "Reaktionsanzeigegröße"
|
||||
noteIdOrUrl: "Notiz-ID oder URL"
|
||||
video: "Video"
|
||||
videos: "Videos"
|
||||
@@ -1055,7 +1059,7 @@ archive: "Archivieren"
|
||||
channelArchiveConfirmTitle: "{name} wirklich archivieren?"
|
||||
channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden."
|
||||
thisChannelArchived: "Dieser Kanal wurde archiviert."
|
||||
displayOfNote: "Anzeige von Notizen"
|
||||
displayOfNote: "Darstellung von Notizen"
|
||||
initialAccountSetting: "Kontoeinrichtung"
|
||||
youFollowing: "Gefolgt"
|
||||
preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen"
|
||||
@@ -1103,6 +1107,7 @@ forYou: "Für dich"
|
||||
currentAnnouncements: "Aktuelle Ankündigungen"
|
||||
pastAnnouncements: "Alte Ankündigungen"
|
||||
youHaveUnreadAnnouncements: "Es gibt neue Ankündigungen."
|
||||
useSecurityKey: "Folge bitten den Anweisungen deines Browsers bzw. Gerätes und verwende deinen Hardware-Sicherheitsschlüssel oder Passkey."
|
||||
_announcement:
|
||||
forExistingUsers: "Nur für existierende Nutzer"
|
||||
forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
|
||||
@@ -1383,6 +1388,9 @@ _achievements:
|
||||
title: "Brain Diver"
|
||||
description: "Sende den Link zu Brain Diver"
|
||||
flavor: "Misskey-Misskey La-Tu-Ma"
|
||||
_smashTestNotificationButton:
|
||||
title: "Testüberfluss"
|
||||
description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
|
||||
_role:
|
||||
new: "Rolle erstellen"
|
||||
edit: "Rolle bearbeiten"
|
||||
@@ -1702,11 +1710,10 @@ _2fa:
|
||||
step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird."
|
||||
setupCompleted: "Einrichtung abgeschlossen"
|
||||
step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen."
|
||||
securityKeyNotSupported: "Dein Browser unterstützt keine Security-Tokens."
|
||||
securityKeyNotSupported: "Dein Browser unterstützt keine Hardware-Sicherheitsschlüssel."
|
||||
registerTOTPBeforeKey: "Um einen Security-Token oder einen Passkey zu registrieren, musst du zuerst eine Authentifizierungs-App registrieren."
|
||||
securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten."
|
||||
chromePasskeyNotSupported: "Chrome-Passkeys werden zur Zeit nicht unterstützt."
|
||||
registerSecurityKey: "Security-Token oder Passkey registrieren"
|
||||
registerSecurityKey: "Hardware-Sicherheitsschlüssel oder Passkey registrieren"
|
||||
securityKeyName: "Schlüsselname eingeben"
|
||||
tapSecurityKey: "Bitten folge den Anweisungen deines Browsers zur Registrierung"
|
||||
removeKey: "Sicherheitsschlüssel entfernen"
|
||||
@@ -1993,6 +2000,10 @@ _notification:
|
||||
unreadAntennaNote: "Antenne {name}"
|
||||
emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert"
|
||||
achievementEarned: "Errungenschaft freigeschaltet"
|
||||
testNotification: "Testbenachrichtigung"
|
||||
checkNotificationBehavior: "Aussehen von Benachrichtigungen überprüfen"
|
||||
sendTestNotification: "Testbenachrichtigung senden"
|
||||
notificationWillBeDisplayedLikeThis: "Benachrichtigungen sehen so aus"
|
||||
_types:
|
||||
all: "Alle"
|
||||
follow: "Neue Follower"
|
||||
|
@@ -45,6 +45,7 @@ pin: "Pin to profile"
|
||||
unpin: "Unpin from profile"
|
||||
copyContent: "Copy contents"
|
||||
copyLink: "Copy link"
|
||||
copyLinkRenote: "Copy renote link"
|
||||
delete: "Delete"
|
||||
deleteAndEdit: "Delete and edit"
|
||||
deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it."
|
||||
@@ -416,6 +417,8 @@ totp: "Authenticator App"
|
||||
totpDescription: "Use an authenticator app to enter one-time passwords"
|
||||
moderator: "Moderator"
|
||||
moderation: "Moderation"
|
||||
moderationNote: "Moderation note"
|
||||
addModerationNote: "Add moderation note"
|
||||
nUsersMentioned: "Mentioned by {n} users"
|
||||
securityKeyAndPasskey: "Security- and passkeys"
|
||||
securityKey: "Security key"
|
||||
@@ -581,7 +584,7 @@ serviceworkerInfo: "Must be enabled for push notifications."
|
||||
deletedNote: "Deleted note"
|
||||
invisibleNote: "Invisible note"
|
||||
enableInfiniteScroll: "Automatically load more"
|
||||
visibility: "Visiblility"
|
||||
visibility: "Visibility"
|
||||
poll: "Poll"
|
||||
useCw: "Hide content"
|
||||
enablePlayer: "Open video player"
|
||||
@@ -655,6 +658,7 @@ behavior: "Behavior"
|
||||
sample: "Sample"
|
||||
abuseReports: "Reports"
|
||||
reportAbuse: "Report"
|
||||
reportAbuseRenote: "Report renote"
|
||||
reportAbuseOf: "Report {name}"
|
||||
fillAbuseReportDescription: "Please fill in details regarding this report. If it is about a specific note, please include its URL."
|
||||
abuseReported: "Your report has been sent. Thank you very much."
|
||||
@@ -1021,7 +1025,7 @@ retryAllQueuesConfirmText: "This will temporarily increase the server load."
|
||||
enableChartsForRemoteUser: "Generate remote user data charts"
|
||||
enableChartsForFederatedInstances: "Generate remote instance data charts"
|
||||
showClipButtonInNoteFooter: "Add \"Clip\" to note action menu"
|
||||
largeNoteReactions: "Enlargen displayed reactions"
|
||||
reactionsDisplaySize: "Reaction display size"
|
||||
noteIdOrUrl: "Note ID or URL"
|
||||
video: "Video"
|
||||
videos: "Videos"
|
||||
@@ -1103,6 +1107,7 @@ forYou: "For you"
|
||||
currentAnnouncements: "Current announcements"
|
||||
pastAnnouncements: "Past announcements"
|
||||
youHaveUnreadAnnouncements: "There are unread announcements."
|
||||
useSecurityKey: "Please follow your browser's or device's instructions to use your security- or passkey."
|
||||
_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."
|
||||
@@ -1383,6 +1388,9 @@ _achievements:
|
||||
title: "Brain Diver"
|
||||
description: "Post the link to Brain Diver"
|
||||
flavor: "Misskey-Misskey La-Tu-Ma"
|
||||
_smashTestNotificationButton:
|
||||
title: "Test overflow"
|
||||
description: "Trigger the notification test repeatedly within an extremely short time"
|
||||
_role:
|
||||
new: "New role"
|
||||
edit: "Edit role"
|
||||
@@ -1705,7 +1713,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Your browser does not support security keys."
|
||||
registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key."
|
||||
securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account."
|
||||
chromePasskeyNotSupported: "Chrome passkeys are currently not supported."
|
||||
registerSecurityKey: "Register a security or pass key"
|
||||
securityKeyName: "Enter a key name"
|
||||
tapSecurityKey: "Please follow your browser to register the security or pass key"
|
||||
@@ -1993,6 +2000,10 @@ _notification:
|
||||
unreadAntennaNote: "Antenna {name}"
|
||||
emptyPushNotificationMessage: "Push notifications have been updated"
|
||||
achievementEarned: "Achievement unlocked"
|
||||
testNotification: "Test notification"
|
||||
checkNotificationBehavior: "Check notification appearance"
|
||||
sendTestNotification: "Send test notification"
|
||||
notificationWillBeDisplayedLikeThis: "Notifications look like this"
|
||||
_types:
|
||||
all: "All"
|
||||
follow: "New followers"
|
||||
|
@@ -45,6 +45,7 @@ pin: "Fijar al perfil"
|
||||
unpin: "Desfijar"
|
||||
copyContent: "Copiar contenido"
|
||||
copyLink: "Copiar enlace"
|
||||
copyLinkRenote: "Copiar enlace de renota"
|
||||
delete: "Borrar"
|
||||
deleteAndEdit: "Borrar y editar"
|
||||
deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta nota y editarla? Perderás todas las reacciones, renotas y respuestas."
|
||||
@@ -655,6 +656,7 @@ behavior: "Comportamiento"
|
||||
sample: "Muestra"
|
||||
abuseReports: "Reportes"
|
||||
reportAbuse: "Reportar"
|
||||
reportAbuseRenote: "Reportar renota"
|
||||
reportAbuseOf: "Reportar a {name}"
|
||||
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta."
|
||||
abuseReported: "Se ha enviado el reporte. Muchas gracias."
|
||||
@@ -1021,7 +1023,7 @@ retryAllQueuesConfirmText: "La carga del servidor está incrementándose tempora
|
||||
enableChartsForRemoteUser: "Generar gráficas de usuarios remotos."
|
||||
enableChartsForFederatedInstances: "Generar gráficos de servidores remotos"
|
||||
showClipButtonInNoteFooter: "Añadir \"Clip\" al menú de notas"
|
||||
largeNoteReactions: "Agrandar las reacciones de las notas"
|
||||
reactionsDisplaySize: "Tamaño de las reacciones"
|
||||
noteIdOrUrl: "ID o URL de la nota"
|
||||
video: "Video"
|
||||
videos: "Video"
|
||||
@@ -1103,6 +1105,7 @@ forYou: "Para ti"
|
||||
currentAnnouncements: "Anuncios actuales"
|
||||
pastAnnouncements: "Anuncios anteriores"
|
||||
youHaveUnreadAnnouncements: "Hay anuncios sin leer"
|
||||
useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso."
|
||||
_announcement:
|
||||
forExistingUsers: "Solo para usuarios registrados"
|
||||
forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
|
||||
@@ -1705,7 +1708,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Tu navegador no soporta claves de autenticación."
|
||||
registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key.\npor favor. configura una aplicación de autenticación para registrar una llave de seguridad."
|
||||
securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad de hardware que soporte FIDO2 o con un certificado de huella digital o con un PIN"
|
||||
chromePasskeyNotSupported: "Las llaves de seguridad de Chrome no son soportadas por el momento."
|
||||
registerSecurityKey: "Registrar una llave de seguridad"
|
||||
securityKeyName: "Ingresa un nombre para la clave"
|
||||
tapSecurityKey: "Por favor, sigue tu navegador para registrar una llave de seguridad"
|
||||
@@ -1754,6 +1756,10 @@ _permissions:
|
||||
"write:gallery": "Editar galería"
|
||||
"read:gallery-likes": "Ver favoritos de la galería"
|
||||
"write:gallery-likes": "Editar favoritos de la galería"
|
||||
"read:flash": "Ver Play"
|
||||
"write:flash": "Editar Plays"
|
||||
"read:flash-likes": "Ver los Play que me gustan"
|
||||
"write:flash-likes": "Editar lista de Play que me gustan"
|
||||
_auth:
|
||||
shareAccessTitle: "Permisos de la aplicación"
|
||||
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
|
||||
@@ -2024,6 +2030,8 @@ _deck:
|
||||
introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas columnas donde quieras."
|
||||
widgetsIntroduction: "Por favor selecciona \"Editar Widgets\" en el menú columna y agrega un widget."
|
||||
useSimpleUiForNonRootPages: "Mostrar páginas no pertenecientes a la raíz con la interfaz simple"
|
||||
usedAsMinWidthWhenFlexible: "Se usará el ancho mínimo cuando la opción \"Autoajustar ancho\" esté habilitada"
|
||||
flexible: "Autoajustar ancho"
|
||||
_columns:
|
||||
main: "Principal"
|
||||
widgets: "Widgets"
|
||||
|
@@ -411,6 +411,8 @@ totp: "Application d'authentification"
|
||||
totpDescription: "Entrez un mot de passe à usage unique à l'aide d'une application d'authentification"
|
||||
moderator: "Modérateur·rice·s"
|
||||
moderation: "Modérations"
|
||||
moderationNote: "Note de modération"
|
||||
addModerationNote: "Ajouter une note de modération"
|
||||
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
|
||||
securityKey: "Clé de sécurité"
|
||||
lastUsed: "Dernier utilisé"
|
||||
@@ -821,6 +823,7 @@ translatedFrom: "Traduit depuis {x}"
|
||||
accountDeletionInProgress: "La suppression de votre compte est en cours"
|
||||
usernameInfo: "C'est un nom qui identifie votre compte sur l'instance de manière unique. Vous pouvez utiliser des lettres de l'alphabet (minuscules et majuscules), des chiffres (de 0 à 9), ou bien le tiret « _ ». Vous ne pourrez pas modifier votre nom d'utilisateur·rice par la suite."
|
||||
aiChanMode: "Mode Ai"
|
||||
devMode: "Mode développement"
|
||||
keepCw: "Garder le CW"
|
||||
pubSub: "Comptes Pub/Sub"
|
||||
lastCommunication: "Dernière communication"
|
||||
@@ -940,6 +943,7 @@ roles: "Rôles"
|
||||
role: "Rôles"
|
||||
noRole: "Aucun rôle"
|
||||
normalUser: "Simple utilisateur·rice"
|
||||
undefined: "Non défini"
|
||||
assign: "Attribuer"
|
||||
color: "Couleur"
|
||||
manageCustomEmojis: "Gestion des émojis personnalisés"
|
||||
@@ -947,9 +951,12 @@ preset: "Préréglage"
|
||||
selectFromPresets: "Sélectionner à partir des préréglages"
|
||||
achievements: "Accomplissements"
|
||||
thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes."
|
||||
thisPostMayBeAnnoyingHome: "Publier vers le fil principal"
|
||||
thisPostMayBeAnnoyingCancel: "Annuler"
|
||||
thisPostMayBeAnnoyingIgnore: "Publier quand-même"
|
||||
internalServerError: "Erreur interne du serveur"
|
||||
copyErrorInfo: "Copier les détails de l’erreur"
|
||||
exploreOtherServers: "Trouver une autre instance"
|
||||
disableFederationOk: "Désactiver"
|
||||
license: "Licence"
|
||||
video: "Vidéo"
|
||||
@@ -957,7 +964,10 @@ videos: "Vidéos"
|
||||
dataSaver: "Économiseur de données"
|
||||
accountMigration: "Migration de compte"
|
||||
accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
|
||||
accountMovedShort: "Ce compte a migré"
|
||||
operationForbidden: "Opération non autorisée"
|
||||
addMemo: "Ajouter un mémo"
|
||||
reactionsList: "Réactions"
|
||||
notificationDisplay: "Style des notifications"
|
||||
leftTop: "En haut à gauche"
|
||||
rightTop: "En haut à droite"
|
||||
@@ -966,6 +976,7 @@ rightBottom: "En bas à droite"
|
||||
vertical: "Vertical"
|
||||
horizontal: "Latéral"
|
||||
serverRules: "Règles du serveur"
|
||||
archive: "Archive"
|
||||
youFollowing: "Abonné·e"
|
||||
later: "Plus tard"
|
||||
goToMisskey: "Retour vers Misskey"
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as ts from 'typescript';
|
||||
import ts from 'typescript';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function createMembers(record) {
|
||||
return Object.entries(record)
|
||||
|
@@ -1019,7 +1019,6 @@ retryAllQueuesConfirmText: "Hal ini akan meningkatkan beban sementara ke peladen
|
||||
enableChartsForRemoteUser: "Buat bagan data pengguna instansi luar"
|
||||
enableChartsForFederatedInstances: "Buat bagan data peladen instansi luar"
|
||||
showClipButtonInNoteFooter: "Tambahkan \"Klip\" ke menu aksi catatan"
|
||||
largeNoteReactions: "Besarkan reaksi yang ditampilkan"
|
||||
noteIdOrUrl: "ID catatan atau URL"
|
||||
video: "Video"
|
||||
videos: "Video"
|
||||
@@ -1691,7 +1690,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Peramban kamu tidak mendukung security key."
|
||||
registerTOTPBeforeKey: "Mohon atur aplikasi autentikator untuk mendaftarkan security key atau passkey."
|
||||
securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu."
|
||||
chromePasskeyNotSupported: "Passkey Chrome saat ini tidak didukung."
|
||||
registerSecurityKey: "Daftarkan security key atau passkey."
|
||||
securityKeyName: "Masukkan nama key."
|
||||
tapSecurityKey: "Mohon ikuti peramban kamu untuk mendaftarkan security key atau passkey"
|
||||
|
23
locales/index.d.ts
vendored
23
locales/index.d.ts
vendored
@@ -359,7 +359,6 @@ export interface Locale {
|
||||
"driveCapacityPerLocalAccount": string;
|
||||
"driveCapacityPerRemoteAccount": string;
|
||||
"inMb": string;
|
||||
"iconUrl": string;
|
||||
"bannerUrl": string;
|
||||
"backgroundImageUrl": string;
|
||||
"basicInfo": string;
|
||||
@@ -420,6 +419,8 @@ export interface Locale {
|
||||
"totpDescription": string;
|
||||
"moderator": string;
|
||||
"moderation": string;
|
||||
"moderationNote": string;
|
||||
"addModerationNote": string;
|
||||
"nUsersMentioned": string;
|
||||
"securityKeyAndPasskey": string;
|
||||
"securityKey": string;
|
||||
@@ -1026,7 +1027,7 @@ export interface Locale {
|
||||
"enableChartsForRemoteUser": string;
|
||||
"enableChartsForFederatedInstances": string;
|
||||
"showClipButtonInNoteFooter": string;
|
||||
"largeNoteReactions": string;
|
||||
"reactionsDisplaySize": string;
|
||||
"noteIdOrUrl": string;
|
||||
"video": string;
|
||||
"videos": string;
|
||||
@@ -1108,6 +1109,7 @@ export interface Locale {
|
||||
"currentAnnouncements": string;
|
||||
"pastAnnouncements": string;
|
||||
"youHaveUnreadAnnouncements": string;
|
||||
"useSecurityKey": string;
|
||||
"_announcement": {
|
||||
"forExistingUsers": string;
|
||||
"forExistingUsersDescription": string;
|
||||
@@ -1137,6 +1139,14 @@ export interface Locale {
|
||||
"_serverRules": {
|
||||
"description": string;
|
||||
};
|
||||
"_serverSettings": {
|
||||
"iconUrl": string;
|
||||
"appIconDescription": string;
|
||||
"appIconUsageExample": string;
|
||||
"appIconStyleRecommendation": string;
|
||||
"appIconResolutionMustBe": string;
|
||||
"manifestJsonOverride": string;
|
||||
};
|
||||
"_accountMigration": {
|
||||
"moveFrom": string;
|
||||
"moveFromSub": string;
|
||||
@@ -1466,6 +1476,10 @@ export interface Locale {
|
||||
"description": string;
|
||||
"flavor": string;
|
||||
};
|
||||
"_smashTestNotificationButton": {
|
||||
"title": string;
|
||||
"description": string;
|
||||
};
|
||||
};
|
||||
};
|
||||
"_role": {
|
||||
@@ -1822,7 +1836,6 @@ export interface Locale {
|
||||
"securityKeyNotSupported": string;
|
||||
"registerTOTPBeforeKey": string;
|
||||
"securityKeyInfo": string;
|
||||
"chromePasskeyNotSupported": string;
|
||||
"registerSecurityKey": string;
|
||||
"securityKeyName": string;
|
||||
"tapSecurityKey": string;
|
||||
@@ -2132,6 +2145,10 @@ export interface Locale {
|
||||
"unreadAntennaNote": string;
|
||||
"emptyPushNotificationMessage": string;
|
||||
"achievementEarned": string;
|
||||
"testNotification": string;
|
||||
"checkNotificationBehavior": string;
|
||||
"sendTestNotification": string;
|
||||
"notificationWillBeDisplayedLikeThis": string;
|
||||
"_types": {
|
||||
"all": string;
|
||||
"follow": string;
|
||||
|
@@ -45,6 +45,7 @@ pin: "Fissa sul profilo"
|
||||
unpin: "Non fissare sul profilo"
|
||||
copyContent: "Copia il contenuto"
|
||||
copyLink: "Copia il link"
|
||||
copyLinkRenote: "Copia collegamento alla Rinota"
|
||||
delete: "Elimina"
|
||||
deleteAndEdit: "Elimina e modifica"
|
||||
deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verranno eliminate anche tutte le reazioni, rinote e risposte collegate."
|
||||
@@ -416,6 +417,8 @@ totp: "App di autenticazione"
|
||||
totpDescription: "Inserisci un codice OTP tramite un'app di autenticazione"
|
||||
moderator: "Moderatore"
|
||||
moderation: "moderazione"
|
||||
moderationNote: "Promemoria di moderazione"
|
||||
addModerationNote: "Aggiungi promemoria di moderazione"
|
||||
nUsersMentioned: "{n} profili menzionati"
|
||||
securityKeyAndPasskey: "Chiave di sicurezza e accesso"
|
||||
securityKey: "Chiave di sicurezza"
|
||||
@@ -655,6 +658,7 @@ behavior: "Comportamento"
|
||||
sample: "Esempio"
|
||||
abuseReports: "Segnalazioni"
|
||||
reportAbuse: "Segnala"
|
||||
reportAbuseRenote: "Segnala la Rinota"
|
||||
reportAbuseOf: "Segnala {name}"
|
||||
fillAbuseReportDescription: "Per favore, spiegaci il motivo della segnalazione. Se riguarda una Nota precisa, indica anche l'indirizzo URL."
|
||||
abuseReported: "La segnalazione è stata inviata. Grazie."
|
||||
@@ -1021,7 +1025,7 @@ retryAllQueuesConfirmText: "Potrebbe sovraccaricare il server temporaneamente."
|
||||
enableChartsForRemoteUser: "Abilita i grafici per i profili remoti"
|
||||
enableChartsForFederatedInstances: "Abilita i grafici per le istanze federate"
|
||||
showClipButtonInNoteFooter: "Aggiungi il bottone Clip tra le azioni delle Note"
|
||||
largeNoteReactions: "Ingrandisci le reazioni"
|
||||
reactionsDisplaySize: "Grandezza delle reazioni"
|
||||
noteIdOrUrl: "ID della Nota o URL"
|
||||
video: "Video"
|
||||
videos: "Video"
|
||||
@@ -1103,6 +1107,7 @@ forYou: "Per te"
|
||||
currentAnnouncements: "Annunci attuali"
|
||||
pastAnnouncements: "Annunci precedenti"
|
||||
youHaveUnreadAnnouncements: "Ci sono Annunci non letti"
|
||||
useSecurityKey: "Per utilizzare la chiave di sicurezza o la passkey, segui le indicazioni del dispositivo"
|
||||
_announcement:
|
||||
forExistingUsers: "Solo ai profili attuali"
|
||||
forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
|
||||
@@ -1383,6 +1388,9 @@ _achievements:
|
||||
title: "Brain Diver"
|
||||
description: "Pubblica un link a Brain Diver"
|
||||
flavor: "Sulle note di Brain Diver"
|
||||
_smashTestNotificationButton:
|
||||
title: "Prove eccessive"
|
||||
description: "Hai provato le notifiche consecutivamente in un periodo di tempo molto breve"
|
||||
_role:
|
||||
new: "Nuovo ruolo"
|
||||
edit: "Modifica ruolo"
|
||||
@@ -1705,7 +1713,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Il tuo browser non supporta le chiavi di sicurezza."
|
||||
registerTOTPBeforeKey: "Ti occorre un'app di autenticazione con OTP, prima di registrare la chiave di sicurezza."
|
||||
securityKeyInfo: "È possibile impostare il dispositivo per accedere utilizzando una chiave di sicurezza hardware che supporta FIDO2 o un'impronta digitale o un PIN sul dispositivo."
|
||||
chromePasskeyNotSupported: "Le passkey di Chrome non sono attualmente supportate."
|
||||
registerSecurityKey: "Registra la chiave di sicurezza"
|
||||
securityKeyName: "Inserisci il nome della chiave"
|
||||
tapSecurityKey: "Segui le istruzioni del browser e registra la chiave di sicurezza."
|
||||
@@ -1993,6 +2000,10 @@ _notification:
|
||||
unreadAntennaNote: "Antenna {name}"
|
||||
emptyPushNotificationMessage: "Le notifiche push sono state aggiornate."
|
||||
achievementEarned: "Obiettivo raggiunto"
|
||||
testNotification: "Prova la notifica"
|
||||
checkNotificationBehavior: "Prova il comportamento della notifica"
|
||||
sendTestNotification: "Spedisci una notifica di prova"
|
||||
notificationWillBeDisplayedLikeThis: "La notifica apparirà così"
|
||||
_types:
|
||||
all: "Tutto"
|
||||
follow: "Novità follower"
|
||||
@@ -2028,8 +2039,8 @@ _deck:
|
||||
introduction2: "È possibile aggiungere colonne in qualsiasi momento premendo + sulla destra dello schermo."
|
||||
widgetsIntroduction: "Dal menu della colonna, selezionare \"Modifica i riquadri\" per aggiungere un un riquadro con funzionalità"
|
||||
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
|
||||
usedAsMinWidthWhenFlexible: "Lunghezza minima sarò usata quando l'opzione \"Lunghezza automacia\" è attivata"
|
||||
flexible: "Lunghezza automatica"
|
||||
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
|
||||
flexible: "Larghezza flessibile"
|
||||
_columns:
|
||||
main: "Principale"
|
||||
widgets: "Riquadri"
|
||||
|
@@ -356,7 +356,6 @@ invite: "招待"
|
||||
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量"
|
||||
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量"
|
||||
inMb: "メガバイト単位"
|
||||
iconUrl: "アイコン画像のURL (faviconなど)"
|
||||
bannerUrl: "バナー画像のURL"
|
||||
backgroundImageUrl: "背景画像のURL"
|
||||
basicInfo: "基本情報"
|
||||
@@ -417,6 +416,8 @@ totp: "認証アプリ"
|
||||
totpDescription: "認証アプリを使ってワンタイムパスワードを入力"
|
||||
moderator: "モデレーター"
|
||||
moderation: "モデレーション"
|
||||
moderationNote: "モデレーションノート"
|
||||
addModerationNote: "モデレーションノートを追加する"
|
||||
nUsersMentioned: "{n}人が投稿"
|
||||
securityKeyAndPasskey: "セキュリティキー・パスキー"
|
||||
securityKey: "セキュリティキー"
|
||||
@@ -1023,7 +1024,7 @@ retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大するこ
|
||||
enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
|
||||
enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
|
||||
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
|
||||
largeNoteReactions: "ノートのリアクションを大きく表示"
|
||||
reactionsDisplaySize: "リアクションの表示サイズ"
|
||||
noteIdOrUrl: "ノートIDまたはURL"
|
||||
video: "動画"
|
||||
videos: "動画"
|
||||
@@ -1105,6 +1106,7 @@ forYou: "あなたへ"
|
||||
currentAnnouncements: "現在のお知らせ"
|
||||
pastAnnouncements: "過去のお知らせ"
|
||||
youHaveUnreadAnnouncements: "未読のお知らせがあります。"
|
||||
useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
|
||||
|
||||
_announcement:
|
||||
forExistingUsers: "既存ユーザーのみ"
|
||||
@@ -1135,6 +1137,14 @@ _initialAccountSetting:
|
||||
_serverRules:
|
||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||
|
||||
_serverSettings:
|
||||
iconUrl: "アイコン画像のURL"
|
||||
appIconDescription: "{host}がアプリとして表示される際のアイコンを指定します。"
|
||||
appIconUsageExample: "例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など"
|
||||
appIconStyleRecommendation: "画像は透過部分が無く、塗りつぶされた余白がある背景を持つことが推奨されます。"
|
||||
appIconResolutionMustBe: "解像度は必ず{resolution}である必要があります。"
|
||||
manifestJsonOverride: "manifest.jsonのオーバーライド"
|
||||
|
||||
_accountMigration:
|
||||
moveFrom: "別のアカウントからこのアカウントに移行"
|
||||
moveFromSub: "別のアカウントへエイリアスを作成"
|
||||
@@ -1390,6 +1400,9 @@ _achievements:
|
||||
title: "Brain Diver"
|
||||
description: "Brain Diverへのリンクを投稿した"
|
||||
flavor: "Misskey-Misskey La-Tu-Ma"
|
||||
_smashTestNotificationButton:
|
||||
title: "テスト過剰"
|
||||
description: "通知のテストをごく短時間のうちに連続して行った"
|
||||
|
||||
_role:
|
||||
new: "ロールの作成"
|
||||
@@ -1740,7 +1753,6 @@ _2fa:
|
||||
securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。"
|
||||
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。"
|
||||
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。"
|
||||
chromePasskeyNotSupported: "Chromeのパスキーは現在サポートしていません。"
|
||||
registerSecurityKey: "セキュリティキー・パスキーを登録する"
|
||||
securityKeyName: "キーの名前を入力"
|
||||
tapSecurityKey: "ブラウザの指示に従い、セキュリティキーやパスキーを登録してください"
|
||||
@@ -2047,6 +2059,10 @@ _notification:
|
||||
unreadAntennaNote: "アンテナ {name}"
|
||||
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
|
||||
achievementEarned: "実績を獲得"
|
||||
testNotification: "通知テスト"
|
||||
checkNotificationBehavior: "通知の表示を確かめる"
|
||||
sendTestNotification: "テスト通知を送信する"
|
||||
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
|
||||
|
||||
_types:
|
||||
all: "すべて"
|
||||
|
@@ -415,6 +415,8 @@ totp: "認証アプリ"
|
||||
totpDescription: "認証アプリ使うてワンタイムパスワードを入れる"
|
||||
moderator: "モデレーター"
|
||||
moderation: "モデレーション"
|
||||
moderationNote: "モデレーションノート"
|
||||
addModerationNote: "モデレーションノートを追加するで"
|
||||
nUsersMentioned: "{n}人が投稿"
|
||||
securityKeyAndPasskey: "セキュリティキー・パスキー"
|
||||
securityKey: "セキュリティキー"
|
||||
@@ -1020,7 +1022,6 @@ retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへん
|
||||
enableChartsForRemoteUser: "リモートユーザーのチャートを作る"
|
||||
enableChartsForFederatedInstances: "リモートサーバーのチャートを作る"
|
||||
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
|
||||
largeNoteReactions: "ノートのツッコミを大きする"
|
||||
noteIdOrUrl: "ノートIDかURL"
|
||||
video: "動画"
|
||||
videos: "動画"
|
||||
@@ -1702,7 +1703,6 @@ _2fa:
|
||||
securityKeyNotSupported: "今使とるブラウザはセキュリティキーに対応してへんのやってさ。"
|
||||
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するんやったら、まず認証アプリを設定してーな。"
|
||||
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーか端末の指紋認証やPINを使ってログインするように設定できるで。"
|
||||
chromePasskeyNotSupported: "Chromeのパスキーは今んとこ対応してないねん。"
|
||||
registerSecurityKey: "セキュリティキー・パスキーを登録するわ"
|
||||
securityKeyName: "キーの名前を入れてーや"
|
||||
tapSecurityKey: "ブラウザが言うこと聞いて、セキュリティキーとかパスキー登録しといでや"
|
||||
|
@@ -45,6 +45,7 @@ pin: "프로필에 고정"
|
||||
unpin: "프로필에서 고정 해제"
|
||||
copyContent: "내용 복사"
|
||||
copyLink: "링크 복사"
|
||||
copyLinkRenote: "Renote 링크 복사"
|
||||
delete: "삭제"
|
||||
deleteAndEdit: "삭제 후 편집"
|
||||
deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니까? 이 노트에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다."
|
||||
@@ -655,6 +656,7 @@ behavior: "동작"
|
||||
sample: "예시"
|
||||
abuseReports: "신고"
|
||||
reportAbuse: "신고"
|
||||
reportAbuseRenote: "Renote를 신고"
|
||||
reportAbuseOf: "{name}을 신고하기"
|
||||
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요."
|
||||
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
|
||||
@@ -1021,7 +1023,7 @@ retryAllQueuesConfirmText: "일시적으로 서버의 부하가 증가할 수
|
||||
enableChartsForRemoteUser: "리모트 유저의 차트를 생성"
|
||||
enableChartsForFederatedInstances: "리모트 서버의 차트를 생성"
|
||||
showClipButtonInNoteFooter: "노트 동작에 클립을 추가"
|
||||
largeNoteReactions: "노트의 리액션을 크게 표시"
|
||||
reactionsDisplaySize: "리액션 표시 크기"
|
||||
noteIdOrUrl: "노트 ID 및 URL"
|
||||
video: "동영상"
|
||||
videos: "동영상"
|
||||
@@ -1103,6 +1105,7 @@ forYou: "당신에게"
|
||||
currentAnnouncements: "현재 공지사항"
|
||||
pastAnnouncements: "과거 공지사항"
|
||||
youHaveUnreadAnnouncements: "읽지 않은 공지사항이 있습니다."
|
||||
useSecurityKey: "브라우저 또는 기기의 안내에 따라 보안 키 또는 패스키를 사용해 주십시오."
|
||||
_announcement:
|
||||
forExistingUsers: "기존 유저에게만 알림"
|
||||
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
|
||||
@@ -1705,7 +1708,6 @@ _2fa:
|
||||
securityKeyNotSupported: "이 브라우저는 보안 키를 지원하지 않습니다."
|
||||
registerTOTPBeforeKey: "보안 키 또는 패스키를 등록하려면 인증 앱을 등록하십시오."
|
||||
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
|
||||
chromePasskeyNotSupported: "현재 Chrome의 패스키는 지원되지 않습니다."
|
||||
registerSecurityKey: "보안 키 또는 패스키 등록"
|
||||
securityKeyName: "키 이름 입력"
|
||||
tapSecurityKey: "브라우저의 지시에 따라 보안 키 또는 패스키를 등록하여 주십시오"
|
||||
@@ -1993,6 +1995,10 @@ _notification:
|
||||
unreadAntennaNote: "안테나 {name}"
|
||||
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
|
||||
achievementEarned: "도전 과제를 달성했습니다"
|
||||
testNotification: "알림 테스트"
|
||||
checkNotificationBehavior: "알림 표시를 체크하기"
|
||||
sendTestNotification: "테스트 알림 보내기"
|
||||
notificationWillBeDisplayedLikeThis: "알림이 이렇게 표시됩니다"
|
||||
_types:
|
||||
all: "전부"
|
||||
follow: "팔로잉"
|
||||
@@ -2028,6 +2034,8 @@ _deck:
|
||||
introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다."
|
||||
widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요"
|
||||
useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기"
|
||||
usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다"
|
||||
flexible: "폭 자동 조정"
|
||||
_columns:
|
||||
main: "메인"
|
||||
widgets: "위젯"
|
||||
|
@@ -45,6 +45,7 @@ pin: "Fixar no perfil"
|
||||
unpin: "Desafixar do perfil"
|
||||
copyContent: "Copiar conteúdos"
|
||||
copyLink: "Copiar link"
|
||||
copyLinkRenote: "Copiar o link da repostagem"
|
||||
delete: "Excluir"
|
||||
deleteAndEdit: "Excluir e editar"
|
||||
deleteAndEditConfirm: "Deseja excluir esta nota e editá-la novamente? Todas as reações, compartilhamentos e respostas a esta nota também serão excluídas."
|
||||
@@ -654,6 +655,7 @@ behavior: "Comportamento"
|
||||
sample: "Exemplo"
|
||||
abuseReports: "Denúncias"
|
||||
reportAbuse: "Denúncias"
|
||||
reportAbuseRenote: "Reportar repostagem"
|
||||
reportAbuseOf: "Denunciar {name}"
|
||||
fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota específica envolvida, inclua também a URL dela."
|
||||
abuseReported: "Denúncia enviada. Obrigado por sua ajuda."
|
||||
@@ -917,23 +919,42 @@ pleaseSelect: "Por favor, selecione."
|
||||
reverse: "Inversão"
|
||||
colored: "Colorido"
|
||||
refreshInterval: "Intervalo de atualização"
|
||||
type: "Tipo"
|
||||
speed: "Velocidade"
|
||||
slow: "Lento"
|
||||
fast: "Rápido"
|
||||
sensitiveMediaDetection: "Detecção de conteúdo sensível"
|
||||
localOnly: "Apenas local"
|
||||
remoteOnly: "Apenas remoto"
|
||||
cannotUploadBecauseExceedsFileSizeLimit: "Não é possível realizar o upload deste arquivo porque ele excede o tamanho máximo permitido."
|
||||
beta: "Beta"
|
||||
enableAutoSensitive: "Marcar automaticamente como conteúdo sensível"
|
||||
enableAutoSensitiveDescription: "Quando disponível, a marcação de mídia sensível será automaticamente atribuído ao conteúdo de mídia usando aprendizado de máquina. Mesmo que você desative essa função, em alguns servidores, isso pode ser configurado automaticamente."
|
||||
activeEmailValidationDescription: "A validação do endereço de e-mail do usuário será realizada de forma mais rigorosa, considerando se é um endereço descartável ou se é possível realizar comunicação efetiva. Se desativado, apenas a validade do formato do endereço será verificada como uma sequência de caracteres."
|
||||
shuffle: "Aleatório"
|
||||
account: "Contas"
|
||||
move: "Mover"
|
||||
pushNotification: "Notificações Push"
|
||||
subscribePushNotification: "Ativar notificações push"
|
||||
unsubscribePushNotification: "Desativar notificações push"
|
||||
windowMinimize: "Minimizar"
|
||||
windowRestore: "Restaurar"
|
||||
caption: "legenda"
|
||||
tools: "Ferramentas"
|
||||
like: "Curtir"
|
||||
unlike: "Remover curtida"
|
||||
numberOfLikes: "Número de curtidas"
|
||||
show: "Visualizar"
|
||||
neverShow: "Não exibir novamente"
|
||||
remindMeLater: "Lembrar mais tarde"
|
||||
didYouLikeMisskey: "Você gostou do Misskey?"
|
||||
pleaseDonate: "O Misskey é um software gratuito utilizado por {host}. Para que possamos continuar o desenvolvimento, pedimos que considerem fazer doações. A sua contribuição é muito importante!"
|
||||
roles: "Cargos"
|
||||
role: "Cargo"
|
||||
noRole: "Nenhum cargo"
|
||||
normalUser: "Usuários padrão"
|
||||
undefined: "Indefinido"
|
||||
assign: "Atribuir"
|
||||
unassign: "Remover"
|
||||
color: "Cor"
|
||||
manageCustomEmojis: "Gerenciar Emojis customizados"
|
||||
@@ -953,7 +974,7 @@ thisPostMayBeAnnoying: "Esta nota pode incomodar outras pessoas."
|
||||
thisPostMayBeAnnoyingHome: "Postar na linha do tempo inicial"
|
||||
thisPostMayBeAnnoyingCancel: "Cancelar"
|
||||
thisPostMayBeAnnoyingIgnore: "Postar mesmo assim"
|
||||
collapseRenotes: "Ocultar Renotes já visualizadas"
|
||||
collapseRenotes: "Ocultar repostagens já visualizadas"
|
||||
internalServerError: "Erro interno de servidor"
|
||||
emailNotSupported: "O envio de e-mails não é suportado nesta instância"
|
||||
likeOnly: "Apenas curtidas"
|
||||
@@ -963,8 +984,20 @@ rolesAssignedToMe: "Cargos atribuídos a mim"
|
||||
unfavoriteConfirm: "Deseja realmente remover dos favoritos?"
|
||||
drivecleaner: "Limpeza do drive"
|
||||
retryAllQueuesConfirmTitle: "Gostaria de tentar novamente agora?"
|
||||
reactionsDisplaySize: "Tamanho de exibição das reações"
|
||||
reactionsList: "Reações"
|
||||
renotesList: "Repostagens"
|
||||
leftTop: "Superior esquerdo"
|
||||
rightTop: "Superior direito"
|
||||
leftBottom: "Inferior esquerdo"
|
||||
rightBottom: "Inferior direito"
|
||||
vertical: "Vertical"
|
||||
horizontal: "Exibir painel lateral inteiro"
|
||||
position: "Posição"
|
||||
serverRules: "Regras do servidor"
|
||||
continue: "Continuar"
|
||||
preservedUsernamesDescription: "Liste os nomes de usuário que deseja reservar, separando-os por quebras de linha. Os nomes de usuário especificados aqui não poderão ser utilizados durante a criação de contas. No entanto, esta restrição não se aplica quando a conta é criada por um administrador. Além disso, as contas que já existem não serão afetadas."
|
||||
archive: "Arquivo"
|
||||
channelArchiveConfirmTitle: "Deseja realmente arquivar {name}?"
|
||||
youFollowing: "Seguindo"
|
||||
preventAiLearningDescription: "Solicita-se que o conteúdo de notas e imagens enviadas não seja usado como objeto de aprendizado por sistemas externos de geração de texto ou imagens. Isso é alcançado incluindo a flag 'noai' na resposta HTML. No entanto, o cumprimento dessa solicitação depende do próprio sistema de IA, portanto, não é garantia total de prevenção de aprendizado."
|
||||
@@ -1268,6 +1301,8 @@ _menuDisplay:
|
||||
sideFull: "Exibir painel lateral inteiro"
|
||||
top: "Exibir barra superior"
|
||||
hide: "Ocultar"
|
||||
_instanceMute:
|
||||
instanceMuteDescription: "Todas as notas e repostagens do servidor configurado serão silenciados, incluindo respostas aos usuários do servidor mutado."
|
||||
_theme:
|
||||
description: "Descrição"
|
||||
alpha: "Opacidade"
|
||||
@@ -1402,6 +1437,7 @@ _notification:
|
||||
youGotMention: "{name} te mencionou"
|
||||
youGotReply: "{name} te respondeu"
|
||||
youGotQuote: "{name} te citou"
|
||||
youRenoted: "Repostagens de {name}"
|
||||
youWereFollowed: "Você tem um novo seguidor"
|
||||
youReceivedFollowRequest: "Você recebeu um pedido de seguidor"
|
||||
yourFollowRequestAccepted: "Seu pedido de seguidor foi aceito"
|
||||
@@ -1454,3 +1490,4 @@ _webhookSettings:
|
||||
_events:
|
||||
follow: "Quando seguindo um usuário"
|
||||
followed: "Quando sendo seguido"
|
||||
renote: "Quando repostado"
|
||||
|
@@ -1017,7 +1017,6 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
|
||||
retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться"
|
||||
enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей"
|
||||
enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов"
|
||||
largeNoteReactions: "Показывать большие реакции на заметки"
|
||||
noteIdOrUrl: "ID или ссылка на заметку"
|
||||
video: "Видео"
|
||||
videos: "Видео"
|
||||
@@ -1616,7 +1615,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Ваш браузер не поддерживает ключи безопасности."
|
||||
registerTOTPBeforeKey: "Чтобы зарегистрировать ключ безопасности и пароль, сначала настройте приложение аутентификации."
|
||||
securityKeyInfo: "Вы можете настроить вход с помощью аппаратного ключа безопасности, поддерживающего FIDO2, или отпечатка пальца или PIN-кода на устройстве."
|
||||
chromePasskeyNotSupported: "В настоящее время Chrome не поддерживает пароль-ключи."
|
||||
registerSecurityKey: "Зарегистрируйте ключ безопасности ・Passkey"
|
||||
securityKeyName: "Введите имя для ключа"
|
||||
tapSecurityKey: "Пожалуйста, следуйте инструкциям в вашем браузере, чтобы зарегистрировать свой ключ безопасности или пароль"
|
||||
|
@@ -45,6 +45,7 @@ pin: "ปักหมุดไปยังโปรไฟล์"
|
||||
unpin: "เลิกปักหมุดจากโปรไฟล์"
|
||||
copyContent: "คัดลอกเนื้อหา"
|
||||
copyLink: "คัดลอกลิงก์"
|
||||
copyLinkRenote: "คัดลอกลิงก์รีโน้ต"
|
||||
delete: "ลบ"
|
||||
deleteAndEdit: "ลบและแก้ไข"
|
||||
deleteAndEditConfirm: "นายแน่ใจแล้วเหรอ? ว่าต้องการลบโน้ตนี้และแก้ไข คุณอาจจะสูญเสียการโต้ตอบ, โน้ต, และการตอบกลับทั้งหมดได้นะ"
|
||||
@@ -411,6 +412,7 @@ aboutMisskey: "เกี่ยวกับ Misskey"
|
||||
administrator: "ผู้ดูแลระบบ"
|
||||
token: "โทเค็น"
|
||||
2fa: "การยืนยันตัวตนแบบสองชั้น"
|
||||
setupOf2fa: "ตั้งค่าการยืนยันตัวตนแบบสองชั้น"
|
||||
totp: "แอป Authenticator"
|
||||
totpDescription: "ใช้แอปยืนยันตัวตนเพื่อป้อนรหัสผ่านแบบใช้ครั้งเดียว"
|
||||
moderator: "ผู้ควบคุม"
|
||||
@@ -654,6 +656,7 @@ behavior: "พฤติกรรม"
|
||||
sample: "ตัวอย่าง"
|
||||
abuseReports: "รายงาน"
|
||||
reportAbuse: "รายงาน"
|
||||
reportAbuseRenote: "รายงานรีโน้ต"
|
||||
reportAbuseOf: "รายงาน {ชื่อ}"
|
||||
fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL"
|
||||
abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ"
|
||||
@@ -1020,7 +1023,6 @@ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการ
|
||||
enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
|
||||
enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล"
|
||||
showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
|
||||
largeNoteReactions: "ขยายรีแอคชั่นการแสดงผล"
|
||||
noteIdOrUrl: "โน้ต ID หรือ URL"
|
||||
video: "วีดีโอ"
|
||||
videos: "วีดีโอ"
|
||||
@@ -1693,11 +1695,11 @@ _2fa:
|
||||
step2Click: "การคลิกที่รหัส QR นี้จะช่วยให้คุณนั้นสามารถลงทะเบียน 2FA กับคีย์ความปลอดภัยหรือแอปตรวจสอบความถูกต้องของโทรศัพท์ได้"
|
||||
step3Title: "ป้อนรหัสยืนยัน"
|
||||
step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า"
|
||||
setupCompleted: "ตั้งค่าสำเร็จแล้ว"
|
||||
step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว"
|
||||
securityKeyNotSupported: "เบราว์เซอร์ของคุณไม่รองรับคีย์ความปลอดภัยนะ"
|
||||
registerTOTPBeforeKey: "กรุณาตั้งค่าแอปยืนยันตัวตนเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน"
|
||||
securityKeyInfo: "นอกจากนี้การตรวจสอบความถูกต้องด้วยลายนิ้วมือหรือ PIN แล้ว คุณยังสามารถตั้งค่าการตรวจสอบสิทธิ์ผ่านคีย์ความปลอดภัยของฮาร์ดแวร์ที่รองรับ FIDO2 เพื่อเพิ่มความปลอดภัยให้กับบัญชีของคุณ"
|
||||
chromePasskeyNotSupported: "ขณะนี้ยังไม่รองรับรหัสผ่านของ Chrome"
|
||||
registerSecurityKey: "ลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน"
|
||||
securityKeyName: "ป้อนชื่อคีย์"
|
||||
tapSecurityKey: "กรุณาทำตามเบราว์เซอร์ของคุณเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน"
|
||||
@@ -1708,6 +1710,7 @@ _2fa:
|
||||
renewTOTPConfirm: "วิธีการแบบนี้จะทําให้รหัสยืนยันจากแอพก่อนหน้าของคุณหยุดทํางานเลยนะ"
|
||||
renewTOTPOk: "ตั้งค่าคอนฟิกใหม่"
|
||||
renewTOTPCancel: "ไม่เป็นไร"
|
||||
backupCodes: "รหัสสำรองข้อมูล"
|
||||
_permissions:
|
||||
"read:account": "ดูข้อมูลบัญชีของคุณ"
|
||||
"write:account": "แก้ไขข้อมูลบัญชีของคุณ"
|
||||
@@ -1741,6 +1744,10 @@ _permissions:
|
||||
"write:gallery": "แก้ไขแกลเลอรี่ของคุณ"
|
||||
"read:gallery-likes": "ดูรายการโพสต์ในแกลเลอรีที่ชอบของคุณ"
|
||||
"write:gallery-likes": "แก้ไขรายการโพสต์ในแกลเลอรีที่ชอบของคุณ"
|
||||
"read:flash": "วิว เพลย์"
|
||||
"write:flash": "แก้ไขเพลย์"
|
||||
"read:flash-likes": "ดูรายชื่อของไลค์ เพลย์"
|
||||
"write:flash-likes": "แก้ไขรายชื่อของไลค์ เพลย์"
|
||||
_auth:
|
||||
shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน"
|
||||
shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?"
|
||||
@@ -2011,6 +2018,8 @@ _deck:
|
||||
introduction2: "คลิกที่เครื่องหมาย + ทางขวาของหน้าจอเพื่อเพิ่มคอลัมน์ใหม่ทุกครั้งที่คุณต้องการ"
|
||||
widgetsIntroduction: "กรุณาเลือก \"แก้ไขวิดเจ็ต\" ในเมนูคอลัมน์และเพิ่มวิดเจ็ต"
|
||||
useSimpleUiForNonRootPages: "แสดง UI ของ Root Page อย่างง่าย "
|
||||
usedAsMinWidthWhenFlexible: "ความกว้างขั้นต่ำนั้นจะถูกใช้งานสำหรับสิ่งนี้เมื่อเปิดใช้งานตัวเลือก \"ปรับความกว้างอัตโนมัติ\" หากเลือกเปิดใช้งานแล้ว"
|
||||
flexible: "ปรับความกว้างอัตโนมัติ"
|
||||
_columns:
|
||||
main: "หลัก"
|
||||
widgets: "วิดเจ็ต"
|
||||
|
@@ -1410,7 +1410,6 @@ _2fa:
|
||||
securityKeyNotSupported: "Trình duyệt của bạn không hỗ trợ khóa bảo mật"
|
||||
registerTOTPBeforeKey: "Vui lòng thiết lập một ứng dụng xác thực để đăng ký khóa bảo mật hoặc mật khẩu."
|
||||
securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình."
|
||||
chromePasskeyNotSupported: "Mật khẩu Chrome hiện không được hỗ trợ."
|
||||
registerSecurityKey: "Tạo khóa bảo mật hoặc mã bảo mật"
|
||||
securityKeyName: "Nhập tên khóa bảo mật"
|
||||
tapSecurityKey: "Vui lòng làm theo hướng dẫn của trình duyệt để đăng ký mã bảo mật hoặc mã khóa"
|
||||
|
@@ -45,6 +45,7 @@ pin: "置顶"
|
||||
unpin: "取消置顶"
|
||||
copyContent: "复制内容"
|
||||
copyLink: "复制链接"
|
||||
copyLinkRenote: "复制转帖链接"
|
||||
delete: "删除"
|
||||
deleteAndEdit: "删除并编辑"
|
||||
deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。"
|
||||
@@ -162,7 +163,7 @@ cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务
|
||||
flagAsBot: "这是一个机器人账号"
|
||||
flagAsBotDescription: "如果此账户由程序控制,请启用此项。启用后,此标志可以帮助其他开发人员防止机器人之间产生无限互动的行为,并让 Misskey 的内部系统将此账户识别为机器人。"
|
||||
flagAsCat: "喵!!!!!!!!!!!!"
|
||||
flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。\n开启后,会在您的头像上出现猫耳朵,并将你的帖子中的「na」替换为「nya」,日文同理。"
|
||||
flagAsCatDescription: "喵喵喵??"
|
||||
flagShowTimelineReplies: "在时间线上显示帖子的回复"
|
||||
flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。"
|
||||
autoAcceptFollowed: "自动允许来自我关注的用户对我的关注请求"
|
||||
@@ -416,6 +417,8 @@ totp: "身份验证应用"
|
||||
totpDescription: "使用认证应用输入一次性密码。"
|
||||
moderator: "监察员"
|
||||
moderation: "管理"
|
||||
moderationNote: "管理笔记"
|
||||
addModerationNote: "添加管理笔记"
|
||||
nUsersMentioned: "{n} 被提到"
|
||||
securityKeyAndPasskey: "安全密钥或 Passkey"
|
||||
securityKey: "安全密钥"
|
||||
@@ -655,6 +658,7 @@ behavior: "行为"
|
||||
sample: "示例"
|
||||
abuseReports: "举报"
|
||||
reportAbuse: "举报"
|
||||
reportAbuseRenote: "举报转帖"
|
||||
reportAbuseOf: "举报 {name}"
|
||||
fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写 URL 地址。"
|
||||
abuseReported: "内容已发送。感谢您提交信息。"
|
||||
@@ -1021,7 +1025,7 @@ retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加"
|
||||
enableChartsForRemoteUser: "生成远程用户的图表"
|
||||
enableChartsForFederatedInstances: "生成远程服务器的图表"
|
||||
showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
|
||||
largeNoteReactions: "使用大图标来显示回应"
|
||||
reactionsDisplaySize: "回应显示大小"
|
||||
noteIdOrUrl: "帖子 ID 或 URL"
|
||||
video: "视频"
|
||||
videos: "视频"
|
||||
@@ -1103,6 +1107,7 @@ forYou: "您的"
|
||||
currentAnnouncements: "现在的公告"
|
||||
pastAnnouncements: "过去的公告"
|
||||
youHaveUnreadAnnouncements: "您有未读的公告"
|
||||
useSecurityKey: "请根据浏览器或设备的提示,使用安全密钥或通行密钥。"
|
||||
_announcement:
|
||||
forExistingUsers: "仅限现有用户"
|
||||
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
|
||||
@@ -1383,6 +1388,9 @@ _achievements:
|
||||
title: "Brain Diver"
|
||||
description: "发布了包含 Brain Diver 链接的帖子"
|
||||
flavor: "Misskey-Misskey La-Tu-Ma"
|
||||
_smashTestNotificationButton:
|
||||
title: "过度测试"
|
||||
description: "短时间内连续测试通知"
|
||||
_role:
|
||||
new: "创建角色"
|
||||
edit: "编辑角色"
|
||||
@@ -1705,7 +1713,6 @@ _2fa:
|
||||
securityKeyNotSupported: "您的浏览器不支持安全密钥。"
|
||||
registerTOTPBeforeKey: "要注册安全密钥或 Passkey,请先设置验证器应用程序。"
|
||||
securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 码以及 Passkey 等。"
|
||||
chromePasskeyNotSupported: "目前不支持 Chrome 的 Passkey。"
|
||||
registerSecurityKey: "注册安全密钥或 Passkey"
|
||||
securityKeyName: "输入密钥名称"
|
||||
tapSecurityKey: "请按照浏览器说明操作来注册安全密钥或 Passkey。"
|
||||
@@ -1993,6 +2000,10 @@ _notification:
|
||||
unreadAntennaNote: "天线 {name}"
|
||||
emptyPushNotificationMessage: "推送通知已更新"
|
||||
achievementEarned: "获得成就"
|
||||
testNotification: "测试通知"
|
||||
checkNotificationBehavior: "检查通知显示"
|
||||
sendTestNotification: "发送测试通知"
|
||||
notificationWillBeDisplayedLikeThis: "通知将会这样表示"
|
||||
_types:
|
||||
all: "全部"
|
||||
follow: "关注中"
|
||||
|
@@ -45,6 +45,7 @@ pin: "置頂"
|
||||
unpin: "取消置頂"
|
||||
copyContent: "複製內容"
|
||||
copyLink: "複製連結"
|
||||
copyLinkRenote: "複製轉貼連結"
|
||||
delete: "刪除"
|
||||
deleteAndEdit: "刪除並編輯"
|
||||
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也將會消失。"
|
||||
@@ -416,6 +417,8 @@ totp: "驗證應用程式"
|
||||
totpDescription: "以驗證應用程式輸入一次性密碼"
|
||||
moderator: "審查員"
|
||||
moderation: "審查"
|
||||
moderationNote: "管理筆記"
|
||||
addModerationNote: "新增管理筆記"
|
||||
nUsersMentioned: "被提及到 {n} 次"
|
||||
securityKeyAndPasskey: "安全金鑰、Passkey"
|
||||
securityKey: "安全金鑰"
|
||||
@@ -655,6 +658,7 @@ behavior: "行為"
|
||||
sample: "範例"
|
||||
abuseReports: "檢舉"
|
||||
reportAbuse: "檢舉"
|
||||
reportAbuseRenote: "檢舉轉貼"
|
||||
reportAbuseOf: "檢舉{name}"
|
||||
fillAbuseReportDescription: "請填寫檢舉的詳細理由。如有需要,請附上相關 URL。"
|
||||
abuseReported: "檢舉完成。感謝您的報告。"
|
||||
@@ -1021,7 +1025,7 @@ retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
|
||||
enableChartsForRemoteUser: "生成遠端使用者的圖表"
|
||||
enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
|
||||
showClipButtonInNoteFooter: "新增摘錄至貼文"
|
||||
largeNoteReactions: "放大顯示貼文反應"
|
||||
reactionsDisplaySize: "表情回應的顯示尺寸"
|
||||
noteIdOrUrl: "貼文ID或URL"
|
||||
video: "影片"
|
||||
videos: "影片"
|
||||
@@ -1103,6 +1107,7 @@ forYou: "給您"
|
||||
currentAnnouncements: "最新公告"
|
||||
pastAnnouncements: "歷史公告"
|
||||
youHaveUnreadAnnouncements: "有未讀的公告。"
|
||||
useSecurityKey: "請按照瀏覽器或設備上的說明使用安全金鑰或 Passkey。"
|
||||
_announcement:
|
||||
forExistingUsers: "僅限既有的使用者"
|
||||
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
|
||||
@@ -1383,6 +1388,9 @@ _achievements:
|
||||
title: "Brain Driver"
|
||||
description: "發佈一篇含歌曲《Brain Driver》連結的貼文"
|
||||
flavor: "Misskey-Misskey La-Tu-Ma"
|
||||
_smashTestNotificationButton:
|
||||
title: "過度測試"
|
||||
description: "極短時間內連續測試通知"
|
||||
_role:
|
||||
new: "建立角色"
|
||||
edit: "編輯角色"
|
||||
@@ -1705,7 +1713,6 @@ _2fa:
|
||||
securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。"
|
||||
registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。"
|
||||
securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全鎖、終端設備的指紋認證,或者 PIN 碼來登入。"
|
||||
chromePasskeyNotSupported: "目前不支援 Chrome 的 Passkey。"
|
||||
registerSecurityKey: "註冊安全金鑰或 Passkey"
|
||||
securityKeyName: "輸入金鑰名稱"
|
||||
tapSecurityKey: "按照瀏覽器的說明註冊安全金鑰或 Passkey。"
|
||||
@@ -1993,6 +2000,10 @@ _notification:
|
||||
unreadAntennaNote: "天線 {name}"
|
||||
emptyPushNotificationMessage: "推送通知已更新"
|
||||
achievementEarned: "獲得成就"
|
||||
testNotification: "通知測試"
|
||||
checkNotificationBehavior: "確認通知的顯示行為"
|
||||
sendTestNotification: "發送測試通知"
|
||||
notificationWillBeDisplayedLikeThis: "通知會以這樣的方式顯示"
|
||||
_types:
|
||||
all: "全部 "
|
||||
follow: "追隨中"
|
||||
|
31
package.json
31
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2023.9.0-beta.3",
|
||||
"version": "2023.9.0-beta.6",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.1",
|
||||
"packageManager": "pnpm@8.7.5",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
@@ -15,15 +15,15 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-pre": "node ./scripts/build-pre.js",
|
||||
"build": "pnpm build-pre && pnpm -r build && pnpm gulp",
|
||||
"build-assets": "node ./scripts/build-assets.mjs",
|
||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/index.js",
|
||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
|
||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"init": "pnpm migrate",
|
||||
"migrate": "cd packages/backend && pnpm migrate",
|
||||
"check:connect": "cd packages/backend && pnpm check:connect",
|
||||
"migrateandstart": "pnpm migrate && pnpm start",
|
||||
"gulp": "pnpm exec gulp build",
|
||||
"watch": "pnpm dev",
|
||||
"dev": "node ./scripts/dev.mjs",
|
||||
"lint": "pnpm -r lint",
|
||||
@@ -34,7 +34,6 @@
|
||||
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
||||
"test": "pnpm -r test",
|
||||
"test-and-coverage": "pnpm -r test-and-coverage",
|
||||
"format": "pnpm exec gulp format",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"clean-all": "node ./scripts/clean-all.js",
|
||||
"cleanall": "pnpm clean-all"
|
||||
@@ -45,22 +44,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"execa": "8.0.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-replace": "1.1.4",
|
||||
"gulp-terser": "2.1.0",
|
||||
"cssnano": "6.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.29",
|
||||
"terser": "5.19.4",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/gulp": "4.0.13",
|
||||
"@types/gulp-rename": "2.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.6.0",
|
||||
"@typescript-eslint/parser": "6.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.0",
|
||||
"@typescript-eslint/parser": "6.7.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.1.0",
|
||||
"eslint": "8.48.0",
|
||||
"cypress": "13.2.0",
|
||||
"eslint": "8.49.0",
|
||||
"start-server-and-test": "2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"decoratorMetadata": true
|
||||
},
|
||||
"experimental": {
|
||||
"keepImportAssertions": true
|
||||
"keepImportAttributes": true
|
||||
},
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
|
49
packages/backend/migration/1691959191872-passkey-support.js
Normal file
49
packages/backend/migration/1691959191872-passkey-support.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class PasskeySupport1691959191872 {
|
||||
name = 'PasskeySupport1691959191872'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" ADD "counter" bigint NOT NULL DEFAULT '0'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialDeviceType" character varying(32)`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" ADD "credentialBackedUp" boolean`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" ADD "transports" character varying(32) array`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'The public key of the UserSecurityKey, hex-encoded.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'Timestamp of the last time the UserSecurityKey was used.'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" SET DEFAULT now()`);
|
||||
await queryRunner.query(`UPDATE "user_security_key" SET "id" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("id", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', ''), "publicKey" = REPLACE(REPLACE(REPLACE(REPLACE(ENCODE(DECODE("publicKey", 'hex'), 'base64'), E'\\n', ''), '+', '-'), '/', '_'), '=', '')`);
|
||||
await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`);
|
||||
await queryRunner.query(`DROP TABLE "attestation_challenge"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `);
|
||||
await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS 'Hex-encoded sha256 hash of the challenge.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS 'The date challenge was created for expiry purposes.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.'`);
|
||||
await queryRunner.query(`UPDATE "user_security_key" SET "id" = ENCODE(DECODE(REPLACE(REPLACE("id" || CASE WHEN LENGTH("id") % 4 = 2 THEN '==' WHEN LENGTH("id") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex'), "publicKey" = ENCODE(DECODE(REPLACE(REPLACE("publicKey" || CASE WHEN LENGTH("publicKey") % 4 = 2 THEN '==' WHEN LENGTH("publicKey") % 4 = 3 THEN '=' ELSE '' END, '-', '+'), '_', '/'), 'base64'), 'hex')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" ALTER COLUMN "lastUsed" DROP DEFAULT`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'The date of the last time the UserSecurityKey was successfully validated.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'Variable-length public key used to verify attestations (hex-encoded).'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."transports" IS 'The type of the credential returned by the browser'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "transports"`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialBackedUp" IS 'Whether or not the credential has been backed up'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialBackedUp"`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."credentialDeviceType" IS 'The type of Backup Eligibility in authenticator data'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "credentialDeviceType"`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."counter" IS 'The number of times the UserSecurityKey was validated.'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_security_key" DROP COLUMN "counter"`);
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
export class ServerIconsAndManifest1694850832075 {
|
||||
name = 'ServerIconsAndManifest1694850832075'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "app192IconUrl" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "app512IconUrl" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "manifestJsonOverride" character varying(8192) NOT NULL DEFAULT '{}'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "manifestJsonOverride"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app512IconUrl"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "app192IconUrl"`);
|
||||
}
|
||||
}
|
@@ -56,12 +56,12 @@
|
||||
"utf-8-validate": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.400.0",
|
||||
"@aws-sdk/lib-storage": "3.400.0",
|
||||
"@aws-sdk/node-http-handler": "3.374.0",
|
||||
"@bull-board/api": "5.8.1",
|
||||
"@bull-board/fastify": "5.8.1",
|
||||
"@bull-board/ui": "5.8.1",
|
||||
"@aws-sdk/client-s3": "3.412.0",
|
||||
"@aws-sdk/lib-storage": "3.412.0",
|
||||
"@smithy/node-http-handler": "2.1.3",
|
||||
"@bull-board/api": "5.8.3",
|
||||
"@bull-board/fastify": "5.8.3",
|
||||
"@bull-board/ui": "5.8.3",
|
||||
"@discordapp/twemoji": "14.1.2",
|
||||
"@fastify/accepts": "4.2.0",
|
||||
"@fastify/cookie": "9.0.4",
|
||||
@@ -69,15 +69,16 @@
|
||||
"@fastify/express": "2.3.0",
|
||||
"@fastify/http-proxy": "9.2.1",
|
||||
"@fastify/multipart": "7.7.3",
|
||||
"@fastify/static": "6.11.0",
|
||||
"@fastify/view": "8.0.0",
|
||||
"@nestjs/common": "10.2.4",
|
||||
"@nestjs/core": "10.2.4",
|
||||
"@nestjs/testing": "10.2.4",
|
||||
"@fastify/static": "6.11.1",
|
||||
"@fastify/view": "8.1.0",
|
||||
"@nestjs/common": "10.2.5",
|
||||
"@nestjs/core": "10.2.5",
|
||||
"@nestjs/testing": "10.2.5",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@simplewebauthn/server": "8.1.1",
|
||||
"@sinonjs/fake-timers": "11.1.0",
|
||||
"@swc/cli": "0.1.62",
|
||||
"@swc/core": "1.3.82",
|
||||
"@swc/core": "1.3.84",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "6.0.1",
|
||||
@@ -85,7 +86,7 @@
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "4.8.0",
|
||||
"bullmq": "4.10.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.1",
|
||||
"chalk": "5.3.0",
|
||||
@@ -96,7 +97,7 @@
|
||||
"content-disposition": "0.5.4",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "4.22.2",
|
||||
"fastify": "4.23.2",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "18.5.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
@@ -112,17 +113,18 @@
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "22.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.2.1",
|
||||
"jsonld": "8.3.1",
|
||||
"jsrsasign": "10.8.6",
|
||||
"meilisearch": "0.34.1",
|
||||
"meilisearch": "0.34.2",
|
||||
"mfm-js": "0.23.3",
|
||||
"microformats-parser": "1.4.1",
|
||||
"microformats-parser": "1.5.2",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"nanoid": "5.0.1",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.4",
|
||||
"nodemailer": "6.9.5",
|
||||
"nsfwjs": "2.4.2",
|
||||
"oauth": "0.10.0",
|
||||
"oauth2orize": "1.11.1",
|
||||
@@ -153,7 +155,7 @@
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"summaly": "github:misskey-dev/summaly",
|
||||
"systeminformation": "5.21.4",
|
||||
"systeminformation": "5.21.5",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.7",
|
||||
@@ -163,12 +165,13 @@
|
||||
"typescript": "5.2.2",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.5",
|
||||
"ws": "8.13.0",
|
||||
"web-push": "3.6.6",
|
||||
"ws": "8.14.1",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.6.4",
|
||||
"@jest/globals": "29.7.0",
|
||||
"@simplewebauthn/typescript-types": "8.0.0",
|
||||
"@swc/jest": "0.2.29",
|
||||
"@types/accepts": "1.3.5",
|
||||
"@types/archiver": "5.3.2",
|
||||
@@ -177,7 +180,7 @@
|
||||
"@types/cbor": "6.0.0",
|
||||
"@types/color-convert": "2.0.1",
|
||||
"@types/content-disposition": "0.5.6",
|
||||
"@types/fluent-ffmpeg": "2.1.21",
|
||||
"@types/fluent-ffmpeg": "2.1.22",
|
||||
"@types/http-link-header": "1.0.3",
|
||||
"@types/jest": "29.5.4",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
@@ -186,9 +189,9 @@
|
||||
"@types/jsrsasign": "10.5.8",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/ms": "0.7.31",
|
||||
"@types/node": "20.5.9",
|
||||
"@types/node": "20.6.0",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.9",
|
||||
"@types/nodemailer": "6.4.10",
|
||||
"@types/oauth": "0.9.2",
|
||||
"@types/oauth2orize": "1.11.1",
|
||||
"@types/oauth2orize-pkce": "0.1.0",
|
||||
@@ -200,24 +203,24 @@
|
||||
"@types/ratelimiter": "3.4.4",
|
||||
"@types/rename": "1.0.4",
|
||||
"@types/sanitize-html": "2.9.0",
|
||||
"@types/semver": "7.5.1",
|
||||
"@types/semver": "7.5.2",
|
||||
"@types/sharp": "0.32.0",
|
||||
"@types/simple-oauth2": "5.0.4",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/tinycolor2": "1.4.4",
|
||||
"@types/tmp": "0.2.4",
|
||||
"@types/vary": "1.1.0",
|
||||
"@types/web-push": "3.6.0",
|
||||
"@types/ws": "8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "6.6.0",
|
||||
"@typescript-eslint/parser": "6.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.0",
|
||||
"@typescript-eslint/parser": "6.7.0",
|
||||
"aws-sdk-client-mock": "3.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.48.0",
|
||||
"eslint": "8.49.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"execa": "8.0.1",
|
||||
"jest": "29.6.4",
|
||||
"jest-mock": "29.6.3",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"simple-oauth2": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ import { ChartManagementService } from '@/core/chart/ChartManagementService.js';
|
||||
import { QueueProcessorService } from '@/queue/QueueProcessorService.js';
|
||||
import { NestLogger } from '@/NestLogger.js';
|
||||
import { QueueProcessorModule } from '@/queue/QueueProcessorModule.js';
|
||||
import { JanitorService } from '@/daemons/JanitorService.js';
|
||||
import { QueueStatsService } from '@/daemons/QueueStatsService.js';
|
||||
import { ServerStatsService } from '@/daemons/ServerStatsService.js';
|
||||
import { ServerService } from '@/server/ServerService.js';
|
||||
@@ -25,7 +24,6 @@ export async function server() {
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
app.get(ChartManagementService).start();
|
||||
app.get(JanitorService).start();
|
||||
app.get(QueueStatsService).start();
|
||||
app.get(ServerStatsService).start();
|
||||
}
|
||||
|
@@ -85,9 +85,11 @@ type Source = {
|
||||
videoThumbnailGenerator?: string;
|
||||
|
||||
signToActivityPubGet?: boolean;
|
||||
outboxNotesFetchLimit?: number;
|
||||
|
||||
perChannelMaxNoteCacheCount?: number;
|
||||
perUserNotificationsMaxCount?: number;
|
||||
deactivateAntennaThreshold?: number;
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
@@ -161,6 +163,7 @@ export type Config = {
|
||||
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
||||
perChannelMaxNoteCacheCount: number;
|
||||
perUserNotificationsMaxCount: number;
|
||||
deactivateAntennaThreshold: number;
|
||||
};
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
@@ -252,6 +255,7 @@ export function loadConfig(): Config {
|
||||
clientManifestExists: clientManifestExists,
|
||||
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
|
||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 300,
|
||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
|
||||
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
||||
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
@@ -85,6 +85,7 @@ export const ACHIEVEMENT_TYPES = [
|
||||
'setNameToSyuilo',
|
||||
'cookieClicked',
|
||||
'brainDiver',
|
||||
'smashTestNotificationButton',
|
||||
] as const;
|
||||
|
||||
@Injectable()
|
||||
|
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/index.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@@ -12,7 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/entities/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -43,7 +43,7 @@ import { RelayService } from './RelayService.js';
|
||||
import { RoleService } from './RoleService.js';
|
||||
import { S3Service } from './S3Service.js';
|
||||
import { SignupService } from './SignupService.js';
|
||||
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
|
||||
import { WebAuthnService } from './WebAuthnService.js';
|
||||
import { UserBlockingService } from './UserBlockingService.js';
|
||||
import { CacheService } from './CacheService.js';
|
||||
import { UserFollowingService } from './UserFollowingService.js';
|
||||
@@ -168,7 +168,7 @@ const $RelayService: Provider = { provide: 'RelayService', useExisting: RelaySer
|
||||
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
|
||||
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
||||
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
|
||||
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
|
||||
const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService };
|
||||
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
|
||||
const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
|
||||
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
|
||||
@@ -296,7 +296,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
RoleService,
|
||||
S3Service,
|
||||
SignupService,
|
||||
TwoFactorAuthenticationService,
|
||||
WebAuthnService,
|
||||
UserBlockingService,
|
||||
CacheService,
|
||||
UserFollowingService,
|
||||
@@ -417,7 +417,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$RoleService,
|
||||
$S3Service,
|
||||
$SignupService,
|
||||
$TwoFactorAuthenticationService,
|
||||
$WebAuthnService,
|
||||
$UserBlockingService,
|
||||
$CacheService,
|
||||
$UserFollowingService,
|
||||
@@ -539,7 +539,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
RoleService,
|
||||
S3Service,
|
||||
SignupService,
|
||||
TwoFactorAuthenticationService,
|
||||
WebAuthnService,
|
||||
UserBlockingService,
|
||||
CacheService,
|
||||
UserFollowingService,
|
||||
@@ -659,7 +659,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$RoleService,
|
||||
$S3Service,
|
||||
$SignupService,
|
||||
$TwoFactorAuthenticationService,
|
||||
$WebAuthnService,
|
||||
$UserBlockingService,
|
||||
$CacheService,
|
||||
$UserFollowingService,
|
||||
|
@@ -12,7 +12,7 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { MiDriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { MiEmoji } from '@/models/entities/Emoji.js';
|
||||
import type { EmojisRepository, MiRole } from '@/models/index.js';
|
||||
import type { EmojisRepository, MiRole } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { UserSuspendService } from '@/core/UserSuspendService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -11,7 +11,7 @@ import { sharpBmp } from 'sharp-read-bmp';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js';
|
||||
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import Logger from '@/logger.js';
|
||||
import type { MiRemoteUser, MiUser } from '@/models/entities/User.js';
|
||||
|
@@ -10,7 +10,7 @@ import { MetaService } from '@/core/MetaService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import type { UserProfilesRepository } from '@/models/index.js';
|
||||
import type { UserProfilesRepository } from '@/models/_.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { InstancesRepository } from '@/models/index.js';
|
||||
import type { InstancesRepository } from '@/models/_.js';
|
||||
import type { MiInstance } from '@/models/entities/Instance.js';
|
||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@@ -25,7 +25,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiRole } from '@/models/index.js';
|
||||
import { MiRole } from '@/models/_.js';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalEventService {
|
||||
|
@@ -9,7 +9,7 @@ import type { MiUser } from '@/models/entities/User.js';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MiHashtag } from '@/models/entities/Hashtag.js';
|
||||
import type { HashtagsRepository } from '@/models/index.js';
|
||||
import type { HashtagsRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
|
@@ -8,6 +8,7 @@ import { ulid } from 'ulid';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { genAid, parseAid } from '@/misc/id/aid.js';
|
||||
import { genAidx, parseAidx } from '@/misc/id/aidx.js';
|
||||
import { genMeid, parseMeid } from '@/misc/id/meid.js';
|
||||
import { genMeidg, parseMeidg } from '@/misc/id/meidg.js';
|
||||
import { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
|
||||
@@ -31,6 +32,7 @@ export class IdService {
|
||||
|
||||
switch (this.method) {
|
||||
case 'aid': return genAid(date);
|
||||
case 'aidx': return genAidx(date);
|
||||
case 'meid': return genMeid(date);
|
||||
case 'meidg': return genMeidg(date);
|
||||
case 'ulid': return ulid(date.getTime());
|
||||
@@ -43,6 +45,7 @@ export class IdService {
|
||||
public parse(id: string): { date: Date; } {
|
||||
switch (this.method) {
|
||||
case 'aid': return parseAid(id);
|
||||
case 'aidx': return parseAidx(id);
|
||||
case 'objectid': return parseObjectId(id);
|
||||
case 'meid': return parseMeid(id);
|
||||
case 'meidg': return parseMeidg(id);
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull } from 'typeorm';
|
||||
import type { MiLocalUser } from '@/models/entities/User.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { MemorySingleCache } from '@/misc/cache.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { ModerationLogsRepository } from '@/models/index.js';
|
||||
import type { ModerationLogsRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
|
||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||
import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
|
||||
import { MiNote } from '@/models/entities/Note.js';
|
||||
import type { ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { MiDriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { MiApp } from '@/models/entities/App.js';
|
||||
import { concat } from '@/misc/prelude/array.js';
|
||||
|
@@ -7,7 +7,7 @@ import { Brackets, In } from 'typeorm';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
|
||||
import type { MiNote, IMentionedRemoteUsers } from '@/models/entities/Note.js';
|
||||
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/_.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
|
@@ -12,7 +12,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/index.js';
|
||||
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
|
@@ -8,7 +8,7 @@ import * as Redis from 'ioredis';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import type { MiNotification } from '@/models/entities/Notification.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, MiUser } from '@/models/index.js';
|
||||
import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, MiUser } from '@/models/_.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import type { MiLocalUser } from '@/models/entities/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
@@ -10,7 +10,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
||||
import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/index.js';
|
||||
import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RedisKVCache } from '@/misc/cache.js';
|
||||
|
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets, ObjectLiteral } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/index.js';
|
||||
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { SelectQueryBuilder } from 'typeorm';
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js';
|
||||
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type { MiRemoteUser, MiUser } from '@/models/entities/User.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull } from 'typeorm';
|
||||
import type { MiLocalUser, MiUser } from '@/models/entities/User.js';
|
||||
import type { RelaysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { RelaysRepository, UsersRepository } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { MemorySingleCache } from '@/misc/cache.js';
|
||||
import type { MiRelay } from '@/models/entities/Relay.js';
|
||||
|
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import chalk from 'chalk';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import type { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { In } from 'typeorm';
|
||||
import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -9,7 +9,7 @@ import * as https from 'node:https';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { Upload } from '@aws-sdk/lib-storage';
|
||||
import { NodeHttpHandler, NodeHttpHandlerOptions } from '@aws-sdk/node-http-handler';
|
||||
import { NodeHttpHandler, NodeHttpHandlerOptions } from '@smithy/node-http-handler';
|
||||
import type { MiMeta } from '@/models/entities/Meta.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@@ -9,8 +9,8 @@ import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiNote } from '@/models/entities/Note.js';
|
||||
import { MiUser } from '@/models/index.js';
|
||||
import type { NotesRepository } from '@/models/index.js';
|
||||
import { MiUser } from '@/models/_.js';
|
||||
import type { NotesRepository } from '@/models/_.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { DataSource, IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { MiUser } from '@/models/entities/User.js';
|
||||
import { MiUserProfile } from '@/models/entities/UserProfile.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@@ -1,446 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as crypto from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as jsrsasign from 'jsrsasign';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
const ECC_PRELUDE = Buffer.from([0x04]);
|
||||
const NULL_BYTE = Buffer.from([0]);
|
||||
const PEM_PRELUDE = Buffer.from(
|
||||
'3059301306072a8648ce3d020106082a8648ce3d030107034200',
|
||||
'hex',
|
||||
);
|
||||
|
||||
// Android Safetynet attestations are signed with this cert:
|
||||
const GSR2 = `-----BEGIN CERTIFICATE-----
|
||||
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
|
||||
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
|
||||
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
|
||||
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
|
||||
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
|
||||
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
|
||||
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
|
||||
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
|
||||
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
|
||||
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
|
||||
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
|
||||
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
|
||||
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
|
||||
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
|
||||
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
|
||||
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
|
||||
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
|
||||
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
|
||||
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
|
||||
-----END CERTIFICATE-----\n`;
|
||||
|
||||
function base64URLDecode(source: string) {
|
||||
return Buffer.from(source.replace(/\-/g, '+').replace(/_/g, '/'), 'base64');
|
||||
}
|
||||
|
||||
function getCertSubject(certificate: string) {
|
||||
const subjectCert = new jsrsasign.X509();
|
||||
subjectCert.readCertPEM(certificate);
|
||||
|
||||
const subjectString = subjectCert.getSubjectString();
|
||||
const subjectFields = subjectString.slice(1).split('/');
|
||||
|
||||
const fields = {} as Record<string, string>;
|
||||
for (const field of subjectFields) {
|
||||
const eqIndex = field.indexOf('=');
|
||||
fields[field.substring(0, eqIndex)] = field.substring(eqIndex + 1);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
function verifyCertificateChain(certificates: string[]) {
|
||||
let valid = true;
|
||||
|
||||
for (let i = 0; i < certificates.length; i++) {
|
||||
const Cert = certificates[i];
|
||||
const certificate = new jsrsasign.X509();
|
||||
certificate.readCertPEM(Cert);
|
||||
|
||||
const CACert = i + 1 >= certificates.length ? Cert : certificates[i + 1];
|
||||
|
||||
const certStruct = jsrsasign.ASN1HEX.getTLVbyList(certificate.hex!, 0, [0]);
|
||||
if (certStruct == null) throw new Error('certStruct is null');
|
||||
|
||||
const algorithm = certificate.getSignatureAlgorithmField();
|
||||
const signatureHex = certificate.getSignatureValueHex();
|
||||
|
||||
// Verify against CA
|
||||
const Signature = new jsrsasign.KJUR.crypto.Signature({ alg: algorithm });
|
||||
Signature.init(CACert);
|
||||
Signature.updateHex(certStruct);
|
||||
valid = valid && !!Signature.verify(signatureHex); // true if CA signed the certificate
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') {
|
||||
if (pemBuffer.length === 65 && pemBuffer[0] === 0x04) {
|
||||
pemBuffer = Buffer.concat([PEM_PRELUDE, pemBuffer], 91);
|
||||
type = 'PUBLIC KEY';
|
||||
}
|
||||
const cert = pemBuffer.toString('base64');
|
||||
|
||||
const keyParts = [];
|
||||
const max = Math.ceil(cert.length / 64);
|
||||
let start = 0;
|
||||
for (let i = 0; i < max; i++) {
|
||||
keyParts.push(cert.substring(start, start + 64));
|
||||
start += 64;
|
||||
}
|
||||
|
||||
return (
|
||||
`-----BEGIN ${type}-----\n` +
|
||||
keyParts.join('\n') +
|
||||
`\n-----END ${type}-----\n`
|
||||
);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TwoFactorAuthenticationService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public hash(data: Buffer) {
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
.update(data)
|
||||
.digest();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public verifySignin({
|
||||
publicKey,
|
||||
authenticatorData,
|
||||
clientDataJSON,
|
||||
clientData,
|
||||
signature,
|
||||
challenge,
|
||||
}: {
|
||||
publicKey: Buffer,
|
||||
authenticatorData: Buffer,
|
||||
clientDataJSON: Buffer,
|
||||
clientData: any,
|
||||
signature: Buffer,
|
||||
challenge: string
|
||||
}) {
|
||||
if (clientData.type !== 'webauthn.get') {
|
||||
throw new Error('type is not webauthn.get');
|
||||
}
|
||||
|
||||
if (this.hash(clientData.challenge).toString('hex') !== challenge) {
|
||||
throw new Error('challenge mismatch');
|
||||
}
|
||||
if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
|
||||
throw new Error('origin mismatch');
|
||||
}
|
||||
|
||||
const verificationData = Buffer.concat(
|
||||
[authenticatorData, this.hash(clientDataJSON)],
|
||||
32 + authenticatorData.length,
|
||||
);
|
||||
|
||||
return crypto
|
||||
.createVerify('SHA256')
|
||||
.update(verificationData)
|
||||
.verify(PEMString(publicKey), signature);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getProcedures() {
|
||||
return {
|
||||
none: {
|
||||
verify({ publicKey }: { publicKey: Map<number, Buffer> }) {
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyU2F = Buffer.concat(
|
||||
[ECC_PRELUDE, negTwo, negThree],
|
||||
1 + 32 + 32,
|
||||
);
|
||||
|
||||
return {
|
||||
publicKey: publicKeyU2F,
|
||||
valid: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
'android-key': {
|
||||
verify({
|
||||
attStmt,
|
||||
authenticatorData,
|
||||
clientDataHash,
|
||||
publicKey,
|
||||
rpIdHash,
|
||||
credentialId,
|
||||
}: {
|
||||
attStmt: any,
|
||||
authenticatorData: Buffer,
|
||||
clientDataHash: Buffer,
|
||||
publicKey: Map<number, any>;
|
||||
rpIdHash: Buffer,
|
||||
credentialId: Buffer,
|
||||
}) {
|
||||
if (attStmt.alg !== -7) {
|
||||
throw new Error('alg mismatch');
|
||||
}
|
||||
|
||||
const verificationData = Buffer.concat([
|
||||
authenticatorData,
|
||||
clientDataHash,
|
||||
]);
|
||||
|
||||
const attCert: Buffer = attStmt.x5c[0];
|
||||
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyData = Buffer.concat(
|
||||
[ECC_PRELUDE, negTwo, negThree],
|
||||
1 + 32 + 32,
|
||||
);
|
||||
|
||||
if (!attCert.equals(publicKeyData)) {
|
||||
throw new Error('public key mismatch');
|
||||
}
|
||||
|
||||
const isValid = crypto
|
||||
.createVerify('SHA256')
|
||||
.update(verificationData)
|
||||
.verify(PEMString(attCert), attStmt.sig);
|
||||
|
||||
// TODO: Check 'attestationChallenge' field in extension of cert matches hash(clientDataJSON)
|
||||
|
||||
return {
|
||||
valid: isValid,
|
||||
publicKey: publicKeyData,
|
||||
};
|
||||
},
|
||||
},
|
||||
// what a stupid attestation
|
||||
'android-safetynet': {
|
||||
verify: ({
|
||||
attStmt,
|
||||
authenticatorData,
|
||||
clientDataHash,
|
||||
publicKey,
|
||||
rpIdHash,
|
||||
credentialId,
|
||||
}: {
|
||||
attStmt: any,
|
||||
authenticatorData: Buffer,
|
||||
clientDataHash: Buffer,
|
||||
publicKey: Map<number, any>;
|
||||
rpIdHash: Buffer,
|
||||
credentialId: Buffer,
|
||||
}) => {
|
||||
const verificationData = this.hash(
|
||||
Buffer.concat([authenticatorData, clientDataHash]),
|
||||
);
|
||||
|
||||
const jwsParts = attStmt.response.toString('utf-8').split('.');
|
||||
|
||||
const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8'));
|
||||
const response = JSON.parse(
|
||||
base64URLDecode(jwsParts[1]).toString('utf-8'),
|
||||
);
|
||||
const signature = jwsParts[2];
|
||||
|
||||
if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) {
|
||||
throw new Error('invalid nonce');
|
||||
}
|
||||
|
||||
const certificateChain = header.x5c
|
||||
.map((key: any) => PEMString(key))
|
||||
.concat([GSR2]);
|
||||
|
||||
if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
|
||||
throw new Error('invalid common name');
|
||||
}
|
||||
|
||||
if (!verifyCertificateChain(certificateChain)) {
|
||||
throw new Error('Invalid certificate chain!');
|
||||
}
|
||||
|
||||
const signatureBase = Buffer.from(
|
||||
jwsParts[0] + '.' + jwsParts[1],
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const valid = crypto
|
||||
.createVerify('sha256')
|
||||
.update(signatureBase)
|
||||
.verify(certificateChain[0], base64URLDecode(signature));
|
||||
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyData = Buffer.concat(
|
||||
[ECC_PRELUDE, negTwo, negThree],
|
||||
1 + 32 + 32,
|
||||
);
|
||||
return {
|
||||
valid,
|
||||
publicKey: publicKeyData,
|
||||
};
|
||||
},
|
||||
},
|
||||
packed: {
|
||||
verify({
|
||||
attStmt,
|
||||
authenticatorData,
|
||||
clientDataHash,
|
||||
publicKey,
|
||||
rpIdHash,
|
||||
credentialId,
|
||||
}: {
|
||||
attStmt: any,
|
||||
authenticatorData: Buffer,
|
||||
clientDataHash: Buffer,
|
||||
publicKey: Map<number, any>;
|
||||
rpIdHash: Buffer,
|
||||
credentialId: Buffer,
|
||||
}) {
|
||||
const verificationData = Buffer.concat([
|
||||
authenticatorData,
|
||||
clientDataHash,
|
||||
]);
|
||||
|
||||
if (attStmt.x5c) {
|
||||
const attCert = attStmt.x5c[0];
|
||||
|
||||
const validSignature = crypto
|
||||
.createVerify('SHA256')
|
||||
.update(verificationData)
|
||||
.verify(PEMString(attCert), attStmt.sig);
|
||||
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyData = Buffer.concat(
|
||||
[ECC_PRELUDE, negTwo, negThree],
|
||||
1 + 32 + 32,
|
||||
);
|
||||
|
||||
return {
|
||||
valid: validSignature,
|
||||
publicKey: publicKeyData,
|
||||
};
|
||||
} else if (attStmt.ecdaaKeyId) {
|
||||
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
|
||||
throw new Error('ECDAA-Verify is not supported');
|
||||
} else {
|
||||
if (attStmt.alg !== -7) throw new Error('alg mismatch');
|
||||
|
||||
throw new Error('self attestation is not supported');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
'fido-u2f': {
|
||||
verify({
|
||||
attStmt,
|
||||
authenticatorData,
|
||||
clientDataHash,
|
||||
publicKey,
|
||||
rpIdHash,
|
||||
credentialId,
|
||||
}: {
|
||||
attStmt: any,
|
||||
authenticatorData: Buffer,
|
||||
clientDataHash: Buffer,
|
||||
publicKey: Map<number, any>,
|
||||
rpIdHash: Buffer,
|
||||
credentialId: Buffer
|
||||
}) {
|
||||
const x5c: Buffer[] = attStmt.x5c;
|
||||
if (x5c.length !== 1) {
|
||||
throw new Error('x5c length does not match expectation');
|
||||
}
|
||||
|
||||
const attCert = x5c[0];
|
||||
|
||||
// TODO: make sure attCert is an Elliptic Curve (EC) public key over the P-256 curve
|
||||
|
||||
const negTwo: Buffer = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree: Buffer = publicKey.get(-3);
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
const publicKeyU2F = Buffer.concat(
|
||||
[ECC_PRELUDE, negTwo, negThree],
|
||||
1 + 32 + 32,
|
||||
);
|
||||
|
||||
const verificationData = Buffer.concat([
|
||||
NULL_BYTE,
|
||||
rpIdHash,
|
||||
clientDataHash,
|
||||
credentialId,
|
||||
publicKeyU2F,
|
||||
]);
|
||||
|
||||
const validSignature = crypto
|
||||
.createVerify('SHA256')
|
||||
.update(verificationData)
|
||||
.verify(PEMString(attCert), attStmt.sig);
|
||||
|
||||
return {
|
||||
valid: validSignature,
|
||||
publicKey: publicKeyU2F,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ import type { MiBlocking } from '@/models/entities/Blocking.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js';
|
||||
import Logger from '@/logger.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
|
@@ -19,7 +19,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { WebhookService } from '@/core/WebhookService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import type { UserKeypairsRepository } from '@/models/index.js';
|
||||
import type { UserKeypairsRepository } from '@/models/_.js';
|
||||
import { RedisKVCache } from '@/misc/cache.js';
|
||||
import type { MiUserKeypair } from '@/models/entities/UserKeypair.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserListJoiningsRepository } from '@/models/index.js';
|
||||
import type { UserListJoiningsRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import type { MiUserList } from '@/models/entities/UserList.js';
|
||||
import type { MiUserListJoining } from '@/models/entities/UserListJoining.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import type { MutingsRepository, MiMuting } from '@/models/index.js';
|
||||
import type { MutingsRepository, MiMuting } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Not, IsNull } from 'typeorm';
|
||||
import type { FollowingsRepository } from '@/models/index.js';
|
||||
import type { FollowingsRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/entities/User.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
|
252
packages/backend/src/core/WebAuthnService.ts
Normal file
252
packages/backend/src/core/WebAuthnService.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import {
|
||||
generateAuthenticationOptions,
|
||||
generateRegistrationOptions, verifyAuthenticationResponse,
|
||||
verifyRegistrationResponse,
|
||||
} from '@simplewebauthn/server';
|
||||
import { AttestationFormat, isoCBOR } from '@simplewebauthn/server/helpers';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UserSecurityKeysRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { MiUser } from '@/models/_.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import type {
|
||||
AuthenticationResponseJSON,
|
||||
AuthenticatorTransportFuture,
|
||||
CredentialDeviceType,
|
||||
PublicKeyCredentialCreationOptionsJSON,
|
||||
PublicKeyCredentialDescriptorFuture,
|
||||
PublicKeyCredentialRequestOptionsJSON,
|
||||
RegistrationResponseJSON,
|
||||
} from '@simplewebauthn/typescript-types';
|
||||
|
||||
@Injectable()
|
||||
export class WebAuthnService {
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.userSecurityKeysRepository)
|
||||
private userSecurityKeysRepository: UserSecurityKeysRepository,
|
||||
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getRelyingParty(): Promise<{ origin: string; rpId: string; rpName: string; rpIcon?: string; }> {
|
||||
const instance = await this.metaService.fetch();
|
||||
return {
|
||||
origin: this.config.url,
|
||||
rpId: this.config.host,
|
||||
rpName: instance.name ?? this.config.host,
|
||||
rpIcon: instance.iconUrl ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> {
|
||||
const relyingParty = await this.getRelyingParty();
|
||||
const keys = await this.userSecurityKeysRepository.findBy({
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
const registrationOptions = await generateRegistrationOptions({
|
||||
rpName: relyingParty.rpName,
|
||||
rpID: relyingParty.rpId,
|
||||
userID: userId,
|
||||
userName: userName,
|
||||
userDisplayName: userDisplayName,
|
||||
attestationType: 'indirect',
|
||||
excludeCredentials: keys.map(key => (<PublicKeyCredentialDescriptorFuture>{
|
||||
id: Buffer.from(key.id, 'base64url'),
|
||||
type: 'public-key',
|
||||
transports: key.transports ?? undefined,
|
||||
})),
|
||||
authenticatorSelection: {
|
||||
residentKey: 'required',
|
||||
userVerification: 'preferred',
|
||||
},
|
||||
});
|
||||
|
||||
await this.redisClient.setex(`webauthn:challenge:${userId}`, 90, registrationOptions.challenge);
|
||||
|
||||
return registrationOptions;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async verifyRegistration(userId: MiUser['id'], response: RegistrationResponseJSON): Promise<{
|
||||
credentialID: Uint8Array;
|
||||
credentialPublicKey: Uint8Array;
|
||||
attestationObject: Uint8Array;
|
||||
fmt: AttestationFormat;
|
||||
counter: number;
|
||||
userVerified: boolean;
|
||||
credentialDeviceType: CredentialDeviceType;
|
||||
credentialBackedUp: boolean;
|
||||
transports?: AuthenticatorTransportFuture[];
|
||||
}> {
|
||||
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
|
||||
|
||||
if (!challenge) {
|
||||
throw new IdentifiableError('7dbfb66c-9216-4e2b-9c27-cef2ac8efb84', 'challenge not found');
|
||||
}
|
||||
|
||||
await this.redisClient.del(`webauthn:challenge:${userId}`);
|
||||
|
||||
const relyingParty = await this.getRelyingParty();
|
||||
|
||||
let verification;
|
||||
try {
|
||||
verification = await verifyRegistrationResponse({
|
||||
response: response,
|
||||
expectedChallenge: challenge,
|
||||
expectedOrigin: relyingParty.origin,
|
||||
expectedRPID: relyingParty.rpId,
|
||||
requireUserVerification: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'verification failed');
|
||||
}
|
||||
|
||||
const { verified } = verification;
|
||||
|
||||
if (!verified || !verification.registrationInfo) {
|
||||
throw new IdentifiableError('bb333667-3832-4a80-8bb5-c505be7d710d', 'verification failed');
|
||||
}
|
||||
|
||||
const { registrationInfo } = verification;
|
||||
|
||||
return {
|
||||
credentialID: registrationInfo.credentialID,
|
||||
credentialPublicKey: registrationInfo.credentialPublicKey,
|
||||
attestationObject: registrationInfo.attestationObject,
|
||||
fmt: registrationInfo.fmt,
|
||||
counter: registrationInfo.counter,
|
||||
userVerified: registrationInfo.userVerified,
|
||||
credentialDeviceType: registrationInfo.credentialDeviceType,
|
||||
credentialBackedUp: registrationInfo.credentialBackedUp,
|
||||
transports: response.response.transports,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> {
|
||||
const keys = await this.userSecurityKeysRepository.findBy({
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
if (keys.length === 0) {
|
||||
throw new IdentifiableError('f27fd449-9af4-4841-9249-1f989b9fa4a4', 'no keys found');
|
||||
}
|
||||
|
||||
const authenticationOptions = await generateAuthenticationOptions({
|
||||
allowCredentials: keys.map(key => (<PublicKeyCredentialDescriptorFuture>{
|
||||
id: Buffer.from(key.id, 'base64url'),
|
||||
type: 'public-key',
|
||||
transports: key.transports ?? undefined,
|
||||
})),
|
||||
userVerification: 'preferred',
|
||||
});
|
||||
|
||||
await this.redisClient.setex(`webauthn:challenge:${userId}`, 90, authenticationOptions.challenge);
|
||||
|
||||
return authenticationOptions;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> {
|
||||
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
|
||||
|
||||
if (!challenge) {
|
||||
throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found');
|
||||
}
|
||||
|
||||
await this.redisClient.del(`webauthn:challenge:${userId}`);
|
||||
|
||||
const key = await this.userSecurityKeysRepository.findOneBy({
|
||||
id: response.id,
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
if (!key) {
|
||||
throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'unknown key');
|
||||
}
|
||||
|
||||
// マイグレーション
|
||||
if (key.counter === 0 && key.publicKey.length === 87) {
|
||||
const cert = new Uint8Array(Buffer.from(key.publicKey, 'base64url'));
|
||||
if (cert[0] === 0x04) { // 前の実装ではいつも 0x04 で始まっていた
|
||||
const halfLength = (cert.length - 1) / 2;
|
||||
|
||||
const cborMap = new Map<number, number | ArrayBufferLike>();
|
||||
cborMap.set(1, 2); // kty, EC2
|
||||
cborMap.set(3, -7); // alg, ES256
|
||||
cborMap.set(-1, 1); // crv, P256
|
||||
cborMap.set(-2, cert.slice(1, halfLength + 1)); // x
|
||||
cborMap.set(-3, cert.slice(halfLength + 1)); // y
|
||||
|
||||
const cborPubKey = Buffer.from(isoCBOR.encode(cborMap)).toString('base64url');
|
||||
await this.userSecurityKeysRepository.update({
|
||||
id: response.id,
|
||||
userId: userId,
|
||||
}, {
|
||||
publicKey: cborPubKey,
|
||||
});
|
||||
key.publicKey = cborPubKey;
|
||||
}
|
||||
}
|
||||
|
||||
const relyingParty = await this.getRelyingParty();
|
||||
|
||||
let verification;
|
||||
try {
|
||||
verification = await verifyAuthenticationResponse({
|
||||
response: response,
|
||||
expectedChallenge: challenge,
|
||||
expectedOrigin: relyingParty.origin,
|
||||
expectedRPID: relyingParty.rpId,
|
||||
authenticator: {
|
||||
credentialID: Buffer.from(key.id, 'base64url'),
|
||||
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
|
||||
counter: key.counter,
|
||||
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
|
||||
},
|
||||
requireUserVerification: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed');
|
||||
}
|
||||
|
||||
const { verified, authenticationInfo } = verification;
|
||||
|
||||
if (!verified) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.userSecurityKeysRepository.update({
|
||||
id: response.id,
|
||||
userId: userId,
|
||||
}, {
|
||||
lastUsed: new Date(),
|
||||
counter: authenticationInfo.newCounter,
|
||||
credentialDeviceType: authenticationInfo.credentialDeviceType,
|
||||
credentialBackedUp: authenticationInfo.credentialBackedUp,
|
||||
});
|
||||
|
||||
return verified;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { WebhooksRepository } from '@/models/index.js';
|
||||
import type { WebhooksRepository } from '@/models/_.js';
|
||||
import type { MiWebhook } from '@/models/entities/Webhook.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { MemoryKVCache } from '@/misc/cache.js';
|
||||
import type { MiUserPublickey } from '@/models/entities/UserPublickey.js';
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowingsRepository } from '@/models/index.js';
|
||||
import type { FollowingsRepository } from '@/models/_.js';
|
||||
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/entities/User.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
@@ -24,10 +24,10 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { MiRemoteUser } from '@/models/entities/User.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isOrderedCollectionPage, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { ApNoteService } from './models/ApNoteService.js';
|
||||
import { ApLoggerService } from './ApLoggerService.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
@@ -87,11 +87,19 @@ export class ApInboxService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
public async performActivity(actor: MiRemoteUser, activity: IObject, {
|
||||
limit = Infinity,
|
||||
allow = null as (string[] | null) } = {},
|
||||
): Promise<void> {
|
||||
if (isCollectionOrOrderedCollection(activity) || isOrderedCollectionPage(activity)) {
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems).slice(0, limit)) {
|
||||
const act = await resolver.resolve(item);
|
||||
const type = getApType(act);
|
||||
if (allow && !allow.includes(type)) {
|
||||
this.logger.info(`skipping activity type: ${type}`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await this.performOneActivity(actor, act);
|
||||
} catch (err) {
|
||||
@@ -367,7 +375,7 @@ export class ApInboxService {
|
||||
});
|
||||
|
||||
if (isPost(object)) {
|
||||
this.createNote(resolver, actor, object, false, activity);
|
||||
await this.createNote(resolver, actor, object, false, activity);
|
||||
} else {
|
||||
this.logger.warn(`Unknown type: ${getApType(object)}`);
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import { MfmService } from '@/core/MfmService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import type { MiUserKeypair } from '@/models/entities/UserKeypair.js';
|
||||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/index.js';
|
||||
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
|
||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
@@ -15,11 +15,11 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { isCollectionOrOrderedCollection } from './type.js';
|
||||
import { isCollectionOrOrderedCollection, isOrderedCollectionPage } from './type.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
import { ApRendererService } from './ApRendererService.js';
|
||||
import { ApRequestService } from './ApRequestService.js';
|
||||
import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
||||
import type { IObject, ICollection, IOrderedCollection, IOrderedCollectionPage } from './type.js';
|
||||
|
||||
export class Resolver {
|
||||
private history: Set<string>;
|
||||
@@ -64,6 +64,18 @@ export class Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
public async resolveOrderedCollectionPage(value: string | IObject): Promise<IOrderedCollectionPage> {
|
||||
const collection = typeof value === 'string'
|
||||
? await this.resolve(value)
|
||||
: value;
|
||||
|
||||
if (isOrderedCollectionPage(collection)) {
|
||||
return collection;
|
||||
} else {
|
||||
throw new Error(`unrecognized collection type: ${collection.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async resolve(value: string | IObject): Promise<IObject> {
|
||||
if (typeof value !== 'string') {
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import type { MiRemoteUser } from '@/models/entities/User.js';
|
||||
import type { MiDriveFile } from '@/models/entities/DriveFile.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import type { MiUser } from '@/models/index.js';
|
||||
import type { MiUser } from '@/models/_.js';
|
||||
import { toArray, unique } from '@/misc/prelude/array.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isMention } from '../type.js';
|
||||
|
@@ -7,7 +7,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { PollsRepository, EmojisRepository } from '@/models/index.js';
|
||||
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiRemoteUser } from '@/models/entities/User.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
|
@@ -8,7 +8,7 @@ import promiseLimit from 'promise-limit';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiLocalUser, MiRemoteUser } from '@/models/entities/User.js';
|
||||
import { MiUser } from '@/models/entities/User.js';
|
||||
@@ -38,7 +38,8 @@ import { MetaService } from '@/core/MetaService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import type { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
|
||||
import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isOrderedCollection, isOrderedCollectionPage, isPropertyValue } from '../type.js';
|
||||
import { ApInboxService } from '../ApInboxService.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { ApNoteService } from './ApNoteService.js';
|
||||
@@ -68,6 +69,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
private apResolverService: ApResolverService;
|
||||
private apNoteService: ApNoteService;
|
||||
private apImageService: ApImageService;
|
||||
private apInboxService: ApInboxService;
|
||||
private apMfmService: ApMfmService;
|
||||
private mfmService: MfmService;
|
||||
private hashtagService: HashtagService;
|
||||
@@ -116,6 +118,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
this.apResolverService = this.moduleRef.get('ApResolverService');
|
||||
this.apNoteService = this.moduleRef.get('ApNoteService');
|
||||
this.apImageService = this.moduleRef.get('ApImageService');
|
||||
this.apInboxService = this.moduleRef.get('ApInboxService');
|
||||
this.apMfmService = this.moduleRef.get('ApMfmService');
|
||||
this.mfmService = this.moduleRef.get('MfmService');
|
||||
this.hashtagService = this.moduleRef.get('HashtagService');
|
||||
@@ -384,7 +387,10 @@ export class ApPersonService implements OnModuleInit {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
await this.updateFeatured(user.id, resolver).catch(err => this.logger.error(err));
|
||||
await Promise.allSettled([
|
||||
this.updateFeatured(user.id, resolver).catch(err => this.logger.error(err)),
|
||||
this.updateOutboxFirstPage(user, person.outbox, resolver).catch(err => this.logger.error(err)),
|
||||
]);
|
||||
|
||||
return user;
|
||||
}
|
||||
@@ -589,7 +595,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
|
||||
const items = await Promise.all(toArray(unresolvedItems).map(x => _resolver.resolve(x)));
|
||||
|
||||
// Resolve and regist Notes
|
||||
// Resolve and register Notes
|
||||
const limit = promiseLimit<MiNote | null>(2);
|
||||
const featuredNotes = await Promise.all(items
|
||||
.filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも
|
||||
@@ -616,6 +622,35 @@ export class ApPersonService implements OnModuleInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve outbox from an actor object.
|
||||
*
|
||||
* This only retrieves the first page for now.
|
||||
*/
|
||||
public async updateOutboxFirstPage(user: RemoteUser, outbox: IActor['outbox'], resolver: Resolver): Promise<void> {
|
||||
if (!this.config.outboxNotesFetchLimit) return;
|
||||
|
||||
// https://www.w3.org/TR/activitypub/#actor-objects
|
||||
// Outbox is a required property for all actors
|
||||
if (!outbox) {
|
||||
throw new Error('No outbox property');
|
||||
}
|
||||
|
||||
this.logger.info(`Fetching the outbox for ${user.uri}: ${outbox}`);
|
||||
|
||||
const collection = await resolver.resolveCollection(outbox);
|
||||
if (!isOrderedCollection(collection)) {
|
||||
throw new Error('Outbox must be an ordered collection');
|
||||
}
|
||||
|
||||
const firstPage = collection.first ?
|
||||
await resolver.resolveOrderedCollectionPage(collection.first) :
|
||||
collection;
|
||||
|
||||
// Perform activity but only the first outboxNotesFetchLimit ones with `type: Create`
|
||||
await this.apInboxService.performActivity(user, firstPage, { limit: this.config.outboxNotesFetchLimit, allow: ['Create'] });
|
||||
}
|
||||
|
||||
/**
|
||||
* リモート由来のアカウント移行処理を行います
|
||||
* @param src 移行元アカウント(リモートかつupdatePerson後である必要がある、というかこれ自体がupdatePersonで呼ばれる前提)
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, PollsRepository } from '@/models/index.js';
|
||||
import type { NotesRepository, PollsRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { IPoll } from '@/models/entities/Poll.js';
|
||||
import type Logger from '@/logger.js';
|
||||
|
@@ -92,16 +92,37 @@ export interface IActivity extends IObject {
|
||||
};
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection
|
||||
export interface ICollection extends IObject {
|
||||
type: 'Collection';
|
||||
totalItems: number;
|
||||
current?: ICollectionPage | string;
|
||||
first?: ICollectionPage | string;
|
||||
last?: ICollectionPage | string;
|
||||
items: ApObject;
|
||||
}
|
||||
|
||||
export interface IOrderedCollection extends IObject {
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection
|
||||
export interface IOrderedCollection extends Omit<ICollection, 'type' | 'items'> {
|
||||
type: 'OrderedCollection';
|
||||
totalItems: number;
|
||||
orderedItems: ApObject;
|
||||
|
||||
// orderedItems is not defined well
|
||||
// https://github.com/w3c/activitystreams/issues/494
|
||||
orderedItems?: ApObject;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collectionpage
|
||||
export interface ICollectionPage extends Omit<ICollection, 'type'> {
|
||||
type: 'CollectionPage';
|
||||
partOf?: ICollection | string;
|
||||
next?: ICollectionPage | string;
|
||||
prev?: ICollectionPage | string;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollectionpage
|
||||
export interface IOrderedCollectionPage extends Omit<IOrderedCollection, 'type'>, Omit<ICollectionPage, 'type' | 'items'> {
|
||||
type: 'OrderedCollectionPage';
|
||||
startIndex?: number,
|
||||
}
|
||||
|
||||
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event'];
|
||||
@@ -188,6 +209,9 @@ export const isCollection = (object: IObject): object is ICollection =>
|
||||
export const isOrderedCollection = (object: IObject): object is IOrderedCollection =>
|
||||
getApType(object) === 'OrderedCollection';
|
||||
|
||||
export const isOrderedCollectionPage = (object: IObject): object is IOrderedCollectionPage =>
|
||||
getApType(object) === 'OrderedCollectionPage';
|
||||
|
||||
export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
|
||||
isCollection(object) || isOrderedCollection(object);
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import type { FollowingsRepository, InstancesRepository } from '@/models/index.js';
|
||||
import type { FollowingsRepository, InstancesRepository } from '@/models/_.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js';
|
||||
import type { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
|
||||
import type { MiDriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { Not, IsNull, DataSource } from 'typeorm';
|
||||
import type { NotesRepository } from '@/models/index.js';
|
||||
import type { NotesRepository } from '@/models/_.js';
|
||||
import type { MiNote } from '@/models/entities/Note.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user