Compare commits
82 Commits
13.14.0-be
...
oauth2oriz
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8aa350ced4 | ||
![]() |
93364cb922 | ||
![]() |
1f38d624c0 | ||
![]() |
deb9ba146f | ||
![]() |
833df85457 | ||
![]() |
d340860b8b | ||
![]() |
d1534ec64e | ||
![]() |
16a73dea26 | ||
![]() |
d0d9b4b19c | ||
![]() |
ca7c3c6063 | ||
![]() |
cb2089981a | ||
![]() |
daa18efc99 | ||
![]() |
0b3fd09bb0 | ||
![]() |
1567a2ea3e | ||
![]() |
ecdd1c115a | ||
![]() |
d7e0e9feca | ||
![]() |
7ed8fbbba3 | ||
![]() |
5db1126db6 | ||
![]() |
628377187a | ||
![]() |
b57d40ed09 | ||
![]() |
1755c75647 | ||
![]() |
c55d9784fe | ||
![]() |
52e7bdd817 | ||
![]() |
260ac0ecfc | ||
![]() |
b81e6eeff9 | ||
![]() |
15f859d562 | ||
![]() |
b938bc7c52 | ||
![]() |
20efdc78e2 | ||
![]() |
aa87fb2f50 | ||
![]() |
95dd66a0ba | ||
![]() |
c83628e5d0 | ||
![]() |
d0245b59bc | ||
![]() |
4c12a9d882 | ||
![]() |
d245306d90 | ||
![]() |
0d2041f5aa | ||
![]() |
b5df8ca0fd | ||
![]() |
3b8b9a658a | ||
![]() |
413fa63093 | ||
![]() |
347a4a0b93 | ||
![]() |
bfe6e5abb8 | ||
![]() |
78c6bb1cc2 | ||
![]() |
9a5fa00f9a | ||
![]() |
967989c5f8 | ||
![]() |
c25836bc1a | ||
![]() |
9022971fb9 | ||
![]() |
cb5cfd4296 | ||
![]() |
cbaae2201f | ||
![]() |
2c6379649a | ||
![]() |
150a6f80d0 | ||
![]() |
c0f63234d7 | ||
![]() |
9c29880f8b | ||
![]() |
2b23120664 | ||
![]() |
b6f6819b76 | ||
![]() |
77ad8c0ac6 | ||
![]() |
92f3ae2d9c | ||
![]() |
94ea15d2d7 | ||
![]() |
8e7fc1ed98 | ||
![]() |
937e9be34e | ||
![]() |
027c5734a4 | ||
![]() |
a688bd1061 | ||
![]() |
87dbe5e9fb | ||
![]() |
f6d9cf1ef1 | ||
![]() |
333d6a9283 | ||
![]() |
deb4429e3a | ||
![]() |
6385ca9b0d | ||
![]() |
515af3176a | ||
![]() |
0cc9d5aa32 | ||
![]() |
401575a903 | ||
![]() |
88fd7f2758 | ||
![]() |
5034e6cd69 | ||
![]() |
2f566e4173 | ||
![]() |
179640af30 | ||
![]() |
098d0670a3 | ||
![]() |
71f62b9d89 | ||
![]() |
82c9820ac8 | ||
![]() |
39526d0225 | ||
![]() |
049dbfeb66 | ||
![]() |
8ea1288234 | ||
![]() |
a55d3f7382 | ||
![]() |
f5a6509663 | ||
![]() |
a4fb17620c | ||
![]() |
0621e94c7d |
@@ -6,7 +6,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/pnpm:2": {},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20.3.1"
|
||||
"version": "18.16.0"
|
||||
}
|
||||
},
|
||||
"forwardPorts": [3000],
|
||||
|
@@ -6,10 +6,6 @@ indent_size = 2
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
|
2
.github/ISSUE_TEMPLATE/01_bug-report.md
vendored
2
.github/ISSUE_TEMPLATE/01_bug-report.md
vendored
@@ -54,7 +54,7 @@ Please include errors from the developer console and/or server log files if you
|
||||
|
||||
* Installation Method or Hosting Service: <!-- Example: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment -->
|
||||
* Misskey: 13.x.x
|
||||
* Node: 20.x.x
|
||||
* Node: 18.x.x
|
||||
* PostgreSQL: 15.x.x
|
||||
* Redis: 7.x.x
|
||||
* OS and Architecture: <!-- Example: Ubuntu 22.04.2 LTS aarch64 -->
|
||||
|
2
.github/workflows/storybook.yml
vendored
2
.github/workflows/storybook.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js 20.x
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
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: [18.x]
|
||||
|
||||
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: [18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
node-version: [18.x]
|
||||
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: [18.x]
|
||||
# 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: [18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -64,6 +64,3 @@ temp
|
||||
*.blend3
|
||||
*.blend4
|
||||
*.blend5
|
||||
|
||||
# VSCode addon
|
||||
.favorites.json
|
||||
|
@@ -1 +1 @@
|
||||
20.3.1
|
||||
18.16.0
|
||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@@ -14,27 +14,8 @@
|
||||
|
||||
## 13.x.x (unreleased)
|
||||
|
||||
### General
|
||||
- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました
|
||||
- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました
|
||||
|
||||
### Client
|
||||
- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように
|
||||
- ドライブファイルのメニューで画像をクロップできるように
|
||||
- 画像を動画と同様に簡単に隠せるように
|
||||
- オリジナル画像を保持せずにアップロードする場合webpでアップロードされるように(Safari以外)
|
||||
- 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように
|
||||
- Fix: サーバーメトリクスが90度傾いている
|
||||
- Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正
|
||||
- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正
|
||||
- Fix: ZenUIでポップアップの表示位置がおかしい問題を修正
|
||||
- Fix: ページ遷移でスクロール位置が保持されない問題を修正
|
||||
|
||||
### Server
|
||||
- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました
|
||||
- nsfwjs のモデルロードを排他することで、重複ロードによってメモリ使用量が増加しないように
|
||||
- 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用)
|
||||
- 全体的なDBクエリのパフォーマンスを向上
|
||||
|
||||
## 13.13.2
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.4
|
||||
|
||||
ARG NODE_VERSION=20.3.1-bullseye
|
||||
ARG NODE_VERSION=18.16.0-bullseye
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
|
10
locales/index.d.ts
vendored
10
locales/index.d.ts
vendored
@@ -139,10 +139,8 @@ export interface Locale {
|
||||
"suspendConfirm": string;
|
||||
"unsuspendConfirm": string;
|
||||
"selectList": string;
|
||||
"editList": string;
|
||||
"selectChannel": string;
|
||||
"selectAntenna": string;
|
||||
"editAntenna": string;
|
||||
"selectWidget": string;
|
||||
"editWidgets": string;
|
||||
"editWidgetsExit": string;
|
||||
@@ -316,7 +314,7 @@ export interface Locale {
|
||||
"rename": string;
|
||||
"avatar": string;
|
||||
"banner": string;
|
||||
"displayOfSensitiveMedia": string;
|
||||
"nsfw": string;
|
||||
"whenServerDisconnected": string;
|
||||
"disconnectedFromServer": string;
|
||||
"reload": string;
|
||||
@@ -1068,9 +1066,6 @@ export interface Locale {
|
||||
"additionalEmojiDictionary": string;
|
||||
"installed": string;
|
||||
"branding": string;
|
||||
"enableServerMachineStats": string;
|
||||
"enableIdenticonGeneration": string;
|
||||
"turnOffToImprovePerformance": string;
|
||||
"_initialAccountSetting": {
|
||||
"accountCreated": string;
|
||||
"letsStartAccountSetup": string;
|
||||
@@ -1531,7 +1526,6 @@ export interface Locale {
|
||||
"back": string;
|
||||
"reduceFrequencyOfThisAd": string;
|
||||
"hide": string;
|
||||
"timezoneinfo": string;
|
||||
};
|
||||
"_forgotPassword": {
|
||||
"enterEmail": string;
|
||||
@@ -1593,7 +1587,7 @@ export interface Locale {
|
||||
"morePatrons": string;
|
||||
"patrons": string;
|
||||
};
|
||||
"_displayOfSensitiveMedia": {
|
||||
"_nsfw": {
|
||||
"respect": string;
|
||||
"ignore": string;
|
||||
"force": string;
|
||||
|
@@ -112,7 +112,7 @@ pinnedNote: "ピン留めされたノート"
|
||||
pinned: "ピン留め"
|
||||
you: "あなた"
|
||||
clickToShow: "クリックして表示"
|
||||
sensitive: "センシティブ"
|
||||
sensitive: "閲覧注意"
|
||||
add: "追加"
|
||||
reaction: "リアクション"
|
||||
reactions: "リアクション"
|
||||
@@ -120,8 +120,8 @@ reactionSetting: "ピッカーに表示するリアクション"
|
||||
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
|
||||
rememberNoteVisibility: "公開範囲を記憶する"
|
||||
attachCancel: "添付取り消し"
|
||||
markAsSensitive: "センシティブとして設定"
|
||||
unmarkAsSensitive: "センシティブを解除する"
|
||||
markAsSensitive: "閲覧注意にする"
|
||||
unmarkAsSensitive: "閲覧注意を解除する"
|
||||
enterFileName: "ファイル名を入力"
|
||||
mute: "ミュート"
|
||||
unmute: "ミュート解除"
|
||||
@@ -136,10 +136,8 @@ unblockConfirm: "ブロック解除しますか?"
|
||||
suspendConfirm: "凍結しますか?"
|
||||
unsuspendConfirm: "解凍しますか?"
|
||||
selectList: "リストを選択"
|
||||
editList: "リストを編集"
|
||||
selectChannel: "チャンネルを選択"
|
||||
selectAntenna: "アンテナを選択"
|
||||
editAntenna: "アンテナを編集"
|
||||
selectWidget: "ウィジェットを選択"
|
||||
editWidgets: "ウィジェットを編集"
|
||||
editWidgetsExit: "編集を終了"
|
||||
@@ -313,7 +311,7 @@ copyUrl: "URLをコピー"
|
||||
rename: "名前を変更"
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
displayOfSensitiveMedia: "センシティブなメディアの表示"
|
||||
nsfw: "閲覧注意"
|
||||
whenServerDisconnected: "サーバーとの接続が失われたとき"
|
||||
disconnectedFromServer: "サーバーから切断されました"
|
||||
reload: "リロード"
|
||||
@@ -695,7 +693,7 @@ driveUsage: "ドライブ使用量"
|
||||
noCrawle: "クローラーによるインデックスを拒否"
|
||||
noCrawleDescription: "外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。"
|
||||
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
|
||||
alwaysMarkSensitive: "デフォルトでメディアをセンシティブ設定にする"
|
||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
|
||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
||||
disableShowingAnimatedImages: "アニメーション画像を再生しない"
|
||||
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
|
||||
@@ -922,8 +920,8 @@ cannotUploadBecauseInappropriate: "不適切な内容を含む可能性がある
|
||||
cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。"
|
||||
cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。"
|
||||
beta: "ベータ"
|
||||
enableAutoSensitive: "自動センシティブ判定"
|
||||
enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにセンシティブフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。"
|
||||
enableAutoSensitive: "自動NSFW判定"
|
||||
enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。"
|
||||
activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。"
|
||||
navbar: "ナビゲーションバー"
|
||||
shuffle: "シャッフル"
|
||||
@@ -1065,9 +1063,6 @@ goToMisskey: "Misskeyへ"
|
||||
additionalEmojiDictionary: "絵文字の追加辞書"
|
||||
installed: "インストール済み"
|
||||
branding: "ブランディング"
|
||||
enableServerMachineStats: "サーバーのマシン情報を公開する"
|
||||
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
|
||||
turnOffToImprovePerformance: "オフにするとパフォーマンスが向上します。"
|
||||
|
||||
_initialAccountSetting:
|
||||
accountCreated: "アカウントの作成が完了しました!"
|
||||
@@ -1417,7 +1412,7 @@ _sensitiveMediaDetection:
|
||||
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
|
||||
sensitivity: "検出感度"
|
||||
sensitivityDescription: "感度を低くすると、誤検知(偽陽性)が減ります。感度を高くすると、検知漏れ(偽陰性)が減ります。"
|
||||
setSensitiveFlagAutomatically: "センシティブフラグを設定する"
|
||||
setSensitiveFlagAutomatically: "NSFWフラグを設定する"
|
||||
setSensitiveFlagAutomaticallyDescription: "この設定をオフにしても内部的に判定結果は保持されます。"
|
||||
analyzeVideos: "動画の解析を有効化"
|
||||
analyzeVideosDescription: "静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。"
|
||||
@@ -1451,7 +1446,6 @@ _ad:
|
||||
back: "戻る"
|
||||
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
|
||||
hide: "表示しない"
|
||||
timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されます。"
|
||||
|
||||
_forgotPassword:
|
||||
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"
|
||||
@@ -1511,9 +1505,9 @@ _aboutMisskey:
|
||||
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
|
||||
patrons: "支援者"
|
||||
|
||||
_displayOfSensitiveMedia:
|
||||
respect: "センシティブ設定されたメディアを隠す"
|
||||
ignore: "センシティブ設定されたメディアを隠さない"
|
||||
_nsfw:
|
||||
respect: "閲覧注意のメディアは隠す"
|
||||
ignore: "閲覧注意のメディアを隠さない"
|
||||
force: "常にメディアを隠す"
|
||||
|
||||
_instanceTicker:
|
||||
|
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "13.14.0-beta.1",
|
||||
"version": "13.13.2",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,7 +25,7 @@
|
||||
"migrateandstart": "pnpm migrate && pnpm start",
|
||||
"gulp": "pnpm exec gulp build",
|
||||
"watch": "pnpm dev",
|
||||
"dev": "node ./scripts/dev.mjs",
|
||||
"dev": "node ./scripts/dev.js",
|
||||
"lint": "pnpm -r lint",
|
||||
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||
"cy:run": "pnpm cypress run",
|
||||
@@ -44,23 +44,23 @@
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"execa": "7.1.1",
|
||||
"execa": "5.1.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",
|
||||
"js-yaml": "4.1.0",
|
||||
"typescript": "5.1.6"
|
||||
"typescript": "5.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/gulp": "4.0.10",
|
||||
"@types/gulp-rename": "2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.61.0",
|
||||
"@typescript-eslint/parser": "5.61.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.60.0",
|
||||
"@typescript-eslint/parser": "5.60.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "12.17.0",
|
||||
"eslint": "8.44.0",
|
||||
"cypress": "12.15.0",
|
||||
"eslint": "8.43.0",
|
||||
"start-server-and-test": "2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
@@ -1,9 +0,0 @@
|
||||
export class ad1677054292210 {
|
||||
name = 'ad1677054292210';
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "ad" ADD "dayOfWeek" integer NOT NULL Default 0`);
|
||||
}
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "dayOfWeek"`);
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
export class AddMetaOptions1688280713783 {
|
||||
name = 'AddMetaOptions1688280713783'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableServerMachineStats" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableIdenticonGeneration" boolean NOT NULL DEFAULT true`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableIdenticonGeneration"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableServerMachineStats"`);
|
||||
}
|
||||
}
|
@@ -3,9 +3,6 @@
|
||||
"main": "./index.js",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./built/index.js",
|
||||
"start:test": "NODE_ENV=test node ./built/index.js",
|
||||
@@ -54,35 +51,36 @@
|
||||
"utf-8-validate": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.367.0",
|
||||
"@aws-sdk/lib-storage": "3.367.0",
|
||||
"@aws-sdk/node-http-handler": "3.360.0",
|
||||
"@bull-board/api": "5.6.0",
|
||||
"@bull-board/fastify": "5.6.0",
|
||||
"@bull-board/ui": "5.6.0",
|
||||
"@aws-sdk/client-s3": "3.321.1",
|
||||
"@aws-sdk/lib-storage": "3.321.1",
|
||||
"@aws-sdk/node-http-handler": "3.321.1",
|
||||
"@bull-board/api": "5.5.3",
|
||||
"@bull-board/fastify": "5.5.3",
|
||||
"@bull-board/ui": "5.5.3",
|
||||
"@discordapp/twemoji": "14.1.2",
|
||||
"@fastify/accepts": "4.2.0",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
"@fastify/cors": "8.3.0",
|
||||
"@fastify/express": "^2.3.0",
|
||||
"@fastify/http-proxy": "9.2.1",
|
||||
"@fastify/multipart": "7.7.0",
|
||||
"@fastify/static": "6.10.2",
|
||||
"@fastify/view": "8.0.0",
|
||||
"@nestjs/common": "10.0.5",
|
||||
"@nestjs/core": "10.0.5",
|
||||
"@nestjs/testing": "10.0.5",
|
||||
"@fastify/view": "7.4.1",
|
||||
"@nestjs/common": "10.0.3",
|
||||
"@nestjs/core": "10.0.3",
|
||||
"@nestjs/testing": "10.0.3",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sinonjs/fake-timers": "10.3.0",
|
||||
"@swc/cli": "0.1.62",
|
||||
"@swc/core": "1.3.68",
|
||||
"@swc/core": "1.3.66",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "5.3.1",
|
||||
"async-mutex": "^0.4.0",
|
||||
"autwh": "0.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"bullmq": "4.2.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"bullmq": "4.1.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.0",
|
||||
"chalk": "5.2.0",
|
||||
@@ -94,18 +92,19 @@
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"escape-regexp": "0.0.1",
|
||||
"fastify": "4.19.2",
|
||||
"fastify": "4.18.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "18.5.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "13.0.0",
|
||||
"happy-dom": "10.0.3",
|
||||
"happy-dom": "9.20.3",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "^1.1.0",
|
||||
"ioredis": "5.3.2",
|
||||
"ip-cidr": "3.1.0",
|
||||
"ipaddr.js": "2.1.0",
|
||||
"is-svg": "5.0.0",
|
||||
"is-svg": "4.3.2",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "22.1.0",
|
||||
"json5": "2.2.3",
|
||||
@@ -121,10 +120,13 @@
|
||||
"nodemailer": "6.9.3",
|
||||
"nsfwjs": "2.4.2",
|
||||
"oauth": "0.10.0",
|
||||
"oauth2orize": "^1.11.1",
|
||||
"oauth2orize-pkce": "^0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.1.3",
|
||||
"otpauth": "9.1.2",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.11.1",
|
||||
"pg": "8.11.0",
|
||||
"pkce-challenge": "^4.0.1",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"pug": "3.0.2",
|
||||
@@ -148,14 +150,14 @@
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"summaly": "github:misskey-dev/summaly",
|
||||
"systeminformation": "5.18.6",
|
||||
"systeminformation": "5.18.4",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.7",
|
||||
"tsc-alias": "1.8.6",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typeorm": "0.3.17",
|
||||
"typescript": "5.1.6",
|
||||
"typescript": "5.1.3",
|
||||
"ulid": "2.3.0",
|
||||
"unzipper": "0.10.14",
|
||||
"uuid": "9.0.0",
|
||||
@@ -165,16 +167,18 @@
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.6.1",
|
||||
"@jest/globals": "29.5.0",
|
||||
"@swc/jest": "0.2.26",
|
||||
"@types/accepts": "1.3.5",
|
||||
"@types/archiver": "5.3.2",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/cbor": "6.0.0",
|
||||
"@types/color-convert": "2.0.0",
|
||||
"@types/content-disposition": "0.5.5",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/fluent-ffmpeg": "2.1.21",
|
||||
"@types/http-link-header": "^1.0.3",
|
||||
"@types/jest": "29.5.2",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/jsdom": "21.1.1",
|
||||
@@ -182,14 +186,15 @@
|
||||
"@types/jsrsasign": "10.5.8",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "20.4.0",
|
||||
"@types/node": "20.3.1",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.8",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/oauth2orize": "^1.11.0",
|
||||
"@types/pg": "8.10.2",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/qrcode": "1.5.1",
|
||||
"@types/qrcode": "1.5.0",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/ratelimiter": "3.4.4",
|
||||
"@types/redis": "4.0.11",
|
||||
@@ -197,6 +202,7 @@
|
||||
"@types/sanitize-html": "2.9.0",
|
||||
"@types/semver": "7.5.0",
|
||||
"@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",
|
||||
@@ -206,14 +212,15 @@
|
||||
"@types/web-push": "3.3.2",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "5.61.0",
|
||||
"@typescript-eslint/parser": "5.61.0",
|
||||
"aws-sdk-client-mock": "3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.60.0",
|
||||
"@typescript-eslint/parser": "5.60.0",
|
||||
"aws-sdk-client-mock": "2.1.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.44.0",
|
||||
"eslint": "8.43.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"execa": "7.1.1",
|
||||
"jest": "29.6.1",
|
||||
"jest-mock": "29.6.1"
|
||||
"execa": "6.1.0",
|
||||
"jest": "29.5.0",
|
||||
"jest-mock": "29.5.0",
|
||||
"simple-oauth2": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
5
packages/backend/src/@types/oauth2orize-pkce.d.ts
vendored
Normal file
5
packages/backend/src/@types/oauth2orize-pkce.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module 'oauth2orize-pkce' {
|
||||
export default {
|
||||
extensions(): any;
|
||||
};
|
||||
}
|
@@ -96,6 +96,12 @@ function showNodejsVersion(): void {
|
||||
const nodejsLogger = bootLogger.createSubLogger('nodejs');
|
||||
|
||||
nodejsLogger.info(`Version ${process.version} detected.`);
|
||||
|
||||
const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, 'utf-8').trim();
|
||||
if (semver.lt(process.version, minVersion)) {
|
||||
nodejsLogger.error(`At least Node.js ${minVersion} required!`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfigBoot(): Config {
|
||||
|
@@ -4,7 +4,6 @@ import { dirname } from 'node:path';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as nsfw from 'nsfwjs';
|
||||
import si from 'systeminformation';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
@@ -18,7 +17,6 @@ let isSupportedCpu: undefined | boolean = undefined;
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
private model: nsfw.NSFWJS;
|
||||
private modelLoadMutex: Mutex = new Mutex();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
@@ -41,13 +39,7 @@ export class AiService {
|
||||
|
||||
const tf = await import('@tensorflow/tfjs-node');
|
||||
|
||||
if (this.model == null) {
|
||||
await this.modelLoadMutex.runExclusive(async () => {
|
||||
if (this.model == null) {
|
||||
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.model == null) this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
||||
|
||||
const buffer = await fs.promises.readFile(path);
|
||||
const image = await tf.node.decodeImage(buffer, 3) as any;
|
||||
|
@@ -32,6 +32,11 @@ export class AppLockService {
|
||||
return this.lock(`ap-object:${uri}`, timeout);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000): Promise<() => void> {
|
||||
return this.lock(`instance:${host}`, timeout);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> {
|
||||
return this.lock(`chart-insert:${lockKey}`, timeout);
|
||||
|
@@ -3,6 +3,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import type { Instance } from '@/models/entities/Instance.js';
|
||||
import type { InstancesRepository } from '@/models/index.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
@@ -10,7 +12,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import type { DOMWindow } from 'jsdom';
|
||||
import * as Redis from 'ioredis';
|
||||
|
||||
type NodeInfo = {
|
||||
openRegistrations?: unknown;
|
||||
@@ -36,43 +37,33 @@ export class FetchInstanceMetadataService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.instancesRepository)
|
||||
private instancesRepository: InstancesRepository,
|
||||
|
||||
private appLockService: AppLockService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
private loggerService: LoggerService,
|
||||
private federatedInstanceService: FederatedInstanceService,
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('metadata', 'cyan');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async tryLock(host: string): Promise<boolean> {
|
||||
const mutex = await this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '1', 'GET');
|
||||
return mutex !== '1';
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public unlock(host: string): Promise<'OK'> {
|
||||
return this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '0');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async fetchInstanceMetadata(instance: Instance, force = false): Promise<void> {
|
||||
const host = instance.host;
|
||||
// Acquire mutex to ensure no parallel runs
|
||||
if (!await this.tryLock(host)) return;
|
||||
try {
|
||||
const unlock = await this.appLockService.getFetchInstanceMetadataLock(instance.host);
|
||||
|
||||
if (!force) {
|
||||
const _instance = await this.federatedInstanceService.fetch(host);
|
||||
const _instance = await this.instancesRepository.findOneBy({ host: instance.host });
|
||||
const now = Date.now();
|
||||
if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) {
|
||||
// unlock at the finally caluse
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info(`Fetching metadata of ${instance.host} ...`);
|
||||
|
||||
try {
|
||||
const [info, dom, manifest] = await Promise.all([
|
||||
this.fetchNodeinfo(instance).catch(() => null),
|
||||
this.fetchDom(instance).catch(() => null),
|
||||
@@ -113,7 +104,7 @@ export class FetchInstanceMetadataService {
|
||||
} catch (e) {
|
||||
this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
||||
} finally {
|
||||
await this.unlock(host);
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -304,11 +304,11 @@ export class FileInfoService {
|
||||
@bindThis
|
||||
public fixMime(mime: string | fileType.MimeType): string {
|
||||
// see https://github.com/misskey-dev/misskey/pull/10686
|
||||
if (mime === 'audio/x-flac') {
|
||||
return 'audio/flac';
|
||||
if (mime === "audio/x-flac") {
|
||||
return "audio/flac";
|
||||
}
|
||||
if (mime === 'audio/vnd.wave') {
|
||||
return 'audio/wav';
|
||||
if (mime === "audio/vnd.wave") {
|
||||
return "audio/wav";
|
||||
}
|
||||
|
||||
return mime;
|
||||
@@ -355,12 +355,11 @@ export class FileInfoService {
|
||||
* Check the file is SVG or not
|
||||
*/
|
||||
@bindThis
|
||||
public async checkSvg(path: string): Promise<boolean> {
|
||||
public async checkSvg(path: string) {
|
||||
try {
|
||||
const size = await this.getFileSize(path);
|
||||
if (size > 1 * 1024 * 1024) return false;
|
||||
const buffer = await fs.promises.readFile(path);
|
||||
return isSvg(buffer.toString());
|
||||
return isSvg(fs.readFileSync(path));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
@@ -121,8 +121,10 @@ export class NoteDeleteService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async findCascadingNotes(note: Note): Promise<Note[]> {
|
||||
const recursive = async (noteId: string): Promise<Note[]> => {
|
||||
private async findCascadingNotes(note: Note) {
|
||||
const cascadingNotes: Note[] = [];
|
||||
|
||||
const recursive = async (noteId: string) => {
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.replyId = :noteId', { noteId })
|
||||
.orWhere(new Brackets(q => {
|
||||
@@ -131,14 +133,12 @@ export class NoteDeleteService {
|
||||
}))
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
const replies = await query.getMany();
|
||||
|
||||
return [
|
||||
replies,
|
||||
...await Promise.all(replies.map(reply => recursive(reply.id))),
|
||||
].flat();
|
||||
for (const reply of replies) {
|
||||
cascadingNotes.push(reply);
|
||||
await recursive(reply.id);
|
||||
}
|
||||
};
|
||||
|
||||
const cascadingNotes: Note[] = await recursive(note.id);
|
||||
await recursive(note.id);
|
||||
|
||||
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
|
||||
import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
|
||||
import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
|
||||
import type httpSignature from '@peertube/http-signature';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@@ -69,7 +69,7 @@ export class QueueService {
|
||||
if (content == null) return null;
|
||||
if (to == null) return null;
|
||||
|
||||
const data: DeliverJobData = {
|
||||
const data = {
|
||||
user: {
|
||||
id: user.id,
|
||||
},
|
||||
@@ -88,40 +88,6 @@ export class QueueService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ApDeliverManager-DeliverManager.execute()からinboxesを突っ込んでaddBulkしたい
|
||||
* @param user `{ id: string; }` この関数ではThinUserに変換しないので前もって変換してください
|
||||
* @param content IActivity | null
|
||||
* @param inboxes `Map<string, boolean>` / key: to (inbox url), value: isSharedInbox (whether it is sharedInbox)
|
||||
* @returns void
|
||||
*/
|
||||
@bindThis
|
||||
public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map<string, boolean>) {
|
||||
if (content == null) return null;
|
||||
|
||||
const opts = {
|
||||
attempts: this.config.deliverJobMaxAttempts ?? 12,
|
||||
backoff: {
|
||||
type: 'custom',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
};
|
||||
|
||||
await this.deliverQueue.addBulk(Array.from(inboxes.entries()).map(d => ({
|
||||
name: d[0],
|
||||
data: {
|
||||
user,
|
||||
content,
|
||||
to: d[0],
|
||||
isSharedInbox: d[1],
|
||||
} as DeliverJobData,
|
||||
opts,
|
||||
})));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public inbox(activity: IActivity, signature: httpSignature.IParsedSignature) {
|
||||
const data = {
|
||||
|
@@ -174,7 +174,7 @@ export class SearchService {
|
||||
if (me) this.queryService.generateMutedUserQuery(query, me);
|
||||
if (me) this.queryService.generateBlockedUserQuery(query, me);
|
||||
|
||||
return await query.limit(pagination.limit).getMany();
|
||||
return await query.take(pagination.limit).getMany();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import escapeRegexp from 'escape-regexp';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -55,18 +56,25 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||
|
||||
@bindThis
|
||||
public parseUri(value: string | IObject): UriParseResult {
|
||||
const separator = '/';
|
||||
const uri = getApId(value);
|
||||
|
||||
const uri = new URL(getApId(value));
|
||||
if (uri.origin !== this.config.url) return { local: false, uri: uri.href };
|
||||
// the host part of a URL is case insensitive, so use the 'i' flag.
|
||||
const localRegex = new RegExp('^' + escapeRegexp(this.config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i');
|
||||
const matchLocal = uri.match(localRegex);
|
||||
|
||||
const [, type, id, ...rest] = uri.pathname.split(separator);
|
||||
if (matchLocal) {
|
||||
return {
|
||||
local: true,
|
||||
type,
|
||||
id,
|
||||
rest: rest.length === 0 ? undefined : rest.join(separator),
|
||||
type: matchLocal[1],
|
||||
id: matchLocal[2],
|
||||
rest: matchLocal[3],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
local: false,
|
||||
uri,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -7,8 +7,6 @@ import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { IActivity } from '@/core/activitypub/type.js';
|
||||
import { ThinUser } from '@/queue/types.js';
|
||||
|
||||
interface IRecipe {
|
||||
type: string;
|
||||
@@ -23,10 +21,10 @@ interface IDirectRecipe extends IRecipe {
|
||||
to: RemoteUser;
|
||||
}
|
||||
|
||||
const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe =>
|
||||
const isFollowers = (recipe: any): recipe is IFollowersRecipe =>
|
||||
recipe.type === 'Followers';
|
||||
|
||||
const isDirect = (recipe: IRecipe): recipe is IDirectRecipe =>
|
||||
const isDirect = (recipe: any): recipe is IDirectRecipe =>
|
||||
recipe.type === 'Direct';
|
||||
|
||||
@Injectable()
|
||||
@@ -48,11 +46,11 @@ export class ApDeliverManagerService {
|
||||
|
||||
/**
|
||||
* Deliver activity to followers
|
||||
* @param actor
|
||||
* @param activity Activity
|
||||
* @param from Followee
|
||||
*/
|
||||
@bindThis
|
||||
public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity) {
|
||||
public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: any) {
|
||||
const manager = new DeliverManager(
|
||||
this.userEntityService,
|
||||
this.followingsRepository,
|
||||
@@ -66,12 +64,11 @@ export class ApDeliverManagerService {
|
||||
|
||||
/**
|
||||
* Deliver activity to user
|
||||
* @param actor
|
||||
* @param activity Activity
|
||||
* @param to Target user
|
||||
*/
|
||||
@bindThis
|
||||
public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser) {
|
||||
public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: any, to: RemoteUser) {
|
||||
const manager = new DeliverManager(
|
||||
this.userEntityService,
|
||||
this.followingsRepository,
|
||||
@@ -84,7 +81,7 @@ export class ApDeliverManagerService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null) {
|
||||
public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) {
|
||||
return new DeliverManager(
|
||||
this.userEntityService,
|
||||
this.followingsRepository,
|
||||
@@ -97,15 +94,12 @@ export class ApDeliverManagerService {
|
||||
}
|
||||
|
||||
class DeliverManager {
|
||||
private actor: ThinUser;
|
||||
private activity: IActivity | null;
|
||||
private actor: { id: User['id']; host: null; };
|
||||
private activity: any;
|
||||
private recipes: IRecipe[] = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param userEntityService
|
||||
* @param followingsRepository
|
||||
* @param queueService
|
||||
* @param actor Actor
|
||||
* @param activity Activity to deliver
|
||||
*/
|
||||
@@ -115,15 +109,9 @@ class DeliverManager {
|
||||
private queueService: QueueService,
|
||||
|
||||
actor: { id: User['id']; host: null; },
|
||||
activity: IActivity | null,
|
||||
activity: any,
|
||||
) {
|
||||
// 型で弾いてはいるが一応ローカルユーザーかチェック
|
||||
if (actor.host != null) throw new Error('actor.host must be null');
|
||||
|
||||
// パフォーマンス向上のためキューに突っ込むのはidのみに絞る
|
||||
this.actor = {
|
||||
id: actor.id,
|
||||
};
|
||||
this.actor = actor;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@@ -167,8 +155,9 @@ class DeliverManager {
|
||||
*/
|
||||
@bindThis
|
||||
public async execute() {
|
||||
if (!this.userEntityService.isLocalUser(this.actor)) return;
|
||||
|
||||
// The value flags whether it is shared or not.
|
||||
// key: inbox URL, value: whether it is sharedInbox
|
||||
const inboxes = new Map<string, boolean>();
|
||||
|
||||
/*
|
||||
@@ -212,6 +201,9 @@ class DeliverManager {
|
||||
.forEach(recipe => inboxes.set(recipe.to.inbox!, false));
|
||||
|
||||
// deliver
|
||||
this.queueService.deliverMany(this.actor, this.activity, inboxes);
|
||||
for (const inbox of inboxes) {
|
||||
// inbox[0]: inbox, inbox[1]: whether it is sharedInbox
|
||||
this.queueService.deliver(this.actor, this.activity, inbox[0], inbox[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,10 +10,9 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { ApResolverService } from '../ApResolverService.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import type { IObject } from '../type.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApImageService {
|
||||
@@ -38,22 +37,18 @@ export class ApImageService {
|
||||
* Imageを作成します。
|
||||
*/
|
||||
@bindThis
|
||||
public async createImage(actor: RemoteUser, value: string | IObject): Promise<DriveFile> {
|
||||
public async createImage(actor: RemoteUser, value: any): Promise<DriveFile> {
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
}
|
||||
|
||||
const image = await this.apResolverService.createResolver().resolve(value);
|
||||
const image = await this.apResolverService.createResolver().resolve(value) as any;
|
||||
|
||||
if (image.url == null) {
|
||||
throw new Error('invalid image: url not privided');
|
||||
}
|
||||
|
||||
if (typeof image.url !== 'string') {
|
||||
throw new Error('invalid image: unexpected type of url: ' + JSON.stringify(image.url, null, 2));
|
||||
}
|
||||
|
||||
if (!checkHttps(image.url)) {
|
||||
throw new Error('invalid image: unexpected schema of url: ' + image.url);
|
||||
}
|
||||
@@ -62,19 +57,29 @@ export class ApImageService {
|
||||
|
||||
const instance = await this.metaService.fetch();
|
||||
|
||||
const file = await this.driveService.uploadFromUrl({
|
||||
let file = await this.driveService.uploadFromUrl({
|
||||
url: image.url,
|
||||
user: actor,
|
||||
uri: image.url,
|
||||
sensitive: image.sensitive,
|
||||
isLink: !instance.cacheRemoteFiles,
|
||||
comment: truncate(image.name ?? undefined, DB_MAX_IMAGE_COMMENT_LENGTH),
|
||||
comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH),
|
||||
});
|
||||
if (!file.isLink || file.url === image.url) return file;
|
||||
|
||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、URLを更新する
|
||||
await this.driveFilesRepository.update({ id: file.id }, { url: image.url, uri: image.url });
|
||||
return await this.driveFilesRepository.findOneByOrFail({ id: file.id });
|
||||
if (file.isLink) {
|
||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
||||
// URLを更新する
|
||||
if (file.url !== image.url) {
|
||||
await this.driveFilesRepository.update({ id: file.id }, {
|
||||
url: image.url,
|
||||
uri: image.url,
|
||||
});
|
||||
|
||||
file = await this.driveFilesRepository.findOneByOrFail({ id: file.id });
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +89,7 @@ export class ApImageService {
|
||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
@bindThis
|
||||
public async resolveImage(actor: RemoteUser, value: string | IObject): Promise<DriveFile> {
|
||||
public async resolveImage(actor: RemoteUser, value: any): Promise<DriveFile> {
|
||||
// TODO
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
|
@@ -22,8 +22,8 @@ export class ApMentionService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise<User[]> {
|
||||
const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href));
|
||||
public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) {
|
||||
const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string));
|
||||
|
||||
const limit = promiseLimit<User | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
|
@@ -20,6 +20,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApMfmService } from '../ApMfmService.js';
|
||||
import { ApDbResolverService } from '../ApDbResolverService.js';
|
||||
@@ -71,9 +72,13 @@ export class ApNoteService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public validateNote(object: IObject, uri: string): Error | null {
|
||||
public validateNote(object: IObject, uri: string) {
|
||||
const expectHost = this.utilityService.extractDbHost(uri);
|
||||
|
||||
if (object == null) {
|
||||
return new Error('invalid Note: object is null');
|
||||
}
|
||||
|
||||
if (!validPost.includes(getApType(object))) {
|
||||
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
|
||||
}
|
||||
@@ -105,7 +110,6 @@ export class ApNoteService {
|
||||
*/
|
||||
@bindThis
|
||||
public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(value);
|
||||
@@ -113,10 +117,12 @@ export class ApNoteService {
|
||||
const entryUri = getApId(value);
|
||||
const err = this.validateNote(object, entryUri);
|
||||
if (err) {
|
||||
this.logger.error(err.message, {
|
||||
resolver: { history: resolver.getHistory() },
|
||||
value,
|
||||
object,
|
||||
this.logger.error(`${err.message}`, {
|
||||
resolver: {
|
||||
history: resolver.getHistory(),
|
||||
},
|
||||
value: value,
|
||||
object: object,
|
||||
});
|
||||
throw new Error('invalid note');
|
||||
}
|
||||
@@ -138,11 +144,7 @@ export class ApNoteService {
|
||||
this.logger.info(`Creating the Note: ${note.id}`);
|
||||
|
||||
// 投稿者をフェッチ
|
||||
if (note.attributedTo == null) {
|
||||
throw new Error('invalid note.attributedTo: ' + note.attributedTo);
|
||||
}
|
||||
|
||||
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as RemoteUser;
|
||||
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo!), resolver) as RemoteUser;
|
||||
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
@@ -162,49 +164,59 @@ export class ApNoteService {
|
||||
}
|
||||
|
||||
const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
|
||||
const apHashtags = extractApHashtags(note.tag);
|
||||
const apHashtags = await extractApHashtags(note.tag);
|
||||
|
||||
// 添付ファイル
|
||||
// TODO: attachmentは必ずしもImageではない
|
||||
// TODO: attachmentは必ずしも配列ではない
|
||||
const limit = promiseLimit<DriveFile>(2);
|
||||
const files = (await Promise.all(toArray(note.attachment).map(attach => (
|
||||
limit(() => this.apImageService.resolveImage(actor, {
|
||||
...attach,
|
||||
sensitive: note.sensitive, // Noteがsensitiveなら添付もsensitiveにする
|
||||
}))
|
||||
))));
|
||||
// Noteがsensitiveなら添付もsensitiveにする
|
||||
const limit = promiseLimit(2);
|
||||
|
||||
note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : [];
|
||||
const files = note.attachment
|
||||
.map(attach => attach.sensitive = note.sensitive)
|
||||
? (await Promise.all(note.attachment.map(x => limit(() => this.apImageService.resolveImage(actor, x)) as Promise<DriveFile>)))
|
||||
.filter(image => image != null)
|
||||
: [];
|
||||
|
||||
// リプライ
|
||||
const reply: Note | null = note.inReplyTo
|
||||
? await this.resolveNote(note.inReplyTo, resolver)
|
||||
.then(x => {
|
||||
? await this.resolveNote(note.inReplyTo, resolver).then(x => {
|
||||
if (x == null) {
|
||||
this.logger.warn('Specified inReplyTo, but not found');
|
||||
throw new Error('inReplyTo not found');
|
||||
}
|
||||
|
||||
} else {
|
||||
return x;
|
||||
})
|
||||
.catch(async err => {
|
||||
}
|
||||
}).catch(async err => {
|
||||
this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`);
|
||||
throw err;
|
||||
})
|
||||
: null;
|
||||
|
||||
// 引用
|
||||
let quote: Note | undefined | null = null;
|
||||
let quote: Note | undefined | null;
|
||||
|
||||
if (note._misskey_quote || note.quoteUrl) {
|
||||
const tryResolveNote = async (uri: string): Promise<
|
||||
| { status: 'ok'; res: Note }
|
||||
| { status: 'permerror' | 'temperror' }
|
||||
> => {
|
||||
if (!uri.match(/^https?:/)) return { status: 'permerror' };
|
||||
const tryResolveNote = async (uri: string): Promise<{
|
||||
status: 'ok';
|
||||
res: Note | null;
|
||||
} | {
|
||||
status: 'permerror' | 'temperror';
|
||||
}> => {
|
||||
if (typeof uri !== 'string' || !uri.match(/^https?:/)) return { status: 'permerror' };
|
||||
try {
|
||||
const res = await this.resolveNote(uri);
|
||||
if (res == null) return { status: 'permerror' };
|
||||
return { status: 'ok', res };
|
||||
if (res) {
|
||||
return {
|
||||
status: 'ok',
|
||||
res,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: 'permerror',
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror',
|
||||
@@ -213,9 +225,9 @@ export class ApNoteService {
|
||||
};
|
||||
|
||||
const uris = unique([note._misskey_quote, note.quoteUrl].filter((x): x is string => typeof x === 'string'));
|
||||
const results = await Promise.all(uris.map(tryResolveNote));
|
||||
const results = await Promise.all(uris.map(uri => tryResolveNote(uri)));
|
||||
|
||||
quote = results.filter((x): x is { status: 'ok', res: Note } => x.status === 'ok').map(x => x.res).at(0);
|
||||
quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x);
|
||||
if (!quote) {
|
||||
if (results.some(x => x.status === 'temperror')) {
|
||||
throw new Error('quote resolve failed');
|
||||
@@ -259,7 +271,7 @@ export class ApNoteService {
|
||||
|
||||
const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => {
|
||||
this.logger.info(`extractEmojis: ${e}`);
|
||||
return [];
|
||||
return [] as Emoji[];
|
||||
});
|
||||
|
||||
const apEmojis = emojis.map(emoji => emoji.name);
|
||||
@@ -297,18 +309,19 @@ export class ApNoteService {
|
||||
const uri = typeof value === 'string' ? value : value.id;
|
||||
if (uri == null) throw new Error('missing uri');
|
||||
|
||||
// ブロックしていたら中断
|
||||
// ブロックしてたら中断
|
||||
const meta = await this.metaService.fetch();
|
||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
|
||||
throw new StatusError('blocked host', 451);
|
||||
}
|
||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451);
|
||||
|
||||
const unlock = await this.appLockService.getApLock(uri);
|
||||
|
||||
try {
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await this.fetchNote(uri);
|
||||
if (exist) return exist;
|
||||
|
||||
if (exist) {
|
||||
return exist;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
if (uri.startsWith(this.config.url)) {
|
||||
@@ -326,41 +339,43 @@ export class ApNoteService {
|
||||
|
||||
@bindThis
|
||||
public async extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
host = this.utilityService.toPuny(host);
|
||||
|
||||
if (!tags) return [];
|
||||
|
||||
const eomjiTags = toArray(tags).filter(isEmoji);
|
||||
|
||||
const existingEmojis = await this.emojisRepository.findBy({
|
||||
host,
|
||||
name: In(eomjiTags.map(tag => tag.name.replaceAll(':', ''))),
|
||||
name: In(eomjiTags.map(tag => tag.name!.replaceAll(':', ''))),
|
||||
});
|
||||
|
||||
return await Promise.all(eomjiTags.map(async tag => {
|
||||
const name = tag.name.replaceAll(':', '');
|
||||
const name = tag.name!.replaceAll(':', '');
|
||||
tag.icon = toSingle(tag.icon);
|
||||
|
||||
const exists = existingEmojis.find(x => x.name === name);
|
||||
|
||||
if (exists) {
|
||||
if ((exists.updatedAt == null)
|
||||
if ((tag.updated != null && exists.updatedAt == null)
|
||||
|| (tag.id != null && exists.uri == null)
|
||||
|| (new Date(tag.updated) > exists.updatedAt)
|
||||
|| (tag.icon.url !== exists.originalUrl)
|
||||
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|
||||
|| (tag.icon!.url !== exists.originalUrl)
|
||||
) {
|
||||
await this.emojisRepository.update({
|
||||
host,
|
||||
name,
|
||||
}, {
|
||||
uri: tag.id,
|
||||
originalUrl: tag.icon.url,
|
||||
publicUrl: tag.icon.url,
|
||||
originalUrl: tag.icon!.url,
|
||||
publicUrl: tag.icon!.url,
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
const emoji = await this.emojisRepository.findOneBy({ host, name });
|
||||
if (emoji == null) throw new Error('emoji update failed');
|
||||
return emoji;
|
||||
return await this.emojisRepository.findOneBy({
|
||||
host,
|
||||
name,
|
||||
}) as Emoji;
|
||||
}
|
||||
|
||||
return exists;
|
||||
@@ -373,11 +388,11 @@ export class ApNoteService {
|
||||
host,
|
||||
name,
|
||||
uri: tag.id,
|
||||
originalUrl: tag.icon.url,
|
||||
publicUrl: tag.icon.url,
|
||||
originalUrl: tag.icon!.url,
|
||||
publicUrl: tag.icon!.url,
|
||||
updatedAt: new Date(),
|
||||
aliases: [],
|
||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
} as Partial<Emoji>).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,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 { BlockingsRepository, MutingsRepository, FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
|
||||
import { User } from '@/models/entities/User.js';
|
||||
@@ -15,6 +15,7 @@ import type Logger from '@/logger.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import type { IdService } from '@/core/IdService.js';
|
||||
import type { MfmService } from '@/core/MfmService.js';
|
||||
import type { Emoji } from '@/models/entities/Emoji.js';
|
||||
import { toArray } from '@/misc/prelude/array.js';
|
||||
import type { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
@@ -47,8 +48,6 @@ import type { IActor, IObject } from '../type.js';
|
||||
const nameLength = 128;
|
||||
const summaryLength = 2048;
|
||||
|
||||
type Field = Record<'name' | 'value', string>;
|
||||
|
||||
@Injectable()
|
||||
export class ApPersonService implements OnModuleInit {
|
||||
private utilityService: UtilityService;
|
||||
@@ -95,10 +94,28 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
//private utilityService: UtilityService,
|
||||
//private userEntityService: UserEntityService,
|
||||
//private idService: IdService,
|
||||
//private globalEventService: GlobalEventService,
|
||||
//private metaService: MetaService,
|
||||
//private federatedInstanceService: FederatedInstanceService,
|
||||
//private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
||||
//private cacheService: CacheService,
|
||||
//private apResolverService: ApResolverService,
|
||||
//private apNoteService: ApNoteService,
|
||||
//private apImageService: ApImageService,
|
||||
//private apMfmService: ApMfmService,
|
||||
//private mfmService: MfmService,
|
||||
//private hashtagService: HashtagService,
|
||||
//private usersChart: UsersChart,
|
||||
//private instanceChart: InstanceChart,
|
||||
//private apLoggerService: ApLoggerService,
|
||||
) {
|
||||
}
|
||||
|
||||
onModuleInit(): void {
|
||||
onModuleInit() {
|
||||
this.utilityService = this.moduleRef.get('UtilityService');
|
||||
this.userEntityService = this.moduleRef.get('UserEntityService');
|
||||
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
||||
@@ -136,6 +153,10 @@ export class ApPersonService implements OnModuleInit {
|
||||
private validateActor(x: IObject, uri: string): IActor {
|
||||
const expectHost = this.punyHost(uri);
|
||||
|
||||
if (x == null) {
|
||||
throw new Error('invalid Actor: object is null');
|
||||
}
|
||||
|
||||
if (!isActor(x)) {
|
||||
throw new Error(`invalid Actor type '${x.type}'`);
|
||||
}
|
||||
@@ -197,19 +218,21 @@ export class ApPersonService implements OnModuleInit {
|
||||
*/
|
||||
@bindThis
|
||||
public async fetchPerson(uri: string): Promise<LocalUser | RemoteUser | null> {
|
||||
const cached = this.cacheService.uriPersonCache.get(uri) as LocalUser | RemoteUser | null | undefined;
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
const cached = this.cacheService.uriPersonCache.get(uri) as LocalUser | RemoteUser | null;
|
||||
if (cached) return cached;
|
||||
|
||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||
if (uri.startsWith(`${this.config.url}/`)) {
|
||||
const id = uri.split('/').pop();
|
||||
const u = await this.usersRepository.findOneBy({ id }) as LocalUser | null;
|
||||
const u = await this.usersRepository.findOneBy({ id }) as LocalUser;
|
||||
if (u) this.cacheService.uriPersonCache.set(uri, u);
|
||||
return u;
|
||||
}
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await this.usersRepository.findOneBy({ uri }) as LocalUser | RemoteUser | null;
|
||||
const exist = await this.usersRepository.findOneBy({ uri }) as LocalUser | RemoteUser;
|
||||
|
||||
if (exist) {
|
||||
this.cacheService.uriPersonCache.set(uri, exist);
|
||||
@@ -231,11 +254,9 @@ export class ApPersonService implements OnModuleInit {
|
||||
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(uri);
|
||||
if (object.id == null) throw new Error('invalid object.id: ' + object.id);
|
||||
const object = await resolver.resolve(uri) as any;
|
||||
|
||||
const person = this.validateActor(object, uri);
|
||||
|
||||
@@ -243,9 +264,9 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
const host = this.punyHost(object.id);
|
||||
|
||||
const fields = this.analyzeAttachments(person.attachment ?? []);
|
||||
const { fields } = this.analyzeAttachments(person.attachment ?? []);
|
||||
|
||||
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
|
||||
const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32);
|
||||
|
||||
const isBot = getApType(object) === 'Service';
|
||||
|
||||
@@ -258,7 +279,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
}
|
||||
|
||||
// Create user
|
||||
let user: RemoteUser | null = null;
|
||||
let user: RemoteUser;
|
||||
try {
|
||||
// Start transaction
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
@@ -269,16 +290,16 @@ export class ApPersonService implements OnModuleInit {
|
||||
createdAt: new Date(),
|
||||
lastFetchedAt: new Date(),
|
||||
name: truncate(person.name, nameLength),
|
||||
isLocked: person.manuallyApprovesFollowers,
|
||||
isLocked: !!person.manuallyApprovesFollowers,
|
||||
movedToUri: person.movedTo,
|
||||
movedAt: person.movedTo ? new Date() : null,
|
||||
alsoKnownAs: person.alsoKnownAs,
|
||||
isExplorable: person.discoverable,
|
||||
isExplorable: !!person.discoverable,
|
||||
username: person.preferredUsername,
|
||||
usernameLower: person.preferredUsername?.toLowerCase(),
|
||||
usernameLower: person.preferredUsername!.toLowerCase(),
|
||||
host,
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox,
|
||||
sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||
followersUri: person.followers ? getApId(person.followers) : undefined,
|
||||
featured: person.featured ? getApId(person.featured) : undefined,
|
||||
uri: person.id,
|
||||
@@ -290,9 +311,9 @@ export class ApPersonService implements OnModuleInit {
|
||||
await transactionalEntityManager.save(new UserProfile({
|
||||
userId: user.id,
|
||||
description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
|
||||
url,
|
||||
url: url,
|
||||
fields,
|
||||
birthday: bday?.[0] ?? null,
|
||||
birthday: bday ? bday[0] : null,
|
||||
location: person['vcard:Address'] ?? null,
|
||||
userHost: host,
|
||||
}));
|
||||
@@ -309,18 +330,21 @@ export class ApPersonService implements OnModuleInit {
|
||||
// duplicate key error
|
||||
if (isDuplicateKeyValueError(e)) {
|
||||
// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
|
||||
const u = await this.usersRepository.findOneBy({ uri: person.id });
|
||||
if (u == null) throw new Error('already registered');
|
||||
const u = await this.usersRepository.findOneBy({
|
||||
uri: person.id,
|
||||
});
|
||||
|
||||
if (u) {
|
||||
user = u as RemoteUser;
|
||||
} else {
|
||||
throw new Error('already registered');
|
||||
}
|
||||
} else {
|
||||
this.logger.error(e instanceof Error ? e : new Error(e as string));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (user == null) throw new Error('failed to create user: user is null');
|
||||
|
||||
// Register host
|
||||
this.federatedInstanceService.fetch(host).then(async i => {
|
||||
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
|
||||
@@ -330,26 +354,29 @@ export class ApPersonService implements OnModuleInit {
|
||||
}
|
||||
});
|
||||
|
||||
this.usersChart.update(user, true);
|
||||
this.usersChart.update(user!, true);
|
||||
|
||||
// ハッシュタグ更新
|
||||
this.hashtagService.updateUsertags(user, tags);
|
||||
this.hashtagService.updateUsertags(user!, tags);
|
||||
|
||||
//#region アバターとヘッダー画像をフェッチ
|
||||
const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => {
|
||||
if (img == null) return null;
|
||||
if (user == null) throw new Error('failed to create user: user is null');
|
||||
return this.apImageService.resolveImage(user, img).catch(() => null);
|
||||
}));
|
||||
const [avatar, banner] = await Promise.all([
|
||||
person.icon,
|
||||
person.image,
|
||||
].map(img =>
|
||||
img == null
|
||||
? Promise.resolve(null)
|
||||
: this.apImageService.resolveImage(user!, img).catch(() => null),
|
||||
));
|
||||
|
||||
const avatarId = avatar?.id ?? null;
|
||||
const bannerId = banner?.id ?? null;
|
||||
const avatarId = avatar ? avatar.id : null;
|
||||
const bannerId = banner ? banner.id : null;
|
||||
const avatarUrl = avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null;
|
||||
const bannerUrl = banner ? this.driveFileEntityService.getPublicUrl(banner) : null;
|
||||
const avatarBlurhash = avatar?.blurhash ?? null;
|
||||
const bannerBlurhash = banner?.blurhash ?? null;
|
||||
const avatarBlurhash = avatar ? avatar.blurhash : null;
|
||||
const bannerBlurhash = banner ? banner.blurhash : null;
|
||||
|
||||
await this.usersRepository.update(user.id, {
|
||||
await this.usersRepository.update(user!.id, {
|
||||
avatarId,
|
||||
bannerId,
|
||||
avatarUrl,
|
||||
@@ -358,28 +385,30 @@ export class ApPersonService implements OnModuleInit {
|
||||
bannerBlurhash,
|
||||
});
|
||||
|
||||
user.avatarId = avatarId;
|
||||
user.bannerId = bannerId;
|
||||
user.avatarUrl = avatarUrl;
|
||||
user.bannerUrl = bannerUrl;
|
||||
user.avatarBlurhash = avatarBlurhash;
|
||||
user.bannerBlurhash = bannerBlurhash;
|
||||
user!.avatarId = avatarId;
|
||||
user!.bannerId = bannerId;
|
||||
user!.avatarUrl = avatarUrl;
|
||||
user!.bannerUrl = bannerUrl;
|
||||
user!.avatarBlurhash = avatarBlurhash;
|
||||
user!.bannerBlurhash = bannerBlurhash;
|
||||
//#endregion
|
||||
|
||||
//#region カスタム絵文字取得
|
||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => {
|
||||
this.logger.info(`extractEmojis: ${err}`);
|
||||
return [];
|
||||
return [] as Emoji[];
|
||||
});
|
||||
|
||||
const emojiNames = emojis.map(emoji => emoji.name);
|
||||
|
||||
await this.usersRepository.update(user.id, { emojis: emojiNames });
|
||||
await this.usersRepository.update(user!.id, {
|
||||
emojis: emojiNames,
|
||||
});
|
||||
//#endregion
|
||||
|
||||
await this.updateFeatured(user.id, resolver).catch(err => this.logger.error(err));
|
||||
await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err));
|
||||
|
||||
return user;
|
||||
return user!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,14 +426,18 @@ export class ApPersonService implements OnModuleInit {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
// URIがこのサーバーを指しているならスキップ
|
||||
if (uri.startsWith(`${this.config.url}/`)) return;
|
||||
if (uri.startsWith(`${this.config.url}/`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//#region このサーバーに既に登録されているか
|
||||
const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser | null;
|
||||
if (exist === null) return;
|
||||
|
||||
if (exist === null) {
|
||||
return;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
const object = hint ?? await resolver.resolve(uri);
|
||||
@@ -414,22 +447,26 @@ export class ApPersonService implements OnModuleInit {
|
||||
this.logger.info(`Updating the Person: ${person.id}`);
|
||||
|
||||
// アバターとヘッダー画像をフェッチ
|
||||
const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => {
|
||||
if (img == null) return null;
|
||||
return this.apImageService.resolveImage(exist, img).catch(() => null);
|
||||
}));
|
||||
const [avatar, banner] = await Promise.all([
|
||||
person.icon,
|
||||
person.image,
|
||||
].map(img =>
|
||||
img == null
|
||||
? Promise.resolve(null)
|
||||
: this.apImageService.resolveImage(exist, img).catch(() => null),
|
||||
));
|
||||
|
||||
// カスタム絵文字取得
|
||||
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
|
||||
this.logger.info(`extractEmojis: ${e}`);
|
||||
return [];
|
||||
return [] as Emoji[];
|
||||
});
|
||||
|
||||
const emojiNames = emojis.map(emoji => emoji.name);
|
||||
|
||||
const fields = this.analyzeAttachments(person.attachment ?? []);
|
||||
const { fields } = this.analyzeAttachments(person.attachment ?? []);
|
||||
|
||||
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
|
||||
const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32);
|
||||
|
||||
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
|
||||
|
||||
@@ -442,7 +479,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
const updates = {
|
||||
lastFetchedAt: new Date(),
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox,
|
||||
sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||
followersUri: person.followers ? getApId(person.followers) : undefined,
|
||||
featured: person.featured,
|
||||
emojis: emojiNames,
|
||||
@@ -450,29 +487,18 @@ export class ApPersonService implements OnModuleInit {
|
||||
tags,
|
||||
isBot: getApType(object) === 'Service',
|
||||
isCat: (person as any).isCat === true,
|
||||
isLocked: person.manuallyApprovesFollowers,
|
||||
isLocked: !!person.manuallyApprovesFollowers,
|
||||
movedToUri: person.movedTo ?? null,
|
||||
alsoKnownAs: person.alsoKnownAs ?? null,
|
||||
isExplorable: person.discoverable,
|
||||
isExplorable: !!person.discoverable,
|
||||
} as Partial<RemoteUser> & Pick<RemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
|
||||
|
||||
const moving = ((): boolean => {
|
||||
const moving =
|
||||
// 移行先がない→ある
|
||||
if (
|
||||
exist.movedToUri === null &&
|
||||
updates.movedToUri
|
||||
) return true;
|
||||
|
||||
(!exist.movedToUri && updates.movedToUri) ||
|
||||
// 移行先がある→別のもの
|
||||
if (
|
||||
exist.movedToUri !== null &&
|
||||
updates.movedToUri !== null &&
|
||||
exist.movedToUri !== updates.movedToUri
|
||||
) return true;
|
||||
|
||||
(exist.movedToUri !== updates.movedToUri && exist.movedToUri && updates.movedToUri);
|
||||
// 移行先がある→ない、ない→ないは無視
|
||||
return false;
|
||||
})();
|
||||
|
||||
if (moving) updates.movedAt = new Date();
|
||||
|
||||
@@ -499,10 +525,10 @@ export class ApPersonService implements OnModuleInit {
|
||||
}
|
||||
|
||||
await this.userProfilesRepository.update({ userId: exist.id }, {
|
||||
url,
|
||||
url: url,
|
||||
fields,
|
||||
description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
|
||||
birthday: bday?.[0] ?? null,
|
||||
birthday: bday ? bday[0] : null,
|
||||
location: person['vcard:Address'] ?? null,
|
||||
});
|
||||
|
||||
@@ -512,10 +538,11 @@ export class ApPersonService implements OnModuleInit {
|
||||
this.hashtagService.updateUsertags(exist, tags);
|
||||
|
||||
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
|
||||
await this.followingsRepository.update(
|
||||
{ followerId: exist.id },
|
||||
{ followerSharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox },
|
||||
);
|
||||
await this.followingsRepository.update({
|
||||
followerId: exist.id,
|
||||
}, {
|
||||
followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||
});
|
||||
|
||||
await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err));
|
||||
|
||||
@@ -553,22 +580,27 @@ export class ApPersonService implements OnModuleInit {
|
||||
*/
|
||||
@bindThis
|
||||
public async resolvePerson(uri: string, resolver?: Resolver): Promise<LocalUser | RemoteUser> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await this.fetchPerson(uri);
|
||||
if (exist) return exist;
|
||||
|
||||
if (exist) {
|
||||
return exist;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
return await this.createPerson(uri, resolver);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
// TODO: `attachments`が`IObject`だった場合、返り値が`[]`になるようだが構わないのか?
|
||||
public analyzeAttachments(attachments: IObject | IObject[] | undefined): Field[] {
|
||||
const fields: Field[] = [];
|
||||
|
||||
public analyzeAttachments(attachments: IObject | IObject[] | undefined) {
|
||||
const fields: {
|
||||
name: string,
|
||||
value: string
|
||||
}[] = [];
|
||||
if (Array.isArray(attachments)) {
|
||||
for (const attachment of attachments.filter(isPropertyValue)) {
|
||||
fields.push({
|
||||
@@ -578,11 +610,11 @@ export class ApPersonService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
return { fields };
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async updateFeatured(userId: User['id'], resolver?: Resolver): Promise<void> {
|
||||
public async updateFeatured(userId: User['id'], resolver?: Resolver) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
if (!this.userEntityService.isRemoteUser(user)) return;
|
||||
if (!user.featured) return;
|
||||
@@ -611,13 +643,13 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
// とりあえずidを別の時間で生成して順番を維持
|
||||
let td = 0;
|
||||
for (const note of featuredNotes.filter((note): note is Note => note != null)) {
|
||||
for (const note of featuredNotes.filter(note => note != null)) {
|
||||
td -= 1000;
|
||||
transactionalEntityManager.insert(UserNotePining, {
|
||||
id: this.idService.genId(new Date(Date.now() + td)),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
noteId: note.id,
|
||||
noteId: note!.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@@ -4,12 +4,12 @@ import type { NotesRepository, PollsRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { IPoll } from '@/models/entities/Poll.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isQuestion } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApResolverService } from '../ApResolverService.js';
|
||||
import type { Resolver } from '../ApResolverService.js';
|
||||
import type { IObject, IQuestion } from '../type.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApQuestionService {
|
||||
@@ -33,25 +33,33 @@ export class ApQuestionService {
|
||||
|
||||
@bindThis
|
||||
public async extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
const question = await resolver.resolve(source);
|
||||
if (!isQuestion(question)) throw new Error('invalid type');
|
||||
|
||||
const multiple = question.oneOf === undefined;
|
||||
if (multiple && question.anyOf === undefined) throw new Error('invalid question');
|
||||
if (!isQuestion(question)) {
|
||||
throw new Error('invalid type');
|
||||
}
|
||||
|
||||
const multiple = !question.oneOf;
|
||||
const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null;
|
||||
|
||||
const choices = question[multiple ? 'anyOf' : 'oneOf']
|
||||
?.map((x) => x.name)
|
||||
.filter((x): x is string => typeof x === 'string')
|
||||
?? [];
|
||||
if (multiple && !question.anyOf) {
|
||||
throw new Error('invalid question');
|
||||
}
|
||||
|
||||
const votes = question[multiple ? 'anyOf' : 'oneOf']?.map((x) => x.replies?.totalItems ?? x._misskey_votes ?? 0);
|
||||
const choices = question[multiple ? 'anyOf' : 'oneOf']!
|
||||
.map((x, i) => x.name!);
|
||||
|
||||
return { choices, votes, multiple, expiresAt };
|
||||
const votes = question[multiple ? 'anyOf' : 'oneOf']!
|
||||
.map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0);
|
||||
|
||||
return {
|
||||
choices,
|
||||
votes,
|
||||
multiple,
|
||||
expiresAt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,9 +68,8 @@ export class ApQuestionService {
|
||||
* @returns true if updated
|
||||
*/
|
||||
@bindThis
|
||||
public async updateQuestion(value: string | IObject, resolver?: Resolver): Promise<boolean> {
|
||||
public async updateQuestion(value: any, resolver?: Resolver) {
|
||||
const uri = typeof value === 'string' ? value : value.id;
|
||||
if (uri == null) throw new Error('uri is null');
|
||||
|
||||
// URIがこのサーバーを指しているならスキップ
|
||||
if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local');
|
||||
@@ -76,7 +83,6 @@ export class ApQuestionService {
|
||||
//#endregion
|
||||
|
||||
// resolve new Question object
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
const question = await resolver.resolve(value) as IQuestion;
|
||||
this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
||||
@@ -84,14 +90,12 @@ export class ApQuestionService {
|
||||
if (question.type !== 'Question') throw new Error('object is not a Question');
|
||||
|
||||
const apChoices = question.oneOf ?? question.anyOf;
|
||||
if (apChoices == null) throw new Error('invalid apChoices: ' + apChoices);
|
||||
|
||||
let changed = false;
|
||||
|
||||
for (const choice of poll.choices) {
|
||||
const oldCount = poll.votes[poll.choices.indexOf(choice)];
|
||||
const newCount = apChoices.filter(ap => ap.name === choice).at(0)?.replies?.totalItems;
|
||||
if (newCount == null) throw new Error('invalid newCount: ' + newCount);
|
||||
const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems;
|
||||
|
||||
if (oldCount !== newCount) {
|
||||
changed = true;
|
||||
@@ -99,7 +103,9 @@ export class ApQuestionService {
|
||||
}
|
||||
}
|
||||
|
||||
await this.pollsRepository.update({ noteId: note.id }, { votes: poll.votes });
|
||||
await this.pollsRepository.update({ noteId: note.id }, {
|
||||
votes: poll.votes,
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { toArray } from '@/misc/prelude/array.js';
|
||||
import { isHashtag } from '../type.js';
|
||||
import type { IObject, IApHashtag } from '../type.js';
|
||||
|
||||
export function extractApHashtags(tags: IObject | IObject[] | null | undefined): string[] {
|
||||
export function extractApHashtags(tags: IObject | IObject[] | null | undefined) {
|
||||
if (tags == null) return [];
|
||||
|
||||
const hashtags = extractApHashtagObjects(tags);
|
||||
|
@@ -3,7 +3,6 @@ import si from 'systeminformation';
|
||||
import Xev from 'xev';
|
||||
import * as osUtils from 'os-utils';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
const ev = new Xev();
|
||||
@@ -15,10 +14,9 @@ const round = (num: number) => Math.round(num * 10) / 10;
|
||||
|
||||
@Injectable()
|
||||
export class ServerStatsService implements OnApplicationShutdown {
|
||||
private intervalId: NodeJS.Timer | null = null;
|
||||
private intervalId: NodeJS.Timer;
|
||||
|
||||
constructor(
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -26,9 +24,7 @@ export class ServerStatsService implements OnApplicationShutdown {
|
||||
* Report server stats regularly
|
||||
*/
|
||||
@bindThis
|
||||
public async start(): Promise<void> {
|
||||
if (!(await this.metaService.fetch(true)).enableServerMachineStats) return;
|
||||
|
||||
public start(): void {
|
||||
const log = [] as any[];
|
||||
|
||||
ev.on('requestServerStatsLog', x => {
|
||||
@@ -68,10 +64,8 @@ export class ServerStatsService implements OnApplicationShutdown {
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
export function checkHttps(url: string): boolean {
|
||||
export function checkHttps(url: string) {
|
||||
return url.startsWith('https://') ||
|
||||
(url.startsWith('http://') && process.env.NODE_ENV !== 'production');
|
||||
}
|
||||
|
@@ -55,10 +55,7 @@ export class Ad {
|
||||
length: 8192, nullable: false,
|
||||
})
|
||||
public memo: string;
|
||||
@Column('integer', {
|
||||
default: 0, nullable: false,
|
||||
})
|
||||
public dayOfWeek: number;
|
||||
|
||||
constructor(data: Partial<Ad>) {
|
||||
if (data == null) return;
|
||||
|
||||
|
@@ -413,16 +413,6 @@ export class Meta {
|
||||
})
|
||||
public enableChartsForFederatedInstances: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableServerMachineStats: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public enableIdenticonGeneration: boolean;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: { },
|
||||
})
|
||||
|
@@ -369,7 +369,7 @@ export class ActivityPubServerService {
|
||||
}))
|
||||
.andWhere('note.localOnly = FALSE');
|
||||
|
||||
const notes = await query.limit(limit).getMany();
|
||||
const notes = await query.take(limit).getMany();
|
||||
|
||||
if (sinceId) notes.reverse();
|
||||
|
||||
|
@@ -36,6 +36,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
|
||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
||||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -78,6 +79,7 @@ import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.
|
||||
ServerStatsChannelService,
|
||||
UserListChannelService,
|
||||
OpenApiServerService,
|
||||
OAuth2ProviderService,
|
||||
],
|
||||
exports: [
|
||||
ServerService,
|
||||
|
@@ -16,7 +16,6 @@ import { createTemp } from '@/misc/create-temp.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ActivityPubServerService } from './ActivityPubServerService.js';
|
||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||
import { ApiServerService } from './api/ApiServerService.js';
|
||||
@@ -25,6 +24,7 @@ import { WellKnownServerService } from './WellKnownServerService.js';
|
||||
import { FileServerService } from './FileServerService.js';
|
||||
import { ClientServerService } from './web/ClientServerService.js';
|
||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||
|
||||
const _dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
@@ -46,7 +46,6 @@ export class ServerService implements OnApplicationShutdown {
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private metaService: MetaService,
|
||||
private userEntityService: UserEntityService,
|
||||
private apiServerService: ApiServerService,
|
||||
private openApiServerService: OpenApiServerService,
|
||||
@@ -58,12 +57,13 @@ export class ServerService implements OnApplicationShutdown {
|
||||
private clientServerService: ClientServerService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private loggerService: LoggerService,
|
||||
private oauth2ProviderService: OAuth2ProviderService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('server', 'gray', false);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async launch() {
|
||||
public async launch(): Promise<void> {
|
||||
const fastify = Fastify({
|
||||
trustProxy: true,
|
||||
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
|
||||
@@ -92,6 +92,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||
fastify.register(this.activityPubServerService.createServer);
|
||||
fastify.register(this.nodeinfoServerService.createServer);
|
||||
fastify.register(this.wellKnownServerService.createServer);
|
||||
fastify.register(this.oauth2ProviderService.createServer);
|
||||
|
||||
fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
|
||||
const path = request.params.path;
|
||||
@@ -163,16 +164,11 @@ export class ServerService implements OnApplicationShutdown {
|
||||
});
|
||||
|
||||
fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => {
|
||||
reply.header('Content-Type', 'image/png');
|
||||
reply.header('Cache-Control', 'public, max-age=86400');
|
||||
|
||||
if ((await this.metaService.fetch()).enableIdenticonGeneration) {
|
||||
const [temp, cleanup] = await createTemp();
|
||||
await genIdenticon(request.params.x, fs.createWriteStream(temp));
|
||||
reply.header('Content-Type', 'image/png');
|
||||
reply.header('Cache-Control', 'public, max-age=86400');
|
||||
return fs.createReadStream(temp).on('close', () => cleanup());
|
||||
} else {
|
||||
return reply.redirect('/static-assets/avatar.png');
|
||||
}
|
||||
});
|
||||
|
||||
fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => {
|
||||
|
@@ -103,13 +103,6 @@ export class StreamingApiServerService {
|
||||
});
|
||||
});
|
||||
|
||||
const globalEv = new EventEmitter();
|
||||
|
||||
this.redisForSub.on('message', (_: string, data: string) => {
|
||||
const parsed = JSON.parse(data);
|
||||
globalEv.emit('message', parsed);
|
||||
});
|
||||
|
||||
this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: {
|
||||
stream: MainStreamConnection,
|
||||
user: LocalUser | null;
|
||||
@@ -119,11 +112,12 @@ export class StreamingApiServerService {
|
||||
|
||||
const ev = new EventEmitter();
|
||||
|
||||
function onRedisMessage(data: any): void {
|
||||
ev.emit(data.channel, data.message);
|
||||
async function onRedisMessage(_: string, data: string): Promise<void> {
|
||||
const parsed = JSON.parse(data);
|
||||
ev.emit(parsed.channel, parsed.message);
|
||||
}
|
||||
|
||||
globalEv.on('message', onRedisMessage);
|
||||
this.redisForSub.on('message', onRedisMessage);
|
||||
|
||||
await stream.listen(ev, connection);
|
||||
|
||||
@@ -143,7 +137,7 @@ export class StreamingApiServerService {
|
||||
connection.once('close', () => {
|
||||
ev.removeAllListeners();
|
||||
stream.dispose();
|
||||
globalEv.off('message', onRedisMessage);
|
||||
this.redisForSub.off('message', onRedisMessage);
|
||||
this.#connections.delete(connection);
|
||||
if (userUpdateIntervalId) clearInterval(userUpdateIntervalId);
|
||||
});
|
||||
|
@@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break;
|
||||
}
|
||||
|
||||
const reports = await query.limit(ps.limit).getMany();
|
||||
const reports = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.abuseUserReportEntityService.packMany(reports);
|
||||
});
|
||||
|
@@ -22,9 +22,8 @@ export const paramDef = {
|
||||
expiresAt: { type: 'integer' },
|
||||
startsAt: { type: 'integer' },
|
||||
imageUrl: { type: 'string', minLength: 1 },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
},
|
||||
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'],
|
||||
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -42,7 +41,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
url: ps.url,
|
||||
imageUrl: ps.imageUrl,
|
||||
priority: ps.priority,
|
||||
|
@@ -32,7 +32,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
|
||||
const ads = await query.limit(ps.limit).getMany();
|
||||
const ads = await query.take(ps.limit).getMany();
|
||||
|
||||
return ads;
|
||||
});
|
||||
|
@@ -31,9 +31,8 @@ export const paramDef = {
|
||||
ratio: { type: 'integer' },
|
||||
expiresAt: { type: 'integer' },
|
||||
startsAt: { type: 'integer' },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'],
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -57,7 +56,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
|
||||
|
||||
const announcements = await query.limit(ps.limit).getMany();
|
||||
const announcements = await query.take(ps.limit).getMany();
|
||||
|
||||
const reads = new Map<Announcement, number>();
|
||||
|
||||
|
@@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
}
|
||||
|
||||
const files = await query.limit(ps.limit).getMany();
|
||||
const files = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.driveFileEntityService.packMany(files, { detail: true, withUser: true, self: true });
|
||||
});
|
||||
|
@@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
const emojis = await q
|
||||
.orderBy('emoji.id', 'DESC')
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return this.emojiEntityService.packDetailedMany(emojis);
|
||||
|
@@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
if (ps.query) {
|
||||
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
//const emojis = await q.limit(ps.limit).getMany();
|
||||
//const emojis = await q.take(ps.limit).getMany();
|
||||
|
||||
emojis = await q.getMany();
|
||||
const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
|
||||
@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
emojis.splice(ps.limit + 1);
|
||||
} else {
|
||||
emojis = await q.limit(ps.limit).getMany();
|
||||
emojis = await q.take(ps.limit).getMany();
|
||||
}
|
||||
|
||||
return this.emojiEntityService.packDetailedMany(emojis);
|
||||
|
@@ -262,14 +262,6 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableServerMachineStats: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableIdenticonGeneration: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
policies: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
@@ -372,8 +364,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
||||
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
|
||||
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
|
||||
enableServerMachineStats: instance.enableServerMachineStats,
|
||||
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||
};
|
||||
});
|
||||
|
@@ -33,17 +33,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
delayedQueues = await this.queueService.deliverQueue.getDelayed();
|
||||
for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
|
||||
const queue = delayedQueues[queueIndex];
|
||||
try {
|
||||
await queue.promote();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e.message.indexOf('not in a delayed state') !== -1) {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -51,17 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
delayedQueues = await this.queueService.inboxQueue.getDelayed();
|
||||
for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
|
||||
const queue = delayedQueues[queueIndex];
|
||||
try {
|
||||
await queue.promote();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e.message.indexOf('not in a delayed state') !== -1) {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@@ -64,7 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.innerJoinAndSelect('assign.user', 'user');
|
||||
|
||||
const assigns = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(assigns.map(async assign => ({
|
||||
|
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
|
||||
|
||||
const reports = await query.limit(ps.limit).getMany();
|
||||
const reports = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.moderationLogEntityService.packMany(reports);
|
||||
});
|
||||
|
@@ -104,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
default: query.orderBy('user.id', 'ASC'); break;
|
||||
}
|
||||
|
||||
query.limit(ps.limit);
|
||||
query.take(ps.limit);
|
||||
query.skip(ps.offset);
|
||||
|
||||
const users = await query.getMany();
|
||||
|
@@ -96,8 +96,6 @@ export const paramDef = {
|
||||
enableActiveEmailValidation: { type: 'boolean' },
|
||||
enableChartsForRemoteUser: { type: 'boolean' },
|
||||
enableChartsForFederatedInstances: { type: 'boolean' },
|
||||
enableServerMachineStats: { type: 'boolean' },
|
||||
enableIdenticonGeneration: { type: 'boolean' },
|
||||
serverRules: { type: 'array', items: { type: 'string' } },
|
||||
preservedUsernames: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
@@ -401,14 +399,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
|
||||
}
|
||||
|
||||
if (ps.enableServerMachineStats !== undefined) {
|
||||
set.enableServerMachineStats = ps.enableServerMachineStats;
|
||||
}
|
||||
|
||||
if (ps.enableIdenticonGeneration !== undefined) {
|
||||
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
|
||||
}
|
||||
|
||||
if (ps.serverRules !== undefined) {
|
||||
set.serverRules = ps.serverRules;
|
||||
}
|
||||
|
@@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
|
||||
|
||||
const announcements = await query.limit(ps.limit).getMany();
|
||||
const announcements = await query.take(ps.limit).getMany();
|
||||
|
||||
if (me) {
|
||||
const reads = (await this.announcementReadsRepository.findBy({
|
||||
|
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('blocking.blockerId = :meId', { meId: me.id });
|
||||
|
||||
const blockings = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.blockingEntityService.packMany(blockings, me);
|
||||
|
@@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('channel.isArchived = FALSE')
|
||||
.orderBy('channel.lastNotedAt', 'DESC');
|
||||
|
||||
const channels = await query.limit(10).getMany();
|
||||
const channels = await query.take(10).getMany();
|
||||
|
||||
return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me)));
|
||||
});
|
||||
|
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere({ followerId: me.id });
|
||||
|
||||
const followings = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(followings.map(x => this.channelEntityService.pack(x.followeeId, me)));
|
||||
|
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere({ userId: me.id });
|
||||
|
||||
const channels = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me)));
|
||||
|
@@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
const channels = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me)));
|
||||
|
@@ -105,7 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
timeline = await query.limit(ps.limit).getMany();
|
||||
timeline = await query.take(ps.limit).getMany();
|
||||
} else {
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
|
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
const notes = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.noteEntityService.packMany(notes, me);
|
||||
|
@@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
case '-size': query.orderBy('file.size', 'ASC'); break;
|
||||
}
|
||||
|
||||
const files = await query.limit(ps.limit).getMany();
|
||||
const files = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.driveFileEntityService.packMany(files, { detail: false, self: true });
|
||||
});
|
||||
|
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
query.andWhere('folder.parentId IS NULL');
|
||||
}
|
||||
|
||||
const folders = await query.limit(ps.limit).getMany();
|
||||
const folders = await query.take(ps.limit).getMany();
|
||||
|
||||
return await Promise.all(folders.map(folder => this.driveFolderEntityService.pack(folder)));
|
||||
});
|
||||
|
@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
}
|
||||
|
||||
const files = await query.limit(ps.limit).getMany();
|
||||
const files = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.driveFileEntityService.packMany(files, { detail: false, self: true });
|
||||
});
|
||||
|
@@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('following.followeeHost = :host', { host: ps.host });
|
||||
|
||||
const followings = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.followingEntityService.packMany(followings, me, { populateFollowee: true });
|
||||
|
@@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('following.followerHost = :host', { host: ps.host });
|
||||
|
||||
const followings = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.followingEntityService.packMany(followings, me, { populateFollowee: true });
|
||||
|
@@ -126,7 +126,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
query.andWhere('instance.host like :host', { host: '%' + sqlLikeEscape(ps.host.toLowerCase()) + '%' });
|
||||
}
|
||||
|
||||
const instances = await query.limit(ps.limit).skip(ps.offset).getMany();
|
||||
const instances = await query.take(ps.limit).skip(ps.offset).getMany();
|
||||
|
||||
return await this.instanceEntityService.packMany(instances);
|
||||
});
|
||||
|
@@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('user.host = :host', { host: ps.host });
|
||||
|
||||
const users = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.userEntityService.packMany(users, me, { detail: true });
|
||||
|
@@ -40,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('flash.likedCount > 0')
|
||||
.orderBy('flash.likedCount', 'DESC');
|
||||
|
||||
const flashs = await query.limit(10).getMany();
|
||||
const flashs = await query.take(10).getMany();
|
||||
|
||||
return await this.flashEntityService.packMany(flashs, me);
|
||||
});
|
||||
|
@@ -59,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.leftJoinAndSelect('like.flash', 'flash');
|
||||
|
||||
const likes = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return this.flashLikeEntityService.packMany(likes, me);
|
||||
|
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('flash.userId = :meId', { meId: me.id });
|
||||
|
||||
const flashs = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.flashEntityService.packMany(flashs);
|
||||
|
@@ -64,7 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('request.followeeId = :meId', { meId: me.id });
|
||||
|
||||
const requests = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(requests.map(req => this.followRequestEntityService.pack(req)));
|
||||
|
@@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('post.likedCount > 0')
|
||||
.orderBy('post.likedCount', 'DESC');
|
||||
|
||||
const posts = await query.limit(10).getMany();
|
||||
const posts = await query.take(10).getMany();
|
||||
|
||||
return await this.galleryPostEntityService.packMany(posts, me);
|
||||
});
|
||||
|
@@ -40,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('post.likedCount > 0')
|
||||
.orderBy('post.likedCount', 'DESC');
|
||||
|
||||
const posts = await query.limit(10).getMany();
|
||||
const posts = await query.take(10).getMany();
|
||||
|
||||
return await this.galleryPostEntityService.packMany(posts, me);
|
||||
});
|
||||
|
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId)
|
||||
.innerJoinAndSelect('post.user', 'user');
|
||||
|
||||
const posts = await query.limit(ps.limit).getMany();
|
||||
const posts = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.galleryPostEntityService.packMany(posts, me);
|
||||
});
|
||||
|
@@ -9,8 +9,6 @@ export const meta = {
|
||||
tags: ['meta'],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 60 * 1,
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
@@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
'tag.attachedRemoteUsersCount',
|
||||
]);
|
||||
|
||||
const tags = await query.limit(ps.limit).getMany();
|
||||
const tags = await query.take(ps.limit).getMany();
|
||||
|
||||
return this.hashtagEntityService.packMany(tags);
|
||||
});
|
||||
|
@@ -41,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' })
|
||||
.orderBy('tag.count', 'DESC')
|
||||
.groupBy('tag.id')
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.skip(ps.offset)
|
||||
.getMany();
|
||||
|
||||
|
@@ -26,8 +26,6 @@ export const meta = {
|
||||
tags: ['hashtags'],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 60 * 1,
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break;
|
||||
}
|
||||
|
||||
const users = await query.limit(ps.limit).getMany();
|
||||
const users = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.userEntityService.packMany(users, me, { detail: true });
|
||||
});
|
||||
|
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.leftJoinAndSelect('favorite.note', 'note');
|
||||
|
||||
const favorites = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.noteFavoriteEntityService.packMany(favorites, me);
|
||||
|
@@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.leftJoinAndSelect('like.post', 'post');
|
||||
|
||||
const likes = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.galleryLikeEntityService.packMany(likes, me);
|
||||
|
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('post.userId = :meId', { meId: me.id });
|
||||
|
||||
const posts = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.galleryPostEntityService.packMany(posts, me);
|
||||
|
@@ -59,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.leftJoinAndSelect('like.page', 'page');
|
||||
|
||||
const likes = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return this.pageLikeEntityService.packMany(likes, me);
|
||||
|
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('page.userId = :meId', { meId: me.id });
|
||||
|
||||
const pages = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.pageEntityService.packMany(pages);
|
||||
|
@@ -35,7 +35,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId)
|
||||
.andWhere('signin.userId = :meId', { meId: me.id });
|
||||
|
||||
const history = await query.limit(ps.limit).getMany();
|
||||
const history = await query.take(ps.limit).getMany();
|
||||
|
||||
return await Promise.all(history.map(record => this.signinEntityService.pack(record)));
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm';
|
||||
import { IsNull, LessThanOrEqual, MoreThan } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import JSON5 from 'json5';
|
||||
import type { AdsRepository, UsersRepository } from '@/models/index.js';
|
||||
@@ -263,15 +263,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
const ads = await this.adsRepository.createQueryBuilder("ads")
|
||||
.where('ads.expiresAt > :now', { now: new Date() })
|
||||
.andWhere('ads.startsAt <= :now', { now: new Date() })
|
||||
.andWhere(new Brackets(qb => {
|
||||
// 曜日のビットフラグを確認する
|
||||
qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() })
|
||||
.orWhere('ads.dayOfWeek = 0');
|
||||
}))
|
||||
.getMany();
|
||||
const ads = await this.adsRepository.find({
|
||||
where: {
|
||||
expiresAt: MoreThan(new Date()),
|
||||
startsAt: LessThanOrEqual(new Date()),
|
||||
},
|
||||
});
|
||||
|
||||
const response: any = {
|
||||
maintainerName: instance.maintainerName,
|
||||
@@ -314,7 +311,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
place: ad.place,
|
||||
ratio: ad.ratio,
|
||||
imageUrl: ad.imageUrl,
|
||||
dayOfWeek: ad.dayOfWeek,
|
||||
})),
|
||||
enableEmail: instance.enableEmail,
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
|
@@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
.andWhere('muting.muterId = :meId', { meId: me.id });
|
||||
|
||||
const mutings = await query
|
||||
.limit(ps.limit)
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.mutingEntityService.packMany(mutings, me);
|
||||
|
@@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
// query.isBot = bot;
|
||||
//}
|
||||
|
||||
const notes = await query.limit(ps.limit).getMany();
|
||||
const notes = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.noteEntityService.packMany(notes);
|
||||
});
|
||||
|
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
this.queryService.generateBlockedUserQuery(query, me);
|
||||
}
|
||||
|
||||
const notes = await query.limit(ps.limit).getMany();
|
||||
const notes = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.noteEntityService.packMany(notes, me);
|
||||
});
|
||||
|
@@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
let notes = await query
|
||||
.orderBy('note.score', 'DESC')
|
||||
.limit(100)
|
||||
.take(100)
|
||||
.getMany();
|
||||
|
||||
notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
|
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await query.limit(ps.limit).getMany();
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
process.nextTick(() => {
|
||||
if (me) {
|
||||
|
@@ -137,7 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await query.limit(ps.limit).getMany();
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
process.nextTick(() => {
|
||||
this.activeUsersChart.read(me);
|
||||
|
@@ -110,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await query.limit(ps.limit).getMany();
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
|
||||
process.nextTick(() => {
|
||||
if (me) {
|
||||
|
@@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
query.setParameters(followingQuery.getParameters());
|
||||
}
|
||||
|
||||
const mentions = await query.limit(ps.limit).getMany();
|
||||
const mentions = await query.take(ps.limit).getMany();
|
||||
|
||||
this.noteReadService.read(me.id, mentions);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user