Compare commits
68 Commits
13.13.0-be
...
13.13.0-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
afa4cd9112 | ||
![]() |
f930eaee02 | ||
![]() |
b35b9bc27f | ||
![]() |
4790ddfad6 | ||
![]() |
b6f21b6edb | ||
![]() |
fd7b77c542 | ||
![]() |
7cbd852fe5 | ||
![]() |
a80003cde5 | ||
![]() |
fb54c58a66 | ||
![]() |
3a924f3dc6 | ||
![]() |
11d22c7b73 | ||
![]() |
a879607479 | ||
![]() |
98aef974df | ||
![]() |
cf46816687 | ||
![]() |
eee1e74174 | ||
![]() |
8050f89d7e | ||
![]() |
406e5d297b | ||
![]() |
10634b3615 | ||
![]() |
fd03e2e1a7 | ||
![]() |
6cc86272f3 | ||
![]() |
06b1250d47 | ||
![]() |
31a7350a10 | ||
![]() |
4129ac157a | ||
![]() |
30cb791e93 | ||
![]() |
1c57983bfd | ||
![]() |
bdf08c8a54 | ||
![]() |
0513ff8b4e | ||
![]() |
62fe3bfb54 | ||
![]() |
38a1d6693a | ||
![]() |
d2eec3a9e4 | ||
![]() |
1de774fa3d | ||
![]() |
ed902658a9 | ||
![]() |
acdcd7c623 | ||
![]() |
b0344e07c4 | ||
![]() |
9a6ce1e867 | ||
![]() |
22a6bd6b22 | ||
![]() |
38e6f3f776 | ||
![]() |
ca75afe065 | ||
![]() |
915ed39715 | ||
![]() |
81fd94e635 | ||
![]() |
05507a4bea | ||
![]() |
d177f97928 | ||
![]() |
30cb03a40d | ||
![]() |
c685989e67 | ||
![]() |
ee3f408c7d | ||
![]() |
1eb35dd5bc | ||
![]() |
15db0b8812 | ||
![]() |
1b78c6a309 | ||
![]() |
c713af8e23 | ||
![]() |
bd6666173a | ||
![]() |
02715f5d14 | ||
![]() |
acd5e0b8f6 | ||
![]() |
be2142bb13 | ||
![]() |
4a703d7cf6 | ||
![]() |
95470a40a7 | ||
![]() |
56d4658b36 | ||
![]() |
f68008b002 | ||
![]() |
6a5ef5b6f2 | ||
![]() |
95b9284e79 | ||
![]() |
8317772436 | ||
![]() |
0c0ae6ff90 | ||
![]() |
d63b943116 | ||
![]() |
dddbc1c894 | ||
![]() |
f68c743f39 | ||
![]() |
59255e11b8 | ||
![]() |
3804c6e7ad | ||
![]() |
527a13b77d | ||
![]() |
a3423bad60 |
6
.github/workflows/storybook.yml
vendored
6
.github/workflows/storybook.yml
vendored
@@ -86,7 +86,11 @@ jobs:
|
||||
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
|
||||
BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}"
|
||||
if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
fi
|
||||
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
|
||||
env:
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
- name: Notify that Chromatic detects changes
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@@ -17,13 +17,30 @@
|
||||
### General
|
||||
- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
|
||||
- カスタム絵文字ごとに連合するかどうか設定できるように
|
||||
- カスタム絵文字ごとにセンシティブフラグを設定できるように
|
||||
- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に
|
||||
- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
|
||||
- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります
|
||||
- リストを公開できるようになりました
|
||||
|
||||
### Client
|
||||
- リアクションの取り消し/変更時に確認ダイアログを出すように
|
||||
- 開発者モードを追加
|
||||
- AiScriptを0.13.3に更新
|
||||
- Deck UIを使用している場合、`/`以外にアクセスした際にZen UIで表示するように
|
||||
- メインカラムを設置していない場合の問題を解決
|
||||
- ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加
|
||||
- アカウント初期設定ウィザードに戻るボタンを追加
|
||||
- アカウントの初期設定ウィザードにあとでボタンを追加
|
||||
- Fix: URLプレビューで情報が取得できなかった際の挙動を修正
|
||||
- Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正
|
||||
- fix:ロールタイムラインが無効でも投稿が流れてしまう問題の修正
|
||||
- fix:ロールタイムラインにて全ての投稿が流れてしまう問題の修正
|
||||
|
||||
### Server
|
||||
- bullをbull-mqにアップグレードし、ジョブキューのパフォーマンスを改善
|
||||
- ストリーミングのパフォーマンスを改善
|
||||
- Fix: お知らせの画像URLを空にできない問題を修正
|
||||
|
||||
## 13.12.2
|
||||
|
||||
@@ -100,6 +117,7 @@ Meilisearchの設定に`index`が必要になりました。値はMisskeyサー
|
||||
* 画像が全て隠れた状態で表示されるようになります
|
||||
- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
|
||||
- モデレーターはノートに添付された画像上から直接NSFW設定できるように
|
||||
- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
|
||||
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
||||
- 新しい実績を追加
|
||||
- AiScriptを0.13.2に更新
|
||||
|
@@ -169,20 +169,25 @@ describe('After user signed in', () => {
|
||||
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
|
||||
// TODO: アイコン設定テスト
|
||||
|
||||
cy.get('[data-cy-user-setup-back]').click();
|
||||
cy.get('[data-cy-user-setup-continue]').click();
|
||||
|
||||
// プライバシー設定
|
||||
|
||||
cy.get('[data-cy-user-setup-back]').click();
|
||||
cy.get('[data-cy-user-setup-continue]').click();
|
||||
|
||||
// フォローはスキップ
|
||||
|
||||
cy.get('[data-cy-user-setup-back]').click();
|
||||
cy.get('[data-cy-user-setup-continue]').click();
|
||||
|
||||
// プッシュ通知設定はスキップ
|
||||
|
||||
cy.get('[data-cy-user-setup-back]').click();
|
||||
cy.get('[data-cy-user-setup-continue]').click();
|
||||
|
||||
cy.get('[data-cy-user-setup-back]').click();
|
||||
cy.get('[data-cy-user-setup-continue]').click();
|
||||
});
|
||||
});
|
||||
|
@@ -1,25 +0,0 @@
|
||||
DONATORS
|
||||
========
|
||||
The list of people who have sent donation for Misskey.
|
||||
|
||||
(In random order, honorific titles are omitted.)
|
||||
|
||||
* らふぁ
|
||||
* 俺様
|
||||
* なぎうり
|
||||
* スルメ https://surume.tk/
|
||||
* 藍
|
||||
* 音船 https://otofune.me/
|
||||
* aqz https://misskey.xyz/aqz
|
||||
* kotodu "虚無創作中"
|
||||
* Maya Minatsuki
|
||||
* Knzk https://knzk.me/@Knzk
|
||||
* ねじりわさび https://knzk.me/@y
|
||||
* NCLS https://knzk.me/@imncls]
|
||||
* こじま @skoji@sandbox.skoji.jp
|
||||
|
||||
:heart: Thanks for donating, guys!
|
||||
|
||||
---
|
||||
|
||||
If your name is missing, please contact us!
|
72
locales/generateDTS.js
Normal file
72
locales/generateDTS.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const ts = require('typescript');
|
||||
|
||||
function createMembers(record) {
|
||||
return Object.entries(record)
|
||||
.map(([k, v]) => ts.factory.createPropertySignature(
|
||||
undefined,
|
||||
ts.factory.createStringLiteral(k),
|
||||
undefined,
|
||||
typeof v === 'string'
|
||||
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
||||
: ts.factory.createTypeLiteralNode(createMembers(v)),
|
||||
));
|
||||
}
|
||||
|
||||
module.exports = function generateDTS() {
|
||||
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
|
||||
const members = createMembers(locale);
|
||||
const elements = [
|
||||
ts.factory.createInterfaceDeclaration(
|
||||
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
||||
ts.factory.createIdentifier('Locale'),
|
||||
undefined,
|
||||
undefined,
|
||||
members,
|
||||
),
|
||||
ts.factory.createVariableStatement(
|
||||
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
||||
ts.factory.createVariableDeclarationList(
|
||||
[ts.factory.createVariableDeclaration(
|
||||
ts.factory.createIdentifier('locales'),
|
||||
undefined,
|
||||
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
|
||||
undefined,
|
||||
[ts.factory.createParameterDeclaration(
|
||||
undefined,
|
||||
undefined,
|
||||
ts.factory.createIdentifier('lang'),
|
||||
undefined,
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||
undefined,
|
||||
)],
|
||||
ts.factory.createTypeReferenceNode(
|
||||
ts.factory.createIdentifier('Locale'),
|
||||
undefined,
|
||||
),
|
||||
)]),
|
||||
undefined,
|
||||
)],
|
||||
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
|
||||
),
|
||||
),
|
||||
ts.factory.createExportAssignment(
|
||||
undefined,
|
||||
true,
|
||||
ts.factory.createIdentifier('locales'),
|
||||
),
|
||||
];
|
||||
const printed = ts.createPrinter({
|
||||
newLine: ts.NewLineKind.LineFeed,
|
||||
}).printList(
|
||||
ts.ListFormat.MultiLine,
|
||||
ts.factory.createNodeArray(elements),
|
||||
ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
|
||||
);
|
||||
|
||||
fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
|
||||
// This file is generated by locales/generateDTS.js
|
||||
// Do not edit this file directly.
|
||||
${printed}`, 'utf-8');
|
||||
}
|
2149
locales/index.d.ts
vendored
2149
locales/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -792,6 +792,7 @@ noMaintainerInformationWarning: "管理者情報が設定されていません
|
||||
noBotProtectionWarning: "Botプロテクションが設定されていません。"
|
||||
configure: "設定する"
|
||||
postToGallery: "ギャラリーへ投稿"
|
||||
postToHashtag: "このハッシュタグで投稿"
|
||||
gallery: "ギャラリー"
|
||||
recentPosts: "最近の投稿"
|
||||
popularPosts: "人気の投稿"
|
||||
@@ -990,7 +991,9 @@ postToTheChannel: "チャンネルに投稿"
|
||||
cannotBeChangedLater: "後から変更できません。"
|
||||
reactionAcceptance: "リアクションの受け入れ"
|
||||
likeOnly: "いいねのみ"
|
||||
likeOnlyForRemote: "リモートからはいいねのみ"
|
||||
likeOnlyForRemote: "全て (リモートはいいねのみ)"
|
||||
nonSensitiveOnly: "非センシティブのみ"
|
||||
nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
|
||||
rolesAssignedToMe: "自分に割り当てられたロール"
|
||||
resetPasswordConfirm: "パスワードリセットしますか?"
|
||||
sensitiveWords: "センシティブワード"
|
||||
@@ -1053,6 +1056,10 @@ update: "更新"
|
||||
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
|
||||
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
|
||||
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
|
||||
cancelReactionConfirm: "リアクションを取り消しますか?"
|
||||
changeReactionConfirm: "リアクションを変更しますか?"
|
||||
later: "あとで"
|
||||
goToMisskey: "Misskeyへ"
|
||||
|
||||
_initialAccountSetting:
|
||||
accountCreated: "アカウントの作成が完了しました!"
|
||||
@@ -1068,6 +1075,7 @@ _initialAccountSetting:
|
||||
haveFun: "{name}をお楽しみください!"
|
||||
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
|
||||
skipAreYouSure: "初期設定をスキップしますか?"
|
||||
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
||||
|
||||
_serverRules:
|
||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||
|
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "13.13.0-beta.1",
|
||||
"version": "13.13.0-beta.4",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.3.1",
|
||||
"packageManager": "pnpm@8.5.1",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
@@ -59,7 +59,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||
"@typescript-eslint/parser": "5.59.5",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "12.12.0",
|
||||
"cypress": "12.13.0",
|
||||
"eslint": "8.40.0",
|
||||
"start-server-and-test": "2.0.0"
|
||||
},
|
||||
|
13
packages/backend/migration/1683847157541-UserList.js
Normal file
13
packages/backend/migration/1683847157541-UserList.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export class UserList1683847157541 {
|
||||
name = 'UserList1683847157541'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_list" ADD "isPublic" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_48a00f08598662b9ca540521eb" ON "user_list" ("isPublic") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_48a00f08598662b9ca540521eb"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "isPublic"`);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
export class UserListFavorites1683869758873 {
|
||||
name = 'UserListFavorites1683869758873'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "user_list_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_c0974b21e18502a4c8178e09fe6" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_016f613dc4feb807e03e3e7da9" ON "user_list_favorite" ("userId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d6765a8c2a4c17c33f9d7f948b" ON "user_list_favorite" ("userId", "userListId") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_016f613dc4feb807e03e3e7da92" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_016f613dc4feb807e03e3e7da92"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_d6765a8c2a4c17c33f9d7f948b"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_016f613dc4feb807e03e3e7da9"`);
|
||||
await queryRunner.query(`DROP TABLE "user_list_favorite"`);
|
||||
}
|
||||
}
|
@@ -35,25 +35,28 @@
|
||||
"@swc/core-win32-x64-msvc": "1.3.56",
|
||||
"@tensorflow/tfjs": "4.4.0",
|
||||
"@tensorflow/tfjs-node": "4.4.0",
|
||||
"slacc-android-arm-eabi": "0.0.7",
|
||||
"slacc-android-arm64": "0.0.7",
|
||||
"slacc-darwin-arm64": "0.0.7",
|
||||
"slacc-darwin-universal": "0.0.7",
|
||||
"slacc-darwin-x64": "0.0.7",
|
||||
"slacc-linux-arm-gnueabihf": "0.0.7",
|
||||
"slacc-linux-arm64-gnu": "0.0.7",
|
||||
"slacc-linux-arm64-musl": "0.0.7",
|
||||
"slacc-linux-x64-gnu": "0.0.7",
|
||||
"slacc-win32-arm64-msvc": "0.0.7",
|
||||
"slacc-win32-x64-msvc": "0.0.7"
|
||||
"bufferutil": "^4.0.7",
|
||||
"slacc-android-arm-eabi": "0.0.9",
|
||||
"slacc-android-arm64": "0.0.9",
|
||||
"slacc-darwin-arm64": "0.0.9",
|
||||
"slacc-darwin-universal": "0.0.9",
|
||||
"slacc-darwin-x64": "0.0.9",
|
||||
"slacc-freebsd-x64": "0.0.9",
|
||||
"slacc-linux-arm-gnueabihf": "0.0.9",
|
||||
"slacc-linux-arm64-gnu": "0.0.9",
|
||||
"slacc-linux-arm64-musl": "0.0.9",
|
||||
"slacc-linux-x64-gnu": "0.0.9",
|
||||
"slacc-win32-arm64-msvc": "0.0.9",
|
||||
"slacc-win32-x64-msvc": "0.0.9",
|
||||
"utf-8-validate": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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.1.2",
|
||||
"@bull-board/fastify": "5.1.2",
|
||||
"@bull-board/ui": "5.1.2",
|
||||
"@bull-board/api": "5.2.0",
|
||||
"@bull-board/fastify": "5.2.0",
|
||||
"@bull-board/ui": "5.2.0",
|
||||
"@discordapp/twemoji": "14.1.2",
|
||||
"@fastify/accepts": "4.1.0",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
@@ -62,20 +65,20 @@
|
||||
"@fastify/multipart": "7.6.0",
|
||||
"@fastify/static": "6.10.1",
|
||||
"@fastify/view": "7.4.1",
|
||||
"@nestjs/common": "9.4.0",
|
||||
"@nestjs/core": "9.4.0",
|
||||
"@nestjs/testing": "9.4.0",
|
||||
"@nestjs/common": "9.4.2",
|
||||
"@nestjs/core": "9.4.2",
|
||||
"@nestjs/testing": "9.4.2",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sinonjs/fake-timers": "10.0.2",
|
||||
"@swc/cli": "0.1.62",
|
||||
"@swc/core": "1.3.56",
|
||||
"@swc/core": "1.3.59",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "5.3.1",
|
||||
"autwh": "0.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"bull": "4.10.4",
|
||||
"bullmq": "3.14.1",
|
||||
"cacheable-lookup": "6.1.0",
|
||||
"cbor": "8.1.0",
|
||||
"chalk": "5.2.0",
|
||||
@@ -93,7 +96,7 @@
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "12.6.0",
|
||||
"happy-dom": "9.16.0",
|
||||
"happy-dom": "9.19.2",
|
||||
"hpagent": "1.2.0",
|
||||
"ioredis": "5.3.2",
|
||||
"ip-cidr": "3.1.0",
|
||||
@@ -102,8 +105,8 @@
|
||||
"jsdom": "21.1.1",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "8.1.1",
|
||||
"meilisearch": "0.32.3",
|
||||
"jsrsasign": "10.8.6",
|
||||
"meilisearch": "0.32.4",
|
||||
"mfm-js": "0.23.3",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -116,7 +119,7 @@
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.1.2",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.10.0",
|
||||
"pg": "8.11.0",
|
||||
"private-ip": "3.0.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
@@ -136,10 +139,10 @@
|
||||
"s-age": "1.1.2",
|
||||
"sanitize-html": "2.10.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"semver": "7.5.0",
|
||||
"semver": "7.5.1",
|
||||
"sharp": "0.32.1",
|
||||
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
|
||||
"slacc": "0.0.7",
|
||||
"slacc": "0.0.9",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"summaly": "github:misskey-dev/summaly",
|
||||
@@ -156,7 +159,6 @@
|
||||
"uuid": "9.0.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.1",
|
||||
"websocket": "1.0.34",
|
||||
"ws": "8.13.0",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
@@ -166,7 +168,6 @@
|
||||
"@types/accepts": "1.3.5",
|
||||
"@types/archiver": "5.3.2",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "4.10.0",
|
||||
"@types/cbor": "6.0.0",
|
||||
"@types/color-convert": "2.0.0",
|
||||
"@types/content-disposition": "0.5.5",
|
||||
@@ -178,11 +179,11 @@
|
||||
"@types/jsonld": "1.5.8",
|
||||
"@types/jsrsasign": "10.5.8",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/node": "20.1.3",
|
||||
"@types/node": "20.2.3",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.7",
|
||||
"@types/nodemailer": "6.4.8",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/pg": "8.6.6",
|
||||
"@types/pg": "8.10.1",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/qrcode": "1.5.0",
|
||||
@@ -196,7 +197,7 @@
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/unzipper": "0.10.5",
|
||||
"@types/unzipper": "0.10.6",
|
||||
"@types/uuid": "9.0.1",
|
||||
"@types/vary": "1.1.0",
|
||||
"@types/web-push": "3.3.2",
|
||||
|
@@ -4,7 +4,7 @@ import * as Redis from 'ioredis';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MeiliSearch } from 'meilisearch';
|
||||
import { DI } from './di-symbols.js';
|
||||
import { loadConfig } from './config.js';
|
||||
import { Config, loadConfig } from './config.js';
|
||||
import { createPostgresDataSource } from './postgres.js';
|
||||
import { RepositoryModule } from './models/RepositoryModule.js';
|
||||
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
||||
@@ -25,7 +25,7 @@ const $db: Provider = {
|
||||
|
||||
const $meilisearch: Provider = {
|
||||
provide: DI.meilisearch,
|
||||
useFactory: (config) => {
|
||||
useFactory: (config: Config) => {
|
||||
if (config.meilisearch) {
|
||||
return new MeiliSearch({
|
||||
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||
@@ -40,7 +40,7 @@ const $meilisearch: Provider = {
|
||||
|
||||
const $redis: Provider = {
|
||||
provide: DI.redis,
|
||||
useFactory: (config) => {
|
||||
useFactory: (config: Config) => {
|
||||
return new Redis.Redis({
|
||||
port: config.redis.port,
|
||||
host: config.redis.host,
|
||||
@@ -55,7 +55,7 @@ const $redis: Provider = {
|
||||
|
||||
const $redisForPub: Provider = {
|
||||
provide: DI.redisForPub,
|
||||
useFactory: (config) => {
|
||||
useFactory: (config: Config) => {
|
||||
const redis = new Redis.Redis({
|
||||
port: config.redisForPubsub.port,
|
||||
host: config.redisForPubsub.host,
|
||||
@@ -71,7 +71,7 @@ const $redisForPub: Provider = {
|
||||
|
||||
const $redisForSub: Provider = {
|
||||
provide: DI.redisForSub,
|
||||
useFactory: (config) => {
|
||||
useFactory: (config: Config) => {
|
||||
const redis = new Redis.Redis({
|
||||
port: config.redisForPubsub.port,
|
||||
host: config.redisForPubsub.host,
|
||||
@@ -100,7 +100,7 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
||||
) {}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
public async dispose(): Promise<void> {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
// XXX:
|
||||
// Shutting down the existing connections causes errors on Jest as
|
||||
@@ -116,4 +116,8 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||
this.redisForSub.disconnect(),
|
||||
]);
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
await this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -190,6 +190,6 @@ function tryCreateUrl(url: string) {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (e) {
|
||||
throw `url="${url}" is not a valid URL.`;
|
||||
throw new Error(`url="${url}" is not a valid URL.`);
|
||||
}
|
||||
}
|
||||
|
@@ -55,11 +55,6 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
this.redisForSub.on('message', this.onRedisMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
this.redisForSub.off('message', this.onRedisMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onRedisMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
@@ -196,4 +191,14 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
|
||||
return this.antennas;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onRedisMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -166,7 +166,12 @@ export class CacheService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ export class CaptchaService {
|
||||
}, { throwErrorWhenResponseNotOk: false });
|
||||
|
||||
if (!res.ok) {
|
||||
throw `${res.status}`;
|
||||
throw new Error(`${res.status}`);
|
||||
}
|
||||
|
||||
return await res.json() as CaptchaResponse;
|
||||
@@ -39,48 +39,48 @@ export class CaptchaService {
|
||||
@bindThis
|
||||
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||
if (response == null) {
|
||||
throw 'recaptcha-failed: no response provided';
|
||||
throw new Error('recaptcha-failed: no response provided');
|
||||
}
|
||||
|
||||
const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => {
|
||||
throw `recaptcha-request-failed: ${err}`;
|
||||
throw new Error(`recaptcha-request-failed: ${err}`);
|
||||
});
|
||||
|
||||
if (result.success !== true) {
|
||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||
throw `recaptcha-failed: ${errorCodes}`;
|
||||
throw new Error(`recaptcha-failed: ${errorCodes}`);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||
if (response == null) {
|
||||
throw 'hcaptcha-failed: no response provided';
|
||||
throw new Error('hcaptcha-failed: no response provided');
|
||||
}
|
||||
|
||||
const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => {
|
||||
throw `hcaptcha-request-failed: ${err}`;
|
||||
throw new Error(`hcaptcha-request-failed: ${err}`);
|
||||
});
|
||||
|
||||
if (result.success !== true) {
|
||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||
throw `hcaptcha-failed: ${errorCodes}`;
|
||||
throw new Error(`hcaptcha-failed: ${errorCodes}`);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
|
||||
if (response == null) {
|
||||
throw 'turnstile-failed: no response provided';
|
||||
throw new Error('turnstile-failed: no response provided');
|
||||
}
|
||||
|
||||
const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => {
|
||||
throw `turnstile-request-failed: ${err}`;
|
||||
throw new Error(`turnstile-request-failed: ${err}`);
|
||||
});
|
||||
|
||||
if (result.success !== true) {
|
||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||
throw `turnstile-failed: ${errorCodes}`;
|
||||
throw new Error(`turnstile-failed: ${errorCodes}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -116,14 +116,14 @@ export class FetchInstanceMetadataService {
|
||||
const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
|
||||
.catch(err => {
|
||||
if (err.statusCode === 404) {
|
||||
throw 'No nodeinfo provided';
|
||||
throw new Error('No nodeinfo provided');
|
||||
} else {
|
||||
throw err.statusCode ?? err.message;
|
||||
}
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
if (wellknown.links == null || !Array.isArray(wellknown.links)) {
|
||||
throw 'No wellknown links';
|
||||
throw new Error('No wellknown links');
|
||||
}
|
||||
|
||||
const links = wellknown.links as any[];
|
||||
@@ -134,7 +134,7 @@ export class FetchInstanceMetadataService {
|
||||
const link = lnik2_1 ?? lnik2_0 ?? lnik1_0;
|
||||
|
||||
if (link == null) {
|
||||
throw 'No nodeinfo link provided';
|
||||
throw new Error('No nodeinfo link provided');
|
||||
}
|
||||
|
||||
const info = await this.httpRequestService.getJson(link.href)
|
||||
|
@@ -120,8 +120,13 @@ export class MetaService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
clearInterval(this.intervalId);
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -510,7 +510,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
if (data.poll && data.poll.expiresAt) {
|
||||
const delay = data.poll.expiresAt.getTime() - Date.now();
|
||||
this.queueService.endedPollNotificationQueue.add({
|
||||
this.queueService.endedPollNotificationQueue.add(note.id, {
|
||||
noteId: note.id,
|
||||
}, {
|
||||
delay,
|
||||
@@ -790,7 +790,13 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
return mentionedUsers;
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined) {
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -122,7 +122,13 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||
}
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined): void {
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -152,7 +152,13 @@ export class NotificationService implements OnApplicationShutdown {
|
||||
*/
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined): void {
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -1,42 +1,11 @@
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
||||
import Bull from 'bull';
|
||||
import * as Bull from 'bullmq';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, DbJobMap } from '../queue/types.js';
|
||||
|
||||
function q<T>(config: Config, name: string, limitPerSec = -1) {
|
||||
return new Bull<T>(name, {
|
||||
redis: {
|
||||
port: config.redisForJobQueue.port,
|
||||
host: config.redisForJobQueue.host,
|
||||
family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
|
||||
password: config.redisForJobQueue.pass,
|
||||
db: config.redisForJobQueue.db ?? 0,
|
||||
},
|
||||
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue` : 'queue',
|
||||
limiter: limitPerSec > 0 ? {
|
||||
max: limitPerSec,
|
||||
duration: 1000,
|
||||
} : undefined,
|
||||
settings: {
|
||||
backoffStrategies: {
|
||||
apBackoff,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
|
||||
function apBackoff(attemptsMade: number, err: Error) {
|
||||
const baseDelay = 60 * 1000; // 1min
|
||||
const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
|
||||
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
|
||||
backoff = Math.min(backoff, maxBackoff);
|
||||
backoff += Math.round(backoff * Math.random() * 0.2);
|
||||
return backoff;
|
||||
}
|
||||
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js';
|
||||
|
||||
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||
@@ -49,49 +18,49 @@ export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
||||
|
||||
const $system: Provider = {
|
||||
provide: 'queue:system',
|
||||
useFactory: (config: Config) => q(config, 'system'),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $endedPollNotification: Provider = {
|
||||
provide: 'queue:endedPollNotification',
|
||||
useFactory: (config: Config) => q(config, 'endedPollNotification'),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $deliver: Provider = {
|
||||
provide: 'queue:deliver',
|
||||
useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $inbox: Provider = {
|
||||
provide: 'queue:inbox',
|
||||
useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $db: Provider = {
|
||||
provide: 'queue:db',
|
||||
useFactory: (config: Config) => q(config, 'db'),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $relationship: Provider = {
|
||||
provide: 'queue:relationship',
|
||||
useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $objectStorage: Provider = {
|
||||
provide: 'queue:objectStorage',
|
||||
useFactory: (config: Config) => q(config, 'objectStorage'),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $webhookDeliver: Provider = {
|
||||
provide: 'queue:webhookDeliver',
|
||||
useFactory: (config: Config) => q(config, 'webhookDeliver', 64),
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.WEBHOOK_DELIVER)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
@@ -131,7 +100,7 @@ export class QueueModule implements OnApplicationShutdown {
|
||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||
) {}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
public async dispose(): Promise<void> {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
// XXX:
|
||||
// Shutting down the existing connections causes errors on Jest as
|
||||
@@ -151,4 +120,8 @@ export class QueueModule implements OnApplicationShutdown {
|
||||
this.webhookDeliverQueue.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
await this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import Bull from 'bull';
|
||||
import type { IActivity } from '@/core/activitypub/type.js';
|
||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js';
|
||||
@@ -11,6 +10,7 @@ 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, RelationshipJobData, ThinUser } from '../queue/types.js';
|
||||
import type httpSignature from '@peertube/http-signature';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class QueueService {
|
||||
@@ -26,7 +26,43 @@ export class QueueService {
|
||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||
) {}
|
||||
) {
|
||||
this.systemQueue.add('tickCharts', {
|
||||
}, {
|
||||
repeat: { pattern: '55 * * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.systemQueue.add('resyncCharts', {
|
||||
}, {
|
||||
repeat: { pattern: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.systemQueue.add('cleanCharts', {
|
||||
}, {
|
||||
repeat: { pattern: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.systemQueue.add('aggregateRetention', {
|
||||
}, {
|
||||
repeat: { pattern: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.systemQueue.add('clean', {
|
||||
}, {
|
||||
repeat: { pattern: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.systemQueue.add('checkExpiredMutings', {
|
||||
}, {
|
||||
repeat: { pattern: '*/5 * * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public deliver(user: ThinUser, content: IActivity | null, to: string | null, isSharedInbox: boolean) {
|
||||
@@ -42,11 +78,10 @@ export class QueueService {
|
||||
isSharedInbox,
|
||||
};
|
||||
|
||||
return this.deliverQueue.add(data, {
|
||||
return this.deliverQueue.add(to, data, {
|
||||
attempts: this.config.deliverJobMaxAttempts ?? 12,
|
||||
timeout: 1 * 60 * 1000, // 1min
|
||||
backoff: {
|
||||
type: 'apBackoff',
|
||||
type: 'custom',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
@@ -60,11 +95,10 @@ export class QueueService {
|
||||
signature,
|
||||
};
|
||||
|
||||
return this.inboxQueue.add(data, {
|
||||
return this.inboxQueue.add('', data, {
|
||||
attempts: this.config.inboxJobMaxAttempts ?? 8,
|
||||
timeout: 5 * 60 * 1000, // 5min
|
||||
backoff: {
|
||||
type: 'apBackoff',
|
||||
type: 'custom',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
@@ -212,7 +246,7 @@ export class QueueService {
|
||||
private generateToDbJobData<T extends 'importFollowingToDb' | 'importBlockingToDb', D extends DbJobData<T>>(name: T, data: D): {
|
||||
name: string,
|
||||
data: D,
|
||||
opts: Bull.JobOptions,
|
||||
opts: Bull.JobsOptions,
|
||||
} {
|
||||
return {
|
||||
name,
|
||||
@@ -299,10 +333,10 @@ export class QueueService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobOptions = {}): {
|
||||
private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobsOptions = {}): {
|
||||
name: string,
|
||||
data: RelationshipJobData,
|
||||
opts: Bull.JobOptions,
|
||||
opts: Bull.JobsOptions,
|
||||
} {
|
||||
return {
|
||||
name,
|
||||
@@ -351,11 +385,10 @@ export class QueueService {
|
||||
eventId: uuid(),
|
||||
};
|
||||
|
||||
return this.webhookDeliverQueue.add(data, {
|
||||
return this.webhookDeliverQueue.add(webhook.id, data, {
|
||||
attempts: 4,
|
||||
timeout: 1 * 60 * 1000, // 1min
|
||||
backoff: {
|
||||
type: 'apBackoff',
|
||||
type: 'custom',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
@@ -367,11 +400,11 @@ export class QueueService {
|
||||
this.deliverQueue.once('cleaned', (jobs, status) => {
|
||||
//deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
||||
});
|
||||
this.deliverQueue.clean(0, 'delayed');
|
||||
this.deliverQueue.clean(0, Infinity, 'delayed');
|
||||
|
||||
this.inboxQueue.once('cleaned', (jobs, status) => {
|
||||
//inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
||||
});
|
||||
this.inboxQueue.clean(0, 'delayed');
|
||||
this.inboxQueue.clean(0, Infinity, 'delayed');
|
||||
}
|
||||
}
|
||||
|
@@ -106,7 +106,7 @@ export class ReactionService {
|
||||
|
||||
let reaction = _reaction ?? FALLBACK;
|
||||
|
||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
|
||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||
reaction = '❤️';
|
||||
} else if (_reaction) {
|
||||
const custom = reaction.match(isCustomEmojiRegexp);
|
||||
@@ -124,6 +124,11 @@ export class ReactionService {
|
||||
if (emoji) {
|
||||
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
|
||||
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
||||
|
||||
// センシティブ
|
||||
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
|
||||
reaction = FALLBACK;
|
||||
}
|
||||
} else {
|
||||
// リアクションとして使う権限がない
|
||||
reaction = FALLBACK;
|
||||
|
@@ -306,6 +306,14 @@ export class RoleService implements OnApplicationShutdown {
|
||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async isExplorable(role: { id: Role['id']} | null): Promise<boolean> {
|
||||
if (role == null) return false;
|
||||
const check = await this.rolesRepository.findOneBy({ id: role.id });
|
||||
if (check == null) return false;
|
||||
return check.isExplorable;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
||||
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||
@@ -425,7 +433,12 @@ export class RoleService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -81,7 +81,12 @@ export class WebhookService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -94,7 +94,7 @@ class LdSignature {
|
||||
@bindThis
|
||||
private getLoader() {
|
||||
return async (url: string): Promise<any> => {
|
||||
if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`;
|
||||
if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`);
|
||||
|
||||
if (this.preLoad) {
|
||||
if (url in CONTEXTS) {
|
||||
@@ -126,7 +126,7 @@ class LdSignature {
|
||||
timeout: this.loderTimeout,
|
||||
}, { throwErrorWhenResponseNotOk: false }).then(res => {
|
||||
if (!res.ok) {
|
||||
throw `${res.status} ${res.statusText}`;
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
} else {
|
||||
return res.json();
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import { PollService } from '@/core/PollService.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
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';
|
||||
@@ -32,7 +33,6 @@ import { ApQuestionService } from './ApQuestionService.js';
|
||||
import { ApImageService } from './ApImageService.js';
|
||||
import type { Resolver } from '../ApResolverService.js';
|
||||
import type { IObject, IPost } from '../type.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApNoteService {
|
||||
@@ -230,7 +230,7 @@ export class ApNoteService {
|
||||
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 'quote resolve failed';
|
||||
throw new Error('quote resolve failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,7 +311,7 @@ export class ApNoteService {
|
||||
|
||||
// ブロックしてたら中断
|
||||
const meta = await this.metaService.fetch();
|
||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw { statusCode: 451 };
|
||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451);
|
||||
|
||||
const unlock = await this.appLockService.getApLock(uri);
|
||||
|
||||
|
@@ -60,7 +60,8 @@ export class ChartManagementService implements OnApplicationShutdown {
|
||||
}, 1000 * 60 * 20);
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
@bindThis
|
||||
public async dispose(): Promise<void> {
|
||||
clearInterval(this.saveIntervalId);
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
await Promise.all(
|
||||
@@ -68,4 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
await this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ export class UserListEntityService {
|
||||
createdAt: userList.createdAt.toISOString(),
|
||||
name: userList.name,
|
||||
userIds: users.map(x => x.userId),
|
||||
isPublic: userList.isPublic,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,12 @@ export class JanitorService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Xev from 'xev';
|
||||
import * as Bull from 'bullmq';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
const ev = new Xev();
|
||||
@@ -13,6 +17,9 @@ export class QueueStatsService implements OnApplicationShutdown {
|
||||
private intervalId: NodeJS.Timer;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
}
|
||||
@@ -31,11 +38,14 @@ export class QueueStatsService implements OnApplicationShutdown {
|
||||
let activeDeliverJobs = 0;
|
||||
let activeInboxJobs = 0;
|
||||
|
||||
this.queueService.deliverQueue.on('global:active', () => {
|
||||
const deliverQueueEvents = new Bull.QueueEvents(QUEUE.DELIVER, baseQueueOptions(this.config, QUEUE.DELIVER));
|
||||
const inboxQueueEvents = new Bull.QueueEvents(QUEUE.INBOX, baseQueueOptions(this.config, QUEUE.INBOX));
|
||||
|
||||
deliverQueueEvents.on('active', () => {
|
||||
activeDeliverJobs++;
|
||||
});
|
||||
|
||||
this.queueService.inboxQueue.on('global:active', () => {
|
||||
inboxQueueEvents.on('active', () => {
|
||||
activeInboxJobs++;
|
||||
});
|
||||
|
||||
@@ -71,9 +81,14 @@ export class QueueStatsService implements OnApplicationShutdown {
|
||||
|
||||
this.intervalId = setInterval(tick, interval);
|
||||
}
|
||||
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -63,9 +63,14 @@ export class ServerStatsService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// CPU STAT
|
||||
|
@@ -25,6 +25,7 @@ export const DI = {
|
||||
userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
|
||||
userPublickeysRepository: Symbol('userPublickeysRepository'),
|
||||
userListsRepository: Symbol('userListsRepository'),
|
||||
userListFavoritesRepository: Symbol('userListFavoritesRepository'),
|
||||
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
|
||||
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
||||
userIpsRepository: Symbol('userIpsRepository'),
|
||||
|
@@ -21,7 +21,7 @@ function getNoise(): string {
|
||||
|
||||
export function genAid(date: Date): string {
|
||||
const t = date.getTime();
|
||||
if (isNaN(t)) throw 'Failed to create AID: Invalid Date';
|
||||
if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date');
|
||||
counter++;
|
||||
return getTime(t) + getNoise();
|
||||
}
|
||||
|
@@ -5,15 +5,16 @@ const dateTimeIntervals = {
|
||||
};
|
||||
|
||||
export function dateUTC(time: number[]): Date {
|
||||
const d = time.length === 2 ? Date.UTC(time[0], time[1])
|
||||
: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
|
||||
: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
|
||||
: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
|
||||
: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
|
||||
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
|
||||
: null;
|
||||
const d =
|
||||
time.length === 2 ? Date.UTC(time[0], time[1])
|
||||
: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
|
||||
: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
|
||||
: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
|
||||
: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
|
||||
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
|
||||
: null;
|
||||
|
||||
if (!d) throw 'wrong number of arguments';
|
||||
if (!d) throw new Error('wrong number of arguments');
|
||||
|
||||
return new Date(d);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js';
|
||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
@@ -112,6 +112,12 @@ const $userListsRepository: Provider = {
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userListFavoritesRepository: Provider = {
|
||||
provide: DI.userListFavoritesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(UserListFavorite),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userListJoiningsRepository: Provider = {
|
||||
provide: DI.userListJoiningsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(UserListJoining),
|
||||
@@ -416,6 +422,7 @@ const $userMemosRepository: Provider = {
|
||||
$userSecurityKeysRepository,
|
||||
$userPublickeysRepository,
|
||||
$userListsRepository,
|
||||
$userListFavoritesRepository,
|
||||
$userListJoiningsRepository,
|
||||
$userNotePiningsRepository,
|
||||
$userIpsRepository,
|
||||
@@ -483,6 +490,7 @@ const $userMemosRepository: Provider = {
|
||||
$userSecurityKeysRepository,
|
||||
$userPublickeysRepository,
|
||||
$userListsRepository,
|
||||
$userListFavoritesRepository,
|
||||
$userListJoiningsRepository,
|
||||
$userNotePiningsRepository,
|
||||
$userIpsRepository,
|
||||
|
@@ -90,7 +90,7 @@ export class Note {
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true,
|
||||
})
|
||||
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
|
||||
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
|
||||
|
||||
@Column('smallint', {
|
||||
default: 0,
|
||||
|
@@ -19,6 +19,12 @@ export class UserList {
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isPublic: boolean;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
|
33
packages/backend/src/models/entities/UserListFavorite.ts
Normal file
33
packages/backend/src/models/entities/UserListFavorite.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './User.js';
|
||||
import { UserList } from './UserList.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'userListId'], { unique: true })
|
||||
export class UserListFavorite {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public userListId: UserList['id'];
|
||||
|
||||
@ManyToOne(type => UserList, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public userList: UserList | null;
|
||||
}
|
@@ -49,6 +49,7 @@ import { User } from '@/models/entities/User.js';
|
||||
import { UserIp } from '@/models/entities/UserIp.js';
|
||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||
import { UserList } from '@/models/entities/UserList.js';
|
||||
import { UserListFavorite } from './entities/UserListFavorite.js';
|
||||
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
||||
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
||||
import { UserPending } from '@/models/entities/UserPending.js';
|
||||
@@ -117,6 +118,7 @@ export {
|
||||
UserIp,
|
||||
UserKeypair,
|
||||
UserList,
|
||||
UserListFavorite,
|
||||
UserListJoining,
|
||||
UserNotePining,
|
||||
UserPending,
|
||||
@@ -184,6 +186,7 @@ export type UsersRepository = Repository<User>;
|
||||
export type UserIpsRepository = Repository<UserIp>;
|
||||
export type UserKeypairsRepository = Repository<UserKeypair>;
|
||||
export type UserListsRepository = Repository<UserList>;
|
||||
export type UserListFavoritesRepository = Repository<UserListFavorite>;
|
||||
export type UserListJoiningsRepository = Repository<UserListJoining>;
|
||||
export type UserNotePiningsRepository = Repository<UserNotePining>;
|
||||
export type UserPendingsRepository = Repository<UserPending>;
|
||||
|
@@ -25,5 +25,10 @@ export const packedUserListSchema = {
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
nullable: false,
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -57,6 +57,7 @@ import { User } from '@/models/entities/User.js';
|
||||
import { UserIp } from '@/models/entities/UserIp.js';
|
||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||
import { UserList } from '@/models/entities/UserList.js';
|
||||
import { UserListFavorite } from '@/models/entities/UserListFavorite.js';
|
||||
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
||||
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
||||
import { UserPending } from '@/models/entities/UserPending.js';
|
||||
@@ -132,6 +133,7 @@ export const entities = [
|
||||
UserKeypair,
|
||||
UserPublickey,
|
||||
UserList,
|
||||
UserListFavorite,
|
||||
UserListJoining,
|
||||
UserNotePining,
|
||||
UserSecurityKey,
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import * as Bull from 'bullmq';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { getJobInfo } from './get-job-info.js';
|
||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||
@@ -35,17 +34,51 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
|
||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||
import { QUEUE, baseQueueOptions } from './const.js';
|
||||
|
||||
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
|
||||
function httpRelatedBackoff(attemptsMade: number) {
|
||||
const baseDelay = 60 * 1000; // 1min
|
||||
const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
|
||||
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
|
||||
backoff = Math.min(backoff, maxBackoff);
|
||||
backoff += Math.round(backoff * Math.random() * 0.2);
|
||||
return backoff;
|
||||
}
|
||||
|
||||
function getJobInfo(job: Bull.Job | undefined, increment = false): string {
|
||||
if (job == null) return '-';
|
||||
|
||||
const age = Date.now() - job.timestamp;
|
||||
|
||||
const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
|
||||
: age > 10000 ? `${Math.floor(age / 1000)}s`
|
||||
: `${age}ms`;
|
||||
|
||||
// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
|
||||
const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
|
||||
const maxAttempts = job.opts ? job.opts.attempts : 0;
|
||||
|
||||
return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class QueueProcessorService {
|
||||
export class QueueProcessorService implements OnApplicationShutdown {
|
||||
private logger: Logger;
|
||||
private systemQueueWorker: Bull.Worker;
|
||||
private dbQueueWorker: Bull.Worker;
|
||||
private deliverQueueWorker: Bull.Worker;
|
||||
private inboxQueueWorker: Bull.Worker;
|
||||
private webhookDeliverQueueWorker: Bull.Worker;
|
||||
private relationshipQueueWorker: Bull.Worker;
|
||||
private objectStorageQueueWorker: Bull.Worker;
|
||||
private endedPollNotificationQueueWorker: Bull.Worker;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private queueService: QueueService,
|
||||
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||
private deliverProcessorService: DeliverProcessorService,
|
||||
@@ -77,10 +110,7 @@ export class QueueProcessorService {
|
||||
private cleanProcessorService: CleanProcessorService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public start() {
|
||||
function renderError(e: Error): any {
|
||||
if (e) { // 何故かeがundefinedで来ることがある
|
||||
return {
|
||||
@@ -97,146 +127,232 @@ export class QueueProcessorService {
|
||||
}
|
||||
}
|
||||
|
||||
const systemLogger = this.logger.createSubLogger('system');
|
||||
const deliverLogger = this.logger.createSubLogger('deliver');
|
||||
const webhookLogger = this.logger.createSubLogger('webhook');
|
||||
const inboxLogger = this.logger.createSubLogger('inbox');
|
||||
const dbLogger = this.logger.createSubLogger('db');
|
||||
const relationshipLogger = this.logger.createSubLogger('relationship');
|
||||
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
|
||||
//#region system
|
||||
this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
|
||||
switch (job.name) {
|
||||
case 'tickCharts': return this.tickChartsProcessorService.process();
|
||||
case 'resyncCharts': return this.resyncChartsProcessorService.process();
|
||||
case 'cleanCharts': return this.cleanChartsProcessorService.process();
|
||||
case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
|
||||
case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
|
||||
case 'clean': return this.cleanProcessorService.process();
|
||||
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.SYSTEM),
|
||||
autorun: false,
|
||||
});
|
||||
|
||||
this.queueService.systemQueue
|
||||
.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
|
||||
const systemLogger = this.logger.createSubLogger('system');
|
||||
|
||||
this.systemQueueWorker
|
||||
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`));
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
this.queueService.deliverQueue
|
||||
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
|
||||
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||
//#region db
|
||||
this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
|
||||
switch (job.name) {
|
||||
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
|
||||
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
|
||||
case 'exportNotes': return this.exportNotesProcessorService.process(job);
|
||||
case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
|
||||
case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
|
||||
case 'exportMuting': return this.exportMutingProcessorService.process(job);
|
||||
case 'exportBlocking': return this.exportBlockingProcessorService.process(job);
|
||||
case 'exportUserLists': return this.exportUserListsProcessorService.process(job);
|
||||
case 'exportAntennas': return this.exportAntennasProcessorService.process(job);
|
||||
case 'importFollowing': return this.importFollowingProcessorService.process(job);
|
||||
case 'importFollowingToDb': return this.importFollowingProcessorService.processDb(job);
|
||||
case 'importMuting': return this.importMutingProcessorService.process(job);
|
||||
case 'importBlocking': return this.importBlockingProcessorService.process(job);
|
||||
case 'importBlockingToDb': return this.importBlockingProcessorService.processDb(job);
|
||||
case 'importUserLists': return this.importUserListsProcessorService.process(job);
|
||||
case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job);
|
||||
case 'importAntennas': return this.importAntennasProcessorService.process(job);
|
||||
case 'deleteAccount': return this.deleteAccountProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for db`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.DB),
|
||||
autorun: false,
|
||||
});
|
||||
|
||||
this.queueService.inboxQueue
|
||||
.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
|
||||
const dbLogger = this.logger.createSubLogger('db');
|
||||
|
||||
this.queueService.dbQueue
|
||||
.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))
|
||||
this.dbQueueWorker
|
||||
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`));
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
this.queueService.relationshipQueue
|
||||
.on('waiting', (jobId) => relationshipLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => relationshipLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => relationshipLogger.warn(`stalled id=${job.id}`));
|
||||
//#region deliver
|
||||
this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.DELIVER),
|
||||
autorun: false,
|
||||
concurrency: this.config.deliverJobConcurrency ?? 128,
|
||||
limiter: {
|
||||
max: this.config.deliverJobPerSec ?? 128,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
this.queueService.objectStorageQueue
|
||||
.on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
||||
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
|
||||
const deliverLogger = this.logger.createSubLogger('deliver');
|
||||
|
||||
this.queueService.webhookDeliverQueue
|
||||
.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
|
||||
this.deliverQueueWorker
|
||||
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
//#region inbox
|
||||
this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.INBOX),
|
||||
autorun: false,
|
||||
concurrency: this.config.inboxJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.inboxJobPerSec ?? 16,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
const inboxLogger = this.logger.createSubLogger('inbox');
|
||||
|
||||
this.inboxQueueWorker
|
||||
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
//#region webhook deliver
|
||||
this.webhookDeliverQueueWorker = new Bull.Worker(QUEUE.WEBHOOK_DELIVER, (job) => this.webhookDeliverProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.WEBHOOK_DELIVER),
|
||||
autorun: false,
|
||||
concurrency: 64,
|
||||
limiter: {
|
||||
max: 64,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
const webhookLogger = this.logger.createSubLogger('webhook');
|
||||
|
||||
this.webhookDeliverQueueWorker
|
||||
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
|
||||
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
this.queueService.systemQueue.add('tickCharts', {
|
||||
//#region relationship
|
||||
this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
|
||||
switch (job.name) {
|
||||
case 'follow': return this.relationshipProcessorService.processFollow(job);
|
||||
case 'unfollow': return this.relationshipProcessorService.processUnfollow(job);
|
||||
case 'block': return this.relationshipProcessorService.processBlock(job);
|
||||
case 'unblock': return this.relationshipProcessorService.processUnblock(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for relationship`);
|
||||
}
|
||||
}, {
|
||||
repeat: { cron: '55 * * * *' },
|
||||
removeOnComplete: true,
|
||||
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
||||
autorun: false,
|
||||
concurrency: this.config.relashionshipJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.relashionshipJobPerSec ?? 64,
|
||||
duration: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
this.queueService.systemQueue.add('resyncCharts', {
|
||||
}, {
|
||||
repeat: { cron: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.queueService.systemQueue.add('cleanCharts', {
|
||||
}, {
|
||||
repeat: { cron: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.queueService.systemQueue.add('aggregateRetention', {
|
||||
}, {
|
||||
repeat: { cron: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.queueService.systemQueue.add('clean', {
|
||||
}, {
|
||||
repeat: { cron: '0 0 * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.queueService.systemQueue.add('checkExpiredMutings', {
|
||||
}, {
|
||||
repeat: { cron: '*/5 * * * *' },
|
||||
removeOnComplete: true,
|
||||
});
|
||||
|
||||
this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
|
||||
this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
|
||||
this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
|
||||
this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
|
||||
|
||||
this.queueService.dbQueue.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
|
||||
this.queueService.dbQueue.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
|
||||
this.queueService.dbQueue.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
|
||||
this.queueService.dbQueue.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
|
||||
|
||||
this.queueService.objectStorageQueue.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
|
||||
this.queueService.objectStorageQueue.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
|
||||
const relationshipLogger = this.logger.createSubLogger('relationship');
|
||||
|
||||
{
|
||||
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
|
||||
this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
|
||||
this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
|
||||
this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
|
||||
this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
|
||||
}
|
||||
this.relationshipQueueWorker
|
||||
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
|
||||
this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
|
||||
this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
|
||||
this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
|
||||
this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
|
||||
this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
|
||||
//#region object storage
|
||||
this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
|
||||
switch (job.name) {
|
||||
case 'deleteFile': return this.deleteFileProcessorService.process(job);
|
||||
case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for objectStorage`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
|
||||
autorun: false,
|
||||
concurrency: 16,
|
||||
});
|
||||
|
||||
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
|
||||
|
||||
this.objectStorageQueueWorker
|
||||
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
//#region ended poll notification
|
||||
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => this.endedPollNotificationProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
|
||||
autorun: false,
|
||||
});
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async start(): Promise<void> {
|
||||
await Promise.all([
|
||||
this.systemQueueWorker.run(),
|
||||
this.dbQueueWorker.run(),
|
||||
this.deliverQueueWorker.run(),
|
||||
this.inboxQueueWorker.run(),
|
||||
this.webhookDeliverQueueWorker.run(),
|
||||
this.relationshipQueueWorker.run(),
|
||||
this.objectStorageQueueWorker.run(),
|
||||
this.endedPollNotificationQueueWorker.run(),
|
||||
]);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async stop(): Promise<void> {
|
||||
await Promise.all([
|
||||
this.systemQueueWorker.close(),
|
||||
this.dbQueueWorker.close(),
|
||||
this.deliverQueueWorker.close(),
|
||||
this.inboxQueueWorker.close(),
|
||||
this.webhookDeliverQueueWorker.close(),
|
||||
this.relationshipQueueWorker.close(),
|
||||
this.objectStorageQueueWorker.close(),
|
||||
this.endedPollNotificationQueueWorker.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
|
||||
await this.stop();
|
||||
}
|
||||
}
|
||||
|
26
packages/backend/src/queue/const.ts
Normal file
26
packages/backend/src/queue/const.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Config } from '@/config.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
export const QUEUE = {
|
||||
DELIVER: 'deliver',
|
||||
INBOX: 'inbox',
|
||||
SYSTEM: 'system',
|
||||
ENDED_POLL_NOTIFICATION: 'endedPollNotification',
|
||||
DB: 'db',
|
||||
RELATIONSHIP: 'relationship',
|
||||
OBJECT_STORAGE: 'objectStorage',
|
||||
WEBHOOK_DELIVER: 'webhookDeliver',
|
||||
};
|
||||
|
||||
export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions {
|
||||
return {
|
||||
connection: {
|
||||
port: config.redisForJobQueue.port,
|
||||
host: config.redisForJobQueue.host,
|
||||
family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
|
||||
password: config.redisForJobQueue.pass,
|
||||
db: config.redisForJobQueue.db ?? 0,
|
||||
},
|
||||
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`,
|
||||
};
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
import Bull from 'bull';
|
||||
|
||||
export function getJobInfo(job: Bull.Job, increment = false) {
|
||||
const age = Date.now() - job.timestamp;
|
||||
|
||||
const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
|
||||
: age > 10000 ? `${Math.floor(age / 1000)}s`
|
||||
: `${age}ms`;
|
||||
|
||||
// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
|
||||
const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
|
||||
const maxAttempts = job.opts ? job.opts.attempts : 0;
|
||||
|
||||
return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
|
||||
}
|
@@ -9,7 +9,7 @@ import { deepClone } from '@/misc/clone.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class AggregateRetentionProcessorService {
|
||||
@@ -32,7 +32,7 @@ export class AggregateRetentionProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Aggregating retention...');
|
||||
|
||||
const now = new Date();
|
||||
@@ -62,7 +62,6 @@ export class AggregateRetentionProcessorService {
|
||||
} catch (err) {
|
||||
if (isDuplicateKeyValueError(err)) {
|
||||
this.logger.succ('Skip because it has already been processed by another worker.');
|
||||
done();
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
@@ -88,6 +87,5 @@ export class AggregateRetentionProcessorService {
|
||||
}
|
||||
|
||||
this.logger.succ('Retention aggregated.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class CheckExpiredMutingsProcessorService {
|
||||
@@ -27,7 +27,7 @@ export class CheckExpiredMutingsProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Checking expired mutings...');
|
||||
|
||||
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
||||
@@ -41,6 +41,5 @@ export class CheckExpiredMutingsProcessorService {
|
||||
}
|
||||
|
||||
this.logger.succ('All expired mutings checked.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
|
||||
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class CleanChartsProcessorService {
|
||||
@@ -45,7 +45,7 @@ export class CleanChartsProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Clean charts...');
|
||||
|
||||
await Promise.all([
|
||||
@@ -64,6 +64,5 @@ export class CleanChartsProcessorService {
|
||||
]);
|
||||
|
||||
this.logger.succ('All charts successfully cleaned.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class CleanProcessorService {
|
||||
@@ -36,7 +36,7 @@ export class CleanProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Cleaning...');
|
||||
|
||||
this.userIpsRepository.delete({
|
||||
@@ -72,6 +72,5 @@ export class CleanProcessorService {
|
||||
}
|
||||
|
||||
this.logger.succ('Cleaned.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -5,9 +5,9 @@ import type { DriveFilesRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class CleanRemoteFilesProcessorService {
|
||||
@@ -27,7 +27,7 @@ export class CleanRemoteFilesProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<Record<string, unknown>>): Promise<void> {
|
||||
this.logger.info('Deleting cached remote files...');
|
||||
|
||||
let deletedCount = 0;
|
||||
@@ -47,7 +47,7 @@ export class CleanRemoteFilesProcessorService {
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
job.progress(100);
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -62,10 +62,9 @@ export class CleanRemoteFilesProcessorService {
|
||||
isLink: false,
|
||||
});
|
||||
|
||||
job.progress(deletedCount / total);
|
||||
job.updateProgress(deletedCount / total);
|
||||
}
|
||||
|
||||
this.logger.succ('All cached remote files has been deleted.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -8,10 +8,10 @@ import { DriveService } from '@/core/DriveService.js';
|
||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbUserDeleteJobData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbUserDeleteJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteAccountProcessorService {
|
||||
|
@@ -5,10 +5,10 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteDriveFilesProcessorService {
|
||||
@@ -31,12 +31,11 @@ export class DeleteDriveFilesProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
|
||||
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +55,7 @@ export class DeleteDriveFilesProcessorService {
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
job.progress(100);
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,10 +70,9 @@ export class DeleteDriveFilesProcessorService {
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
job.progress(deletedCount / total);
|
||||
job.updateProgress(deletedCount / total);
|
||||
}
|
||||
|
||||
this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -3,10 +3,10 @@ import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { ObjectStorageFileJobData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { ObjectStorageFileJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteFileProcessorService {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Bull from 'bullmq';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -16,7 +17,6 @@ import { StatusError } from '@/misc/status-error.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DeliverJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -121,15 +121,13 @@ export class DeliverProcessorService {
|
||||
isSuspended: true,
|
||||
});
|
||||
});
|
||||
return `${host} is gone`;
|
||||
throw new Bull.UnrecoverableError(`${host} is gone`);
|
||||
}
|
||||
// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
|
||||
// 何回再送しても成功することはないということなのでエラーにはしないでおく
|
||||
return `${res.statusCode} ${res.statusMessage}`;
|
||||
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
|
||||
}
|
||||
|
||||
// 5xx etc.
|
||||
throw `${res.statusCode} ${res.statusMessage}`;
|
||||
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
||||
} else {
|
||||
// DNS error, socket error, timeout ...
|
||||
throw res;
|
||||
|
@@ -6,7 +6,7 @@ import type Logger from '@/logger.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { EndedPollNotificationJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -30,10 +30,9 @@ export class EndedPollNotificationProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<EndedPollNotificationJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<EndedPollNotificationJobData>): Promise<void> {
|
||||
const note = await this.notesRepository.findOneBy({ id: job.data.noteId });
|
||||
if (note == null || !note.hasPoll) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +50,5 @@ export class EndedPollNotificationProcessorService {
|
||||
noteId: note.id,
|
||||
});
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { createTemp } from '@/misc/create-temp.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type { DBExportAntennasData } from '../types.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class ExportAntennasProcessorService {
|
||||
@@ -39,10 +39,9 @@ export class ExportAntennasProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DBExportAntennasData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DBExportAntennasData>): Promise<void> {
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
const [path, cleanup] = await createTemp();
|
||||
@@ -96,7 +95,6 @@ export class ExportAntennasProcessorService {
|
||||
this.logger.succ('Exported to: ' + driveFile.id);
|
||||
} finally {
|
||||
cleanup();
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,10 @@ import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ExportBlockingProcessorService {
|
||||
@@ -36,12 +36,11 @@ export class ExportBlockingProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
|
||||
this.logger.info(`Exporting blocking of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,7 +68,7 @@ export class ExportBlockingProcessorService {
|
||||
});
|
||||
|
||||
if (blockings.length === 0) {
|
||||
job.progress(100);
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ export class ExportBlockingProcessorService {
|
||||
blockerId: user.id,
|
||||
});
|
||||
|
||||
job.progress(exportedCount / total);
|
||||
job.updateProgress(exportedCount / total);
|
||||
}
|
||||
|
||||
stream.end();
|
||||
@@ -112,7 +111,5 @@ export class ExportBlockingProcessorService {
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ import { createTemp, createTempDir } from '@/misc/create-temp.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class ExportCustomEmojisProcessorService {
|
||||
@@ -37,12 +37,11 @@ export class ExportCustomEmojisProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job): Promise<void> {
|
||||
this.logger.info('Exporting custom emojis ...');
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,24 +116,26 @@ export class ExportCustomEmojisProcessorService {
|
||||
metaStream.end();
|
||||
|
||||
// Create archive
|
||||
const [archivePath, archiveCleanup] = await createTemp();
|
||||
const archiveStream = fs.createWriteStream(archivePath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: 0 },
|
||||
});
|
||||
archiveStream.on('close', async () => {
|
||||
this.logger.succ(`Exported to: ${archivePath}`);
|
||||
await new Promise<void>(async (resolve) => {
|
||||
const [archivePath, archiveCleanup] = await createTemp();
|
||||
const archiveStream = fs.createWriteStream(archivePath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: 0 },
|
||||
});
|
||||
archiveStream.on('close', async () => {
|
||||
this.logger.succ(`Exported to: ${archivePath}`);
|
||||
|
||||
const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip';
|
||||
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
||||
const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip';
|
||||
const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
||||
|
||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||
cleanup();
|
||||
archiveCleanup();
|
||||
done();
|
||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||
cleanup();
|
||||
archiveCleanup();
|
||||
resolve();
|
||||
});
|
||||
archive.pipe(archiveStream);
|
||||
archive.directory(path, false);
|
||||
archive.finalize();
|
||||
});
|
||||
archive.pipe(archiveStream);
|
||||
archive.directory(path, false);
|
||||
archive.finalize();
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -42,12 +42,11 @@ export class ExportFavoritesProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
|
||||
this.logger.info(`Exporting favorites of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ export class ExportFavoritesProcessorService {
|
||||
}) as (NoteFavorite & { note: Note & { user: User } })[];
|
||||
|
||||
if (favorites.length === 0) {
|
||||
job.progress(100);
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -112,7 +111,7 @@ export class ExportFavoritesProcessorService {
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
job.progress(exportedFavoritesCount / total);
|
||||
job.updateProgress(exportedFavoritesCount / total);
|
||||
}
|
||||
|
||||
await write(']');
|
||||
@@ -127,8 +126,6 @@ export class ExportFavoritesProcessorService {
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,10 +10,10 @@ import { DriveService } from '@/core/DriveService.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
import type { Following } from '@/models/entities/Following.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbExportFollowingData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbExportFollowingData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ExportFollowingProcessorService {
|
||||
@@ -40,12 +40,11 @@ export class ExportFollowingProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbExportFollowingData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbExportFollowingData>): Promise<void> {
|
||||
this.logger.info(`Exporting following of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,7 +115,5 @@ export class ExportFollowingProcessorService {
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,10 @@ import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ExportMutingProcessorService {
|
||||
@@ -39,12 +39,11 @@ export class ExportMutingProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
|
||||
this.logger.info(`Exporting muting of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,7 +72,7 @@ export class ExportMutingProcessorService {
|
||||
});
|
||||
|
||||
if (mutes.length === 0) {
|
||||
job.progress(100);
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ export class ExportMutingProcessorService {
|
||||
muterId: user.id,
|
||||
});
|
||||
|
||||
job.progress(exportedCount / total);
|
||||
job.updateProgress(exportedCount / total);
|
||||
}
|
||||
|
||||
stream.end();
|
||||
@@ -116,7 +115,5 @@ export class ExportMutingProcessorService {
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -39,12 +39,11 @@ export class ExportNotesProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
|
||||
this.logger.info(`Exporting notes of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,7 +86,7 @@ export class ExportNotesProcessorService {
|
||||
}) as Note[];
|
||||
|
||||
if (notes.length === 0) {
|
||||
job.progress(100);
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -108,7 +107,7 @@ export class ExportNotesProcessorService {
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
job.progress(exportedNotesCount / total);
|
||||
job.updateProgress(exportedNotesCount / total);
|
||||
}
|
||||
|
||||
await write(']');
|
||||
@@ -123,8 +122,6 @@ export class ExportNotesProcessorService {
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,10 +9,10 @@ import type Logger from '@/logger.js';
|
||||
import { DriveService } from '@/core/DriveService.js';
|
||||
import { createTemp } from '@/misc/create-temp.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbJobDataWithUser } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ExportUserListsProcessorService {
|
||||
@@ -39,12 +39,11 @@ export class ExportUserListsProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
|
||||
this.logger.info(`Exporting user lists of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,7 +91,5 @@ export class ExportUserListsProcessorService {
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import { DBAntennaImportJobData } from '../types.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
const validate = new Ajv().compile({
|
||||
type: 'object',
|
||||
@@ -59,7 +59,7 @@ export class ImportAntennasProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DBAntennaImportJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DBAntennaImportJobData>): Promise<void> {
|
||||
const now = new Date();
|
||||
try {
|
||||
for (const antenna of job.data.antenna) {
|
||||
@@ -89,8 +89,6 @@ export class ImportAntennasProcessorService {
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.logger.error(err);
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ImportBlockingProcessorService {
|
||||
@@ -34,12 +34,11 @@ export class ImportBlockingProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
|
||||
this.logger.info(`Importing blocking of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,7 +46,6 @@ export class ImportBlockingProcessorService {
|
||||
id: job.data.fileId,
|
||||
});
|
||||
if (file == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +54,6 @@ export class ImportBlockingProcessorService {
|
||||
this.queueService.createImportBlockingToDbJob({ id: user.id }, targets);
|
||||
|
||||
this.logger.succ('Import jobs created');
|
||||
done();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -85,7 +82,7 @@ export class ImportBlockingProcessorService {
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw `Unable to resolve user: @${username}@${host}`;
|
||||
throw new Error(`Unable to resolve user: @${username}@${host}`);
|
||||
}
|
||||
|
||||
// skip myself
|
||||
|
@@ -12,7 +12,7 @@ import { DriveService } from '@/core/DriveService.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbUserImportJobData } from '../types.js';
|
||||
|
||||
// TODO: 名前衝突時の動作を選べるようにする
|
||||
@@ -45,14 +45,13 @@ export class ImportCustomEmojisProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
|
||||
this.logger.info('Importing custom emojis ...');
|
||||
|
||||
const file = await this.driveFilesRepository.findOneBy({
|
||||
id: job.data.fileId,
|
||||
});
|
||||
if (file == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,7 +115,6 @@ export class ImportCustomEmojisProcessorService {
|
||||
cleanup();
|
||||
|
||||
this.logger.succ('Imported');
|
||||
done();
|
||||
});
|
||||
unzipStream.pipe(extractor);
|
||||
this.logger.succ(`Unzipping to ${outputPath}`);
|
||||
|
@@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ImportFollowingProcessorService {
|
||||
@@ -34,12 +34,11 @@ export class ImportFollowingProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
|
||||
this.logger.info(`Importing following of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,7 +46,6 @@ export class ImportFollowingProcessorService {
|
||||
id: job.data.fileId,
|
||||
});
|
||||
if (file == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +54,6 @@ export class ImportFollowingProcessorService {
|
||||
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets);
|
||||
|
||||
this.logger.succ('Import jobs created');
|
||||
done();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -85,7 +82,7 @@ export class ImportFollowingProcessorService {
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw `Unable to resolve user: @${username}@${host}`;
|
||||
throw new Error(`Unable to resolve user: @${username}@${host}`);
|
||||
}
|
||||
|
||||
// skip myself
|
||||
|
@@ -9,10 +9,10 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbUserImportJobData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbUserImportJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ImportMutingProcessorService {
|
||||
@@ -38,12 +38,11 @@ export class ImportMutingProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
|
||||
this.logger.info(`Importing muting of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +50,6 @@ export class ImportMutingProcessorService {
|
||||
id: job.data.fileId,
|
||||
});
|
||||
if (file == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,7 +81,7 @@ export class ImportMutingProcessorService {
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw `cannot resolve user: @${username}@${host}`;
|
||||
throw new Error(`cannot resolve user: @${username}@${host}`);
|
||||
}
|
||||
|
||||
// skip myself
|
||||
@@ -98,6 +96,5 @@ export class ImportMutingProcessorService {
|
||||
}
|
||||
|
||||
this.logger.succ('Imported');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import { IdService } from '@/core/IdService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DbUserImportJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -46,12 +46,11 @@ export class ImportUserListsProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
|
||||
public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
|
||||
this.logger.info(`Importing user lists of ${job.data.user.id} ...`);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||
if (user == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,7 +58,6 @@ export class ImportUserListsProcessorService {
|
||||
id: job.data.fileId,
|
||||
});
|
||||
if (file == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,6 +107,5 @@ export class ImportUserListsProcessorService {
|
||||
}
|
||||
|
||||
this.logger.succ('Imported');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { URL } from 'node:url';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import httpSignature from '@peertube/http-signature';
|
||||
import * as Bull from 'bullmq';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { InstancesRepository, DriveFilesRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
@@ -23,10 +23,8 @@ import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
|
||||
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { InboxJobData } from '../types.js';
|
||||
|
||||
// ユーザーのinboxにアクティビティが届いた時の処理
|
||||
@Injectable()
|
||||
export class InboxProcessorService {
|
||||
private logger: Logger;
|
||||
@@ -35,12 +33,6 @@ export class InboxProcessorService {
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.instancesRepository)
|
||||
private instancesRepository: InstancesRepository,
|
||||
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private metaService: MetaService,
|
||||
private apInboxService: ApInboxService,
|
||||
@@ -93,24 +85,24 @@ export class InboxProcessorService {
|
||||
try {
|
||||
authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor));
|
||||
} catch (err) {
|
||||
// 対象が4xxならスキップ
|
||||
// 対象が4xxならスキップ
|
||||
if (err instanceof StatusError) {
|
||||
if (err.isClientError) {
|
||||
return `skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`;
|
||||
throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
|
||||
}
|
||||
throw `Error in actor ${activity.actor} - ${err.statusCode ?? err}`;
|
||||
throw new Error(`Error in actor ${activity.actor} - ${err.statusCode ?? err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// それでもわからなければ終了
|
||||
if (authUser == null) {
|
||||
return 'skip: failed to resolve user';
|
||||
throw new Bull.UnrecoverableError('skip: failed to resolve user');
|
||||
}
|
||||
|
||||
// publicKey がなくても終了
|
||||
if (authUser.key == null) {
|
||||
return 'skip: failed to resolve user publicKey';
|
||||
throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
|
||||
}
|
||||
|
||||
// HTTP-Signatureの検証
|
||||
@@ -118,10 +110,10 @@ export class InboxProcessorService {
|
||||
|
||||
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||
if (activity.signature) {
|
||||
if (activity.signature.type !== 'RsaSignature2017') {
|
||||
return `skip: unsupported LD-signature type ${activity.signature.type}`;
|
||||
throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${activity.signature.type}`);
|
||||
}
|
||||
|
||||
// activity.signature.creator: https://example.oom/users/user#main-key
|
||||
@@ -134,32 +126,32 @@ export class InboxProcessorService {
|
||||
// keyIdからLD-Signatureのユーザーを取得
|
||||
authUser = await this.apDbResolverService.getAuthUserFromKeyId(activity.signature.creator);
|
||||
if (authUser == null) {
|
||||
return 'skip: LD-Signatureのユーザーが取得できませんでした';
|
||||
throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした');
|
||||
}
|
||||
|
||||
if (authUser.key == null) {
|
||||
return 'skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした';
|
||||
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
|
||||
}
|
||||
|
||||
// LD-Signature検証
|
||||
const ldSignature = this.ldSignatureService.use();
|
||||
const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
|
||||
if (!verified) {
|
||||
return 'skip: LD-Signatureの検証に失敗しました';
|
||||
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
|
||||
}
|
||||
|
||||
// もう一度actorチェック
|
||||
if (authUser.user.uri !== activity.actor) {
|
||||
return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
|
||||
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
|
||||
}
|
||||
|
||||
// ブロックしてたら中断
|
||||
const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
|
||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) {
|
||||
return `Blocked request: ${ldHost}`;
|
||||
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
|
||||
}
|
||||
} else {
|
||||
return `skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`;
|
||||
throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +160,7 @@ export class InboxProcessorService {
|
||||
const signerHost = this.utilityService.extractDbHost(authUser.user.uri!);
|
||||
const activityIdHost = this.utilityService.extractDbHost(activity.id);
|
||||
if (signerHost !== activityIdHost) {
|
||||
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
|
||||
throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
|
@@ -15,7 +15,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
|
||||
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class ResyncChartsProcessorService {
|
||||
@@ -43,7 +43,7 @@ export class ResyncChartsProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Resync charts...');
|
||||
|
||||
// TODO: ユーザーごとのチャートも更新する
|
||||
@@ -55,6 +55,5 @@ export class ResyncChartsProcessorService {
|
||||
]);
|
||||
|
||||
this.logger.succ('All charts successfully resynced.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
|
||||
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class TickChartsProcessorService {
|
||||
@@ -45,7 +45,7 @@ export class TickChartsProcessorService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Tick charts...');
|
||||
|
||||
await Promise.all([
|
||||
@@ -64,6 +64,5 @@ export class TickChartsProcessorService {
|
||||
]);
|
||||
|
||||
this.logger.succ('All charts successfully ticked.');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Bull from 'bullmq';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { WebhooksRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -7,7 +8,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { WebhookDeliverJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -66,11 +66,11 @@ export class WebhookDeliverProcessorService {
|
||||
if (res instanceof StatusError) {
|
||||
// 4xx
|
||||
if (res.isClientError) {
|
||||
return `${res.statusCode} ${res.statusMessage}`;
|
||||
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
|
||||
}
|
||||
|
||||
// 5xx etc.
|
||||
throw `${res.statusCode} ${res.statusMessage}`;
|
||||
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
||||
} else {
|
||||
// DNS error, socket error, timeout ...
|
||||
throw res;
|
||||
|
@@ -194,7 +194,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||
|
||||
fastify.register(this.clientServerService.createServer);
|
||||
|
||||
this.streamingApiServerService.attachStreamingApi(fastify.server);
|
||||
this.streamingApiServerService.attach(fastify.server);
|
||||
|
||||
fastify.server.on('error', err => {
|
||||
switch ((err as any).code) {
|
||||
@@ -222,7 +222,14 @@ export class ServerService implements OnApplicationShutdown {
|
||||
await fastify.ready();
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
@bindThis
|
||||
public async dispose(): Promise<void> {
|
||||
await this.streamingApiServerService.detach();
|
||||
await this.#fastify.close();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
await this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -359,7 +359,12 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined) {
|
||||
public dispose(): void {
|
||||
clearInterval(this.userIpHistoriesClearIntervalId);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ export class AuthenticateService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async authenticate(token: string | null | undefined): Promise<[LocalUser | null | undefined, AccessToken | null | undefined]> {
|
||||
public async authenticate(token: string | null | undefined): Promise<[LocalUser | null, AccessToken | null]> {
|
||||
if (token == null) {
|
||||
return [null, null];
|
||||
}
|
||||
|
@@ -321,6 +321,9 @@ import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
||||
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
||||
import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
|
||||
import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
|
||||
import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
|
||||
import * as ep___users_notes from './endpoints/users/notes.js';
|
||||
import * as ep___users_pages from './endpoints/users/pages.js';
|
||||
import * as ep___users_reactions from './endpoints/users/reactions.js';
|
||||
@@ -659,6 +662,9 @@ const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass:
|
||||
const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
|
||||
const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
|
||||
const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
|
||||
const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
|
||||
const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
|
||||
const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default };
|
||||
const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
|
||||
const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
|
||||
const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default };
|
||||
@@ -1001,6 +1007,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_lists_push,
|
||||
$users_lists_show,
|
||||
$users_lists_update,
|
||||
$users_lists_favorite,
|
||||
$users_lists_unfavorite,
|
||||
$users_lists_create_from_public,
|
||||
$users_notes,
|
||||
$users_pages,
|
||||
$users_reactions,
|
||||
@@ -1335,6 +1344,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_lists_push,
|
||||
$users_lists_show,
|
||||
$users_lists_update,
|
||||
$users_lists_favorite,
|
||||
$users_lists_unfavorite,
|
||||
$users_lists_create_from_public,
|
||||
$users_notes,
|
||||
$users_pages,
|
||||
$users_reactions,
|
||||
|
@@ -1,23 +1,25 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import * as websocket from 'websocket';
|
||||
import * as WebSocket from 'ws';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js';
|
||||
import type { UsersRepository, AccessToken } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { AuthenticateService } from './AuthenticateService.js';
|
||||
import { LocalUser } from '@/models/entities/User';
|
||||
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
||||
import MainStreamConnection from './stream/index.js';
|
||||
import { ChannelsService } from './stream/ChannelsService.js';
|
||||
import type { ParsedUrlQuery } from 'querystring';
|
||||
import type * as http from 'node:http';
|
||||
|
||||
@Injectable()
|
||||
export class StreamingApiServerService {
|
||||
#wss: WebSocket.WebSocketServer;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
@@ -28,24 +30,6 @@ export class StreamingApiServerService {
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
@Inject(DI.renoteMutingsRepository)
|
||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||
|
||||
@Inject(DI.blockingsRepository)
|
||||
private blockingsRepository: BlockingsRepository,
|
||||
|
||||
@Inject(DI.channelFollowingsRepository)
|
||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
||||
|
||||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
private noteReadService: NoteReadService,
|
||||
private authenticateService: AuthenticateService,
|
||||
@@ -55,25 +39,65 @@ export class StreamingApiServerService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public attachStreamingApi(server: http.Server) {
|
||||
// Init websocket server
|
||||
const ws = new websocket.server({
|
||||
httpServer: server,
|
||||
public attach(server: http.Server): void {
|
||||
this.#wss = new WebSocket.WebSocketServer({
|
||||
noServer: true,
|
||||
});
|
||||
|
||||
ws.on('request', async (request) => {
|
||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
||||
|
||||
// TODO: トークンが間違ってるなどしてauthenticateに失敗したら
|
||||
// コネクション切断するなりエラーメッセージ返すなりする
|
||||
// (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので)
|
||||
const [user, miapp] = await this.authenticateService.authenticate(q.i as string);
|
||||
|
||||
if (user?.isSuspended) {
|
||||
request.reject(400);
|
||||
server.on('upgrade', async (request, socket, head) => {
|
||||
if (request.url == null) {
|
||||
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const q = new URL(request.url, `http://${request.headers.host}`).searchParams;
|
||||
|
||||
let user: LocalUser | null = null;
|
||||
let app: AccessToken | null = null;
|
||||
|
||||
try {
|
||||
[user, app] = await this.authenticateService.authenticate(q.get('i'));
|
||||
} catch (e) {
|
||||
if (e instanceof AuthenticationError) {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
} else {
|
||||
socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
|
||||
}
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (user?.isSuspended) {
|
||||
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = new MainStreamConnection(
|
||||
this.channelsService,
|
||||
this.noteReadService,
|
||||
this.notificationService,
|
||||
this.cacheService,
|
||||
user, app,
|
||||
);
|
||||
|
||||
await stream.init();
|
||||
|
||||
this.#wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
this.#wss.emit('connection', ws, request, {
|
||||
stream, user, app,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: {
|
||||
stream: MainStreamConnection,
|
||||
user: LocalUser | null;
|
||||
app: AccessToken | null
|
||||
}) => {
|
||||
const { stream, user, app } = ctx;
|
||||
|
||||
const ev = new EventEmitter();
|
||||
|
||||
async function onRedisMessage(_: string, data: string): Promise<void> {
|
||||
@@ -83,19 +107,7 @@ export class StreamingApiServerService {
|
||||
|
||||
this.redisForSub.on('message', onRedisMessage);
|
||||
|
||||
const main = new MainStreamConnection(
|
||||
this.channelsService,
|
||||
this.noteReadService,
|
||||
this.notificationService,
|
||||
this.cacheService,
|
||||
ev, user, miapp,
|
||||
);
|
||||
|
||||
await main.init();
|
||||
|
||||
const connection = request.accept();
|
||||
|
||||
main.init2(connection);
|
||||
await stream.listen(ev, connection);
|
||||
|
||||
const intervalId = user ? setInterval(() => {
|
||||
this.usersRepository.update(user.id, {
|
||||
@@ -110,16 +122,23 @@ export class StreamingApiServerService {
|
||||
|
||||
connection.once('close', () => {
|
||||
ev.removeAllListeners();
|
||||
main.dispose();
|
||||
stream.dispose();
|
||||
this.redisForSub.off('message', onRedisMessage);
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
});
|
||||
|
||||
connection.on('message', async (data) => {
|
||||
if (data.type === 'utf8' && data.utf8Data === 'ping') {
|
||||
if (data.toString() === 'ping') {
|
||||
connection.send('pong');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public detach(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
this.#wss.close(() => resolve());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -320,6 +320,9 @@ import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
||||
import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
|
||||
import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
|
||||
import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
|
||||
import * as ep___users_lists_update from './endpoints/users/lists/update.js';
|
||||
import * as ep___users_notes from './endpoints/users/notes.js';
|
||||
import * as ep___users_pages from './endpoints/users/pages.js';
|
||||
@@ -656,7 +659,10 @@ const eps = [
|
||||
['users/lists/pull', ep___users_lists_pull],
|
||||
['users/lists/push', ep___users_lists_push],
|
||||
['users/lists/show', ep___users_lists_show],
|
||||
['users/lists/favorite', ep___users_lists_favorite],
|
||||
['users/lists/unfavorite', ep___users_lists_unfavorite],
|
||||
['users/lists/update', ep___users_lists_update],
|
||||
['users/lists/create-from-public', ep___users_lists_create_from_public],
|
||||
['users/notes', ep___users_notes],
|
||||
['users/pages', ep___users_pages],
|
||||
['users/reactions', ep___users_reactions],
|
||||
|
@@ -25,7 +25,7 @@ export const paramDef = {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
title: { type: 'string', minLength: 1 },
|
||||
text: { type: 'string', minLength: 1 },
|
||||
imageUrl: { type: 'string', nullable: true, minLength: 1 },
|
||||
imageUrl: { type: 'string', nullable: true, minLength: 0 },
|
||||
},
|
||||
required: ['id', 'title', 'text', 'imageUrl'],
|
||||
} as const;
|
||||
@@ -46,7 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
updatedAt: new Date(),
|
||||
title: ps.title,
|
||||
text: ps.text,
|
||||
imageUrl: ps.imageUrl,
|
||||
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
|
||||
imageUrl: ps.imageUrl || null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
try {
|
||||
if (new URL(ps.inbox).protocol !== 'https:') throw 'https only';
|
||||
if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only');
|
||||
} catch {
|
||||
throw new ApiError(meta.errors.invalidUrl);
|
||||
}
|
||||
|
@@ -99,7 +99,7 @@ export const paramDef = {
|
||||
} },
|
||||
cw: { type: 'string', nullable: true, maxLength: 100 },
|
||||
localOnly: { type: 'boolean', default: false },
|
||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null },
|
||||
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
|
||||
noExtractMentions: { type: 'boolean', default: false },
|
||||
noExtractHashtags: { type: 'boolean', default: false },
|
||||
noExtractEmojis: { type: 'boolean', default: false },
|
||||
|
@@ -82,14 +82,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
try {
|
||||
if (ps.tag) {
|
||||
if (!safeForSql(normalizeForSearch(ps.tag))) throw 'Injection';
|
||||
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
|
||||
query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
|
||||
} else {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
for (const tags of ps.query!) {
|
||||
qb.orWhere(new Brackets(qb => {
|
||||
for (const tag of tags) {
|
||||
if (!safeForSql(normalizeForSearch(tag))) throw 'Injection';
|
||||
if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
|
||||
qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
|
||||
}
|
||||
}));
|
||||
|
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private redisClient: Redis.Redis,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
|
||||
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
||||
|
||||
await redisClient.flushdb();
|
||||
await resetDb(this.db);
|
||||
|
@@ -93,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.andWhere('(note.visibility = \'public\')')
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
.leftJoinAndSelect('note.renote', 'renote')
|
||||
|
@@ -0,0 +1,148 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserList } from '@/models/entities/UserList.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { UserListService } from '@/core/UserListService.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
prohibitMoved: true,
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserList',
|
||||
},
|
||||
|
||||
errors: {
|
||||
tooManyUserLists: {
|
||||
message: 'You cannot create user list any more.',
|
||||
code: 'TOO_MANY_USERLISTS',
|
||||
id: 'e9c105b2-c595-47de-97fb-7f7c2c33e92f',
|
||||
},
|
||||
noSuchList: {
|
||||
message: 'No such list.',
|
||||
code: 'NO_SUCH_LIST',
|
||||
id: '9292f798-6175-4f7d-93f4-b6742279667d',
|
||||
},
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '13c457db-a8cb-4d88-b70a-211ceeeabb5f',
|
||||
},
|
||||
|
||||
alreadyAdded: {
|
||||
message: 'That user has already been added to that list.',
|
||||
code: 'ALREADY_ADDED',
|
||||
id: 'c3ad6fdb-692b-47ee-a455-7bd12c7af615',
|
||||
},
|
||||
|
||||
youHaveBeenBlocked: {
|
||||
message: 'You cannot push this user because you have been blocked by this user.',
|
||||
code: 'YOU_HAVE_BEEN_BLOCKED',
|
||||
id: 'a2497f2a-2389-439c-8626-5298540530f4',
|
||||
},
|
||||
|
||||
tooManyUsers: {
|
||||
message: 'You can not push users any more.',
|
||||
code: 'TOO_MANY_USERS',
|
||||
id: '1845ea77-38d1-426e-8e4e-8b83b24f5bd7',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
listId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['name', 'listId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userListJoiningsRepository)
|
||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||
|
||||
@Inject(DI.blockingsRepository)
|
||||
private blockingsRepository: BlockingsRepository,
|
||||
|
||||
private userListService: UserListService,
|
||||
private userListEntityService: UserListEntityService,
|
||||
private idService: IdService,
|
||||
private getterService: GetterService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const list = await this.userListsRepository.findOneBy({
|
||||
id: ps.listId,
|
||||
isPublic: true,
|
||||
});
|
||||
if (list === null) throw new ApiError(meta.errors.noSuchList);
|
||||
const currentCount = await this.userListsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||
throw new ApiError(meta.errors.tooManyUserLists);
|
||||
}
|
||||
|
||||
const userList = await this.userListsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
userId: me.id,
|
||||
name: ps.name,
|
||||
} as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
const users = (await this.userListJoiningsRepository.findBy({
|
||||
userListId: ps.listId,
|
||||
})).map(x => x.userId);
|
||||
|
||||
for (const user of users) {
|
||||
const currentUser = await this.getterService.getUser(user).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (currentUser.id !== me.id) {
|
||||
const block = await this.blockingsRepository.findOneBy({
|
||||
blockerId: currentUser.id,
|
||||
blockeeId: me.id,
|
||||
});
|
||||
if (block) {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
const exist = await this.userListJoiningsRepository.findOneBy({
|
||||
userListId: userList.id,
|
||||
userId: currentUser.id,
|
||||
});
|
||||
|
||||
if (exist) {
|
||||
throw new ApiError(meta.errors.alreadyAdded);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.userListService.push(currentUser, userList, me);
|
||||
} catch (err) {
|
||||
if (err instanceof UserListService.TooManyUsersError) {
|
||||
throw new ApiError(meta.errors.tooManyUsers);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return await this.userListEntityService.pack(userList);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,70 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: 'No such user list.',
|
||||
code: 'NO_SUCH_USER_LIST',
|
||||
id: '7dbaf3cf-7b42-4b8f-b431-b3919e580dbe',
|
||||
},
|
||||
|
||||
alreadyFavorited: {
|
||||
message: 'The list has already been favorited.',
|
||||
code: 'ALREADY_FAVORITED',
|
||||
id: '6425bba0-985b-461e-af1b-518070e72081',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
listId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['listId'],
|
||||
} as const;
|
||||
|
||||
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor (
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userListFavoritesRepository)
|
||||
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userList = await this.userListsRepository.findOneBy({
|
||||
id: ps.listId,
|
||||
isPublic: true,
|
||||
});
|
||||
|
||||
if (userList === null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
|
||||
const exist = await this.userListFavoritesRepository.findOneBy({
|
||||
userId: me.id,
|
||||
userListId: ps.listId,
|
||||
});
|
||||
|
||||
if (exist !== null) {
|
||||
throw new ApiError(meta.errors.alreadyFavorited);
|
||||
}
|
||||
|
||||
await this.userListFavoritesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
userId: me.id,
|
||||
userListId: ps.listId,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,13 +1,14 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserListsRepository } from '@/models/index.js';
|
||||
import type { UserListsRepository, UsersRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['lists', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
requireCredential: false,
|
||||
|
||||
kind: 'read:account',
|
||||
|
||||
@@ -22,26 +23,58 @@ export const meta = {
|
||||
ref: 'UserList',
|
||||
},
|
||||
},
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'a8af4a82-0980-4cc4-a6af-8b0ffd54465e',
|
||||
},
|
||||
remoteUser: {
|
||||
message: 'Not allowed to load the remote user\'s list',
|
||||
code: 'REMOTE_USER_NOT_ALLOWED',
|
||||
id: '53858f1b-3315-4a01-81b7-db9b48d4b79a',
|
||||
},
|
||||
invalidParam: {
|
||||
message: 'Invalid param.',
|
||||
code: 'INVALID_PARAM',
|
||||
id: 'ab36de0e-29e9-48cb-9732-d82f1281620d',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
private userListEntityService: UserListEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userLists = await this.userListsRepository.findBy({
|
||||
if (typeof ps.userId !== 'undefined') {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
if (user === null) throw new ApiError(meta.errors.noSuchUser);
|
||||
if (user.host !== null) throw new ApiError(meta.errors.remoteUser);
|
||||
} else if (me === null) {
|
||||
throw new ApiError(meta.errors.invalidParam);
|
||||
}
|
||||
|
||||
const userLists = await this.userListsRepository.findBy(typeof ps.userId === 'undefined' && me !== null ? {
|
||||
userId: me.id,
|
||||
} : {
|
||||
userId: ps.userId,
|
||||
isPublic: true,
|
||||
});
|
||||
|
||||
return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserListsRepository } from '@/models/index.js';
|
||||
import type { UserListsRepository, UserListFavoritesRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -8,7 +8,7 @@ import { ApiError } from '../../../error.js';
|
||||
export const meta = {
|
||||
tags: ['lists', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
requireCredential: false,
|
||||
|
||||
kind: 'read:account',
|
||||
|
||||
@@ -33,31 +33,54 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
listId: { type: 'string', format: 'misskey:id' },
|
||||
forPublic: { type: 'boolean', default: false },
|
||||
},
|
||||
required: ['listId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userListFavoritesRepository)
|
||||
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||
|
||||
private userListEntityService: UserListEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const additionalProperties: Partial<{ likedCount: number, isLiked: boolean }> = {};
|
||||
// Fetch the list
|
||||
const userList = await this.userListsRepository.findOneBy({
|
||||
const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? {
|
||||
id: ps.listId,
|
||||
userId: me.id,
|
||||
} : {
|
||||
id: ps.listId,
|
||||
isPublic: true,
|
||||
});
|
||||
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
|
||||
return await this.userListEntityService.pack(userList);
|
||||
if (ps.forPublic && userList.isPublic) {
|
||||
additionalProperties.likedCount = await this.userListFavoritesRepository.countBy({
|
||||
userListId: ps.listId,
|
||||
});
|
||||
if (me !== null) {
|
||||
additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({
|
||||
userId: me.id,
|
||||
userListId: ps.listId,
|
||||
}) !== null);
|
||||
} else {
|
||||
additionalProperties.isLiked = false;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...await this.userListEntityService.pack(userList),
|
||||
...additionalProperties,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,63 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: 'No such user list.',
|
||||
code: 'NO_SUCH_USER_LIST',
|
||||
id: 'baedb33e-76b8-4b0c-86a8-9375c0a7b94b',
|
||||
},
|
||||
|
||||
notFavorited: {
|
||||
message: 'You have not favorited the list.',
|
||||
code: 'ALREADY_FAVORITED',
|
||||
id: '835c4b27-463d-4cfa-969b-a9058678d465',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
listId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['listId'],
|
||||
} as const;
|
||||
|
||||
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor (
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userListFavoritesRepository)
|
||||
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userList = await this.userListsRepository.findOneBy({
|
||||
id: ps.listId,
|
||||
isPublic: true,
|
||||
});
|
||||
|
||||
if (userList === null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
|
||||
const exist = await this.userListFavoritesRepository.findOneBy({
|
||||
userListId: ps.listId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (exist === null) {
|
||||
throw new ApiError(meta.errors.notFavorited);
|
||||
}
|
||||
|
||||
await this.userListFavoritesRepository.delete({ id: exist.id });
|
||||
});
|
||||
}
|
||||
}
|
@@ -34,8 +34,9 @@ export const paramDef = {
|
||||
properties: {
|
||||
listId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
isPublic: { type: 'boolean' },
|
||||
},
|
||||
required: ['listId', 'name'],
|
||||
required: ['listId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -48,7 +49,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private userListEntityService: UserListEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the list
|
||||
const userList = await this.userListsRepository.findOneBy({
|
||||
id: ps.listId,
|
||||
userId: me.id,
|
||||
@@ -60,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
await this.userListsRepository.update(userList.id, {
|
||||
name: ps.name,
|
||||
isPublic: ps.isPublic,
|
||||
});
|
||||
|
||||
return await this.userListEntityService.pack(userList.id);
|
||||
|
@@ -5,15 +5,17 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import Channel from '../channel.js';
|
||||
import { StreamMessages } from '../types.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
class RoleTimelineChannel extends Channel {
|
||||
public readonly chName = 'roleTimeline';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = false;
|
||||
private roleId: string;
|
||||
|
||||
|
||||
constructor(
|
||||
private noteEntityService: NoteEntityService,
|
||||
private roleservice: RoleService,
|
||||
|
||||
id: string,
|
||||
connection: Channel['connection'],
|
||||
@@ -34,6 +36,11 @@ class RoleTimelineChannel extends Channel {
|
||||
if (data.type === 'note') {
|
||||
const note = data.body;
|
||||
|
||||
if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
|
||||
return;
|
||||
}
|
||||
if (note.visibility !== 'public') return;
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
@@ -61,6 +68,7 @@ export class RoleTimelineChannelService {
|
||||
|
||||
constructor(
|
||||
private noteEntityService: NoteEntityService,
|
||||
private roleservice: RoleService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -68,6 +76,7 @@ export class RoleTimelineChannelService {
|
||||
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
||||
return new RoleTimelineChannel(
|
||||
this.noteEntityService,
|
||||
this.roleservice,
|
||||
id,
|
||||
connection,
|
||||
);
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import * as WebSocket from 'ws';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
@@ -7,7 +8,6 @@ import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { UserProfile } from '@/models/index.js';
|
||||
import type { ChannelsService } from './ChannelsService.js';
|
||||
import type * as websocket from 'websocket';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type Channel from './channel.js';
|
||||
import type { StreamEventEmitter, StreamMessages } from './types.js';
|
||||
@@ -18,7 +18,7 @@ import type { StreamEventEmitter, StreamMessages } from './types.js';
|
||||
export default class Connection {
|
||||
public user?: User;
|
||||
public token?: AccessToken;
|
||||
private wsConnection: websocket.connection;
|
||||
private wsConnection: WebSocket.WebSocket;
|
||||
public subscriber: StreamEventEmitter;
|
||||
private channels: Channel[] = [];
|
||||
private subscribingNotes: any = {};
|
||||
@@ -37,11 +37,9 @@ export default class Connection {
|
||||
private notificationService: NotificationService,
|
||||
private cacheService: CacheService,
|
||||
|
||||
subscriber: EventEmitter,
|
||||
user: User | null | undefined,
|
||||
token: AccessToken | null | undefined,
|
||||
) {
|
||||
this.subscriber = subscriber;
|
||||
if (user) this.user = user;
|
||||
if (token) this.token = token;
|
||||
}
|
||||
@@ -70,12 +68,16 @@ export default class Connection {
|
||||
if (this.user != null) {
|
||||
await this.fetch();
|
||||
|
||||
this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
|
||||
if (!this.fetchIntervalId) {
|
||||
this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init2(wsConnection: websocket.connection) {
|
||||
public async listen(subscriber: EventEmitter, wsConnection: WebSocket.WebSocket) {
|
||||
this.subscriber = subscriber;
|
||||
|
||||
this.wsConnection = wsConnection;
|
||||
this.wsConnection.on('message', this.onWsConnectionMessage);
|
||||
|
||||
@@ -88,14 +90,11 @@ export default class Connection {
|
||||
* クライアントからメッセージ受信時
|
||||
*/
|
||||
@bindThis
|
||||
private async onWsConnectionMessage(data: websocket.Message) {
|
||||
if (data.type !== 'utf8') return;
|
||||
if (data.utf8Data == null) return;
|
||||
|
||||
private async onWsConnectionMessage(data: WebSocket.RawData) {
|
||||
let obj: Record<string, any>;
|
||||
|
||||
try {
|
||||
obj = JSON.parse(data.utf8Data);
|
||||
obj = JSON.parse(data.toString());
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
@@ -160,37 +160,41 @@
|
||||
<path d="M12 9v2m0 4v.01"></path>
|
||||
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
|
||||
</svg>
|
||||
<h1>An error has occurred!</h1>
|
||||
<button class="button-big" onclick="location.reload();">
|
||||
<span class="button-label-big">Refresh</span>
|
||||
<h1>Failed to load<br>読み込みに失敗しました</h1>
|
||||
<button class="button-big" onclick="location.reload(true);">
|
||||
<span class="button-label-big">Reload / リロード</span>
|
||||
</button>
|
||||
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
|
||||
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
|
||||
<p>Update your os and browser.</p>
|
||||
<p>Disable an adblocker.</p>
|
||||
<a href="/flush">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Clear preferences and cache</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/cli">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Start the simple client</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/bios">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Start the repair tool</span>
|
||||
</button>
|
||||
</a>
|
||||
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります。</b></p>
|
||||
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
|
||||
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
|
||||
<p>Disable an adblocker / アドブロッカーを無効にする</p>
|
||||
<details style="color: #86b300;">
|
||||
<summary>Other options / その他のオプション</summary>
|
||||
<a href="/flush">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Clear preferences and cache</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/cli">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Start the simple client</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/bios">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Start the repair tool</span>
|
||||
</button>
|
||||
</a>
|
||||
</details>
|
||||
<br>
|
||||
<div id="errors"></div>
|
||||
`;
|
||||
errorsElement = document.getElementById('errors');
|
||||
}
|
||||
const detailsElement = document.createElement('details');
|
||||
detailsElement.id = 'errorInfo';
|
||||
detailsElement.innerHTML = `
|
||||
<br>
|
||||
<summary>
|
||||
@@ -247,7 +251,7 @@
|
||||
.button-label-big {
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
font-size: 1.2em;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
@@ -267,11 +271,6 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dont-worry,
|
||||
#msg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
color: #dec340;
|
||||
height: 4rem;
|
||||
@@ -279,14 +278,15 @@
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-size: 1.5em;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Fira, FiraCode, monospace;
|
||||
}
|
||||
|
||||
details {
|
||||
#errorInfo {
|
||||
background: #333;
|
||||
margin-bottom: 2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
@@ -296,16 +296,16 @@
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
summary {
|
||||
#errorInfo summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
summary > * {
|
||||
#errorInfo summary > * {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
details {
|
||||
#errorInfo {
|
||||
width: 50%;
|
||||
}
|
||||
`)
|
||||
|
@@ -25,7 +25,6 @@ html
|
||||
meta(name='referrer' content='origin')
|
||||
meta(name='theme-color' content= themeColor || '#86b300')
|
||||
meta(name='theme-color-orig' content= themeColor || '#86b300')
|
||||
meta(property='twitter:card' content='summary')
|
||||
meta(property='og:site_name' content= instanceName || 'Misskey')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
link(rel='icon' href= icon || '/favicon.ico')
|
||||
@@ -59,6 +58,7 @@ html
|
||||
meta(property='og:title' content= title || 'Misskey')
|
||||
meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
||||
meta(property='og:image' content= img)
|
||||
meta(property='twitter:card' content='summary')
|
||||
|
||||
style
|
||||
include ../style.css
|
||||
|
@@ -16,3 +16,4 @@ block og
|
||||
meta(property='og:description' content= channel.description)
|
||||
meta(property='og:url' content= url)
|
||||
meta(property='og:image' content= channel.bannerUrl)
|
||||
meta(property='twitter:card' content='summary')
|
||||
|
@@ -17,6 +17,7 @@ block og
|
||||
meta(property='og:description' content= clip.description)
|
||||
meta(property='og:url' content= url)
|
||||
meta(property='og:image' content= avatarUrl)
|
||||
meta(property='twitter:card' content='summary')
|
||||
|
||||
block meta
|
||||
if profile.noCrawle
|
||||
|
@@ -17,6 +17,7 @@ block og
|
||||
meta(property='og:description' content= flash.summary)
|
||||
meta(property='og:url' content= url)
|
||||
meta(property='og:image' content= avatarUrl)
|
||||
meta(property='twitter:card' content='summary')
|
||||
|
||||
block meta
|
||||
if profile.noCrawle
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user