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
|
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
|
||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
fi
|
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:
|
env:
|
||||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
- name: Notify that Chromatic detects changes
|
- name: Notify that Chromatic detects changes
|
||||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@@ -17,13 +17,30 @@
|
|||||||
### General
|
### General
|
||||||
- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
|
- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
|
||||||
- カスタム絵文字ごとに連合するかどうか設定できるように
|
- カスタム絵文字ごとに連合するかどうか設定できるように
|
||||||
|
- カスタム絵文字ごとにセンシティブフラグを設定できるように
|
||||||
|
- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に
|
||||||
- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
|
- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
|
||||||
- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります
|
- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります
|
||||||
|
- リストを公開できるようになりました
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
- リアクションの取り消し/変更時に確認ダイアログを出すように
|
||||||
- 開発者モードを追加
|
- 開発者モードを追加
|
||||||
- AiScriptを0.13.3に更新
|
- AiScriptを0.13.3に更新
|
||||||
|
- Deck UIを使用している場合、`/`以外にアクセスした際にZen UIで表示するように
|
||||||
|
- メインカラムを設置していない場合の問題を解決
|
||||||
|
- ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加
|
||||||
|
- アカウント初期設定ウィザードに戻るボタンを追加
|
||||||
|
- アカウントの初期設定ウィザードにあとでボタンを追加
|
||||||
- Fix: URLプレビューで情報が取得できなかった際の挙動を修正
|
- Fix: URLプレビューで情報が取得できなかった際の挙動を修正
|
||||||
|
- Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正
|
||||||
|
- fix:ロールタイムラインが無効でも投稿が流れてしまう問題の修正
|
||||||
|
- fix:ロールタイムラインにて全ての投稿が流れてしまう問題の修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- bullをbull-mqにアップグレードし、ジョブキューのパフォーマンスを改善
|
||||||
|
- ストリーミングのパフォーマンスを改善
|
||||||
|
- Fix: お知らせの画像URLを空にできない問題を修正
|
||||||
|
|
||||||
## 13.12.2
|
## 13.12.2
|
||||||
|
|
||||||
@@ -100,6 +117,7 @@ Meilisearchの設定に`index`が必要になりました。値はMisskeyサー
|
|||||||
* 画像が全て隠れた状態で表示されるようになります
|
* 画像が全て隠れた状態で表示されるようになります
|
||||||
- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
|
- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
|
||||||
- モデレーターはノートに添付された画像上から直接NSFW設定できるように
|
- モデレーターはノートに添付された画像上から直接NSFW設定できるように
|
||||||
|
- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
|
||||||
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
||||||
- 新しい実績を追加
|
- 新しい実績を追加
|
||||||
- AiScriptを0.13.2に更新
|
- AiScriptを0.13.2に更新
|
||||||
|
@@ -169,20 +169,25 @@ describe('After user signed in', () => {
|
|||||||
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
|
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
|
||||||
// TODO: アイコン設定テスト
|
// TODO: アイコン設定テスト
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').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-continue]').click();
|
||||||
|
|
||||||
// フォローはスキップ
|
// フォローはスキップ
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').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-continue]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy-user-setup-back]').click();
|
||||||
cy.get('[data-cy-user-setup-continue]').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プロテクションが設定されていません。"
|
noBotProtectionWarning: "Botプロテクションが設定されていません。"
|
||||||
configure: "設定する"
|
configure: "設定する"
|
||||||
postToGallery: "ギャラリーへ投稿"
|
postToGallery: "ギャラリーへ投稿"
|
||||||
|
postToHashtag: "このハッシュタグで投稿"
|
||||||
gallery: "ギャラリー"
|
gallery: "ギャラリー"
|
||||||
recentPosts: "最近の投稿"
|
recentPosts: "最近の投稿"
|
||||||
popularPosts: "人気の投稿"
|
popularPosts: "人気の投稿"
|
||||||
@@ -990,7 +991,9 @@ postToTheChannel: "チャンネルに投稿"
|
|||||||
cannotBeChangedLater: "後から変更できません。"
|
cannotBeChangedLater: "後から変更できません。"
|
||||||
reactionAcceptance: "リアクションの受け入れ"
|
reactionAcceptance: "リアクションの受け入れ"
|
||||||
likeOnly: "いいねのみ"
|
likeOnly: "いいねのみ"
|
||||||
likeOnlyForRemote: "リモートからはいいねのみ"
|
likeOnlyForRemote: "全て (リモートはいいねのみ)"
|
||||||
|
nonSensitiveOnly: "非センシティブのみ"
|
||||||
|
nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
|
||||||
rolesAssignedToMe: "自分に割り当てられたロール"
|
rolesAssignedToMe: "自分に割り当てられたロール"
|
||||||
resetPasswordConfirm: "パスワードリセットしますか?"
|
resetPasswordConfirm: "パスワードリセットしますか?"
|
||||||
sensitiveWords: "センシティブワード"
|
sensitiveWords: "センシティブワード"
|
||||||
@@ -1053,6 +1056,10 @@ update: "更新"
|
|||||||
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
|
rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
|
||||||
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
|
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
|
||||||
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
|
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
|
||||||
|
cancelReactionConfirm: "リアクションを取り消しますか?"
|
||||||
|
changeReactionConfirm: "リアクションを変更しますか?"
|
||||||
|
later: "あとで"
|
||||||
|
goToMisskey: "Misskeyへ"
|
||||||
|
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "アカウントの作成が完了しました!"
|
accountCreated: "アカウントの作成が完了しました!"
|
||||||
@@ -1068,6 +1075,7 @@ _initialAccountSetting:
|
|||||||
haveFun: "{name}をお楽しみください!"
|
haveFun: "{name}をお楽しみください!"
|
||||||
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
|
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
|
||||||
skipAreYouSure: "初期設定をスキップしますか?"
|
skipAreYouSure: "初期設定をスキップしますか?"
|
||||||
|
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
||||||
|
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "13.13.0-beta.1",
|
"version": "13.13.0-beta.4",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.3.1",
|
"packageManager": "pnpm@8.5.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||||
"@typescript-eslint/parser": "5.59.5",
|
"@typescript-eslint/parser": "5.59.5",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "12.12.0",
|
"cypress": "12.13.0",
|
||||||
"eslint": "8.40.0",
|
"eslint": "8.40.0",
|
||||||
"start-server-and-test": "2.0.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",
|
"@swc/core-win32-x64-msvc": "1.3.56",
|
||||||
"@tensorflow/tfjs": "4.4.0",
|
"@tensorflow/tfjs": "4.4.0",
|
||||||
"@tensorflow/tfjs-node": "4.4.0",
|
"@tensorflow/tfjs-node": "4.4.0",
|
||||||
"slacc-android-arm-eabi": "0.0.7",
|
"bufferutil": "^4.0.7",
|
||||||
"slacc-android-arm64": "0.0.7",
|
"slacc-android-arm-eabi": "0.0.9",
|
||||||
"slacc-darwin-arm64": "0.0.7",
|
"slacc-android-arm64": "0.0.9",
|
||||||
"slacc-darwin-universal": "0.0.7",
|
"slacc-darwin-arm64": "0.0.9",
|
||||||
"slacc-darwin-x64": "0.0.7",
|
"slacc-darwin-universal": "0.0.9",
|
||||||
"slacc-linux-arm-gnueabihf": "0.0.7",
|
"slacc-darwin-x64": "0.0.9",
|
||||||
"slacc-linux-arm64-gnu": "0.0.7",
|
"slacc-freebsd-x64": "0.0.9",
|
||||||
"slacc-linux-arm64-musl": "0.0.7",
|
"slacc-linux-arm-gnueabihf": "0.0.9",
|
||||||
"slacc-linux-x64-gnu": "0.0.7",
|
"slacc-linux-arm64-gnu": "0.0.9",
|
||||||
"slacc-win32-arm64-msvc": "0.0.7",
|
"slacc-linux-arm64-musl": "0.0.9",
|
||||||
"slacc-win32-x64-msvc": "0.0.7"
|
"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": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.321.1",
|
"@aws-sdk/client-s3": "3.321.1",
|
||||||
"@aws-sdk/lib-storage": "3.321.1",
|
"@aws-sdk/lib-storage": "3.321.1",
|
||||||
"@aws-sdk/node-http-handler": "3.321.1",
|
"@aws-sdk/node-http-handler": "3.321.1",
|
||||||
"@bull-board/api": "5.1.2",
|
"@bull-board/api": "5.2.0",
|
||||||
"@bull-board/fastify": "5.1.2",
|
"@bull-board/fastify": "5.2.0",
|
||||||
"@bull-board/ui": "5.1.2",
|
"@bull-board/ui": "5.2.0",
|
||||||
"@discordapp/twemoji": "14.1.2",
|
"@discordapp/twemoji": "14.1.2",
|
||||||
"@fastify/accepts": "4.1.0",
|
"@fastify/accepts": "4.1.0",
|
||||||
"@fastify/cookie": "8.3.0",
|
"@fastify/cookie": "8.3.0",
|
||||||
@@ -62,20 +65,20 @@
|
|||||||
"@fastify/multipart": "7.6.0",
|
"@fastify/multipart": "7.6.0",
|
||||||
"@fastify/static": "6.10.1",
|
"@fastify/static": "6.10.1",
|
||||||
"@fastify/view": "7.4.1",
|
"@fastify/view": "7.4.1",
|
||||||
"@nestjs/common": "9.4.0",
|
"@nestjs/common": "9.4.2",
|
||||||
"@nestjs/core": "9.4.0",
|
"@nestjs/core": "9.4.2",
|
||||||
"@nestjs/testing": "9.4.0",
|
"@nestjs/testing": "9.4.2",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sinonjs/fake-timers": "10.0.2",
|
"@sinonjs/fake-timers": "10.0.2",
|
||||||
"@swc/cli": "0.1.62",
|
"@swc/cli": "0.1.62",
|
||||||
"@swc/core": "1.3.56",
|
"@swc/core": "1.3.59",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.12.0",
|
||||||
"archiver": "5.3.1",
|
"archiver": "5.3.1",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"bull": "4.10.4",
|
"bullmq": "3.14.1",
|
||||||
"cacheable-lookup": "6.1.0",
|
"cacheable-lookup": "6.1.0",
|
||||||
"cbor": "8.1.0",
|
"cbor": "8.1.0",
|
||||||
"chalk": "5.2.0",
|
"chalk": "5.2.0",
|
||||||
@@ -93,7 +96,7 @@
|
|||||||
"fluent-ffmpeg": "2.1.2",
|
"fluent-ffmpeg": "2.1.2",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"got": "12.6.0",
|
"got": "12.6.0",
|
||||||
"happy-dom": "9.16.0",
|
"happy-dom": "9.19.2",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"ip-cidr": "3.1.0",
|
"ip-cidr": "3.1.0",
|
||||||
@@ -102,8 +105,8 @@
|
|||||||
"jsdom": "21.1.1",
|
"jsdom": "21.1.1",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.1.1",
|
"jsonld": "8.1.1",
|
||||||
"meilisearch": "0.32.3",
|
|
||||||
"jsrsasign": "10.8.6",
|
"jsrsasign": "10.8.6",
|
||||||
|
"meilisearch": "0.32.4",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
@@ -116,7 +119,7 @@
|
|||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.1.2",
|
"otpauth": "9.1.2",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"pg": "8.10.0",
|
"pg": "8.11.0",
|
||||||
"private-ip": "3.0.0",
|
"private-ip": "3.0.0",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
@@ -136,10 +139,10 @@
|
|||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sanitize-html": "2.10.0",
|
"sanitize-html": "2.10.0",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"semver": "7.5.0",
|
"semver": "7.5.1",
|
||||||
"sharp": "0.32.1",
|
"sharp": "0.32.1",
|
||||||
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
|
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
|
||||||
"slacc": "0.0.7",
|
"slacc": "0.0.9",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"summaly": "github:misskey-dev/summaly",
|
"summaly": "github:misskey-dev/summaly",
|
||||||
@@ -156,7 +159,6 @@
|
|||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
"web-push": "3.6.1",
|
"web-push": "3.6.1",
|
||||||
"websocket": "1.0.34",
|
|
||||||
"ws": "8.13.0",
|
"ws": "8.13.0",
|
||||||
"xev": "3.0.2"
|
"xev": "3.0.2"
|
||||||
},
|
},
|
||||||
@@ -166,7 +168,6 @@
|
|||||||
"@types/accepts": "1.3.5",
|
"@types/accepts": "1.3.5",
|
||||||
"@types/archiver": "5.3.2",
|
"@types/archiver": "5.3.2",
|
||||||
"@types/bcryptjs": "2.4.2",
|
"@types/bcryptjs": "2.4.2",
|
||||||
"@types/bull": "4.10.0",
|
|
||||||
"@types/cbor": "6.0.0",
|
"@types/cbor": "6.0.0",
|
||||||
"@types/color-convert": "2.0.0",
|
"@types/color-convert": "2.0.0",
|
||||||
"@types/content-disposition": "0.5.5",
|
"@types/content-disposition": "0.5.5",
|
||||||
@@ -178,11 +179,11 @@
|
|||||||
"@types/jsonld": "1.5.8",
|
"@types/jsonld": "1.5.8",
|
||||||
"@types/jsrsasign": "10.5.8",
|
"@types/jsrsasign": "10.5.8",
|
||||||
"@types/mime-types": "2.1.1",
|
"@types/mime-types": "2.1.1",
|
||||||
"@types/node": "20.1.3",
|
"@types/node": "20.2.3",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.7",
|
"@types/nodemailer": "6.4.8",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
"@types/pg": "8.6.6",
|
"@types/pg": "8.10.1",
|
||||||
"@types/pug": "2.0.6",
|
"@types/pug": "2.0.6",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
"@types/qrcode": "1.5.0",
|
"@types/qrcode": "1.5.0",
|
||||||
@@ -196,7 +197,7 @@
|
|||||||
"@types/sinonjs__fake-timers": "8.1.2",
|
"@types/sinonjs__fake-timers": "8.1.2",
|
||||||
"@types/tinycolor2": "1.4.3",
|
"@types/tinycolor2": "1.4.3",
|
||||||
"@types/tmp": "0.2.3",
|
"@types/tmp": "0.2.3",
|
||||||
"@types/unzipper": "0.10.5",
|
"@types/unzipper": "0.10.6",
|
||||||
"@types/uuid": "9.0.1",
|
"@types/uuid": "9.0.1",
|
||||||
"@types/vary": "1.1.0",
|
"@types/vary": "1.1.0",
|
||||||
"@types/web-push": "3.3.2",
|
"@types/web-push": "3.3.2",
|
||||||
|
@@ -4,7 +4,7 @@ import * as Redis from 'ioredis';
|
|||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { MeiliSearch } from 'meilisearch';
|
import { MeiliSearch } from 'meilisearch';
|
||||||
import { DI } from './di-symbols.js';
|
import { DI } from './di-symbols.js';
|
||||||
import { loadConfig } from './config.js';
|
import { Config, loadConfig } from './config.js';
|
||||||
import { createPostgresDataSource } from './postgres.js';
|
import { createPostgresDataSource } from './postgres.js';
|
||||||
import { RepositoryModule } from './models/RepositoryModule.js';
|
import { RepositoryModule } from './models/RepositoryModule.js';
|
||||||
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
|
||||||
@@ -25,7 +25,7 @@ const $db: Provider = {
|
|||||||
|
|
||||||
const $meilisearch: Provider = {
|
const $meilisearch: Provider = {
|
||||||
provide: DI.meilisearch,
|
provide: DI.meilisearch,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
if (config.meilisearch) {
|
if (config.meilisearch) {
|
||||||
return new MeiliSearch({
|
return new MeiliSearch({
|
||||||
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
|
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||||
@@ -40,7 +40,7 @@ const $meilisearch: Provider = {
|
|||||||
|
|
||||||
const $redis: Provider = {
|
const $redis: Provider = {
|
||||||
provide: DI.redis,
|
provide: DI.redis,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
return new Redis.Redis({
|
return new Redis.Redis({
|
||||||
port: config.redis.port,
|
port: config.redis.port,
|
||||||
host: config.redis.host,
|
host: config.redis.host,
|
||||||
@@ -55,7 +55,7 @@ const $redis: Provider = {
|
|||||||
|
|
||||||
const $redisForPub: Provider = {
|
const $redisForPub: Provider = {
|
||||||
provide: DI.redisForPub,
|
provide: DI.redisForPub,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
const redis = new Redis.Redis({
|
const redis = new Redis.Redis({
|
||||||
port: config.redisForPubsub.port,
|
port: config.redisForPubsub.port,
|
||||||
host: config.redisForPubsub.host,
|
host: config.redisForPubsub.host,
|
||||||
@@ -71,7 +71,7 @@ const $redisForPub: Provider = {
|
|||||||
|
|
||||||
const $redisForSub: Provider = {
|
const $redisForSub: Provider = {
|
||||||
provide: DI.redisForSub,
|
provide: DI.redisForSub,
|
||||||
useFactory: (config) => {
|
useFactory: (config: Config) => {
|
||||||
const redis = new Redis.Redis({
|
const redis = new Redis.Redis({
|
||||||
port: config.redisForPubsub.port,
|
port: config.redisForPubsub.port,
|
||||||
host: config.redisForPubsub.host,
|
host: config.redisForPubsub.host,
|
||||||
@@ -100,7 +100,7 @@ export class GlobalModule implements OnApplicationShutdown {
|
|||||||
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async onApplicationShutdown(signal: string): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
// XXX:
|
// XXX:
|
||||||
// Shutting down the existing connections causes errors on Jest as
|
// Shutting down the existing connections causes errors on Jest as
|
||||||
@@ -116,4 +116,8 @@ export class GlobalModule implements OnApplicationShutdown {
|
|||||||
this.redisForSub.disconnect(),
|
this.redisForSub.disconnect(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onApplicationShutdown(signal: string): Promise<void> {
|
||||||
|
await this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -190,6 +190,6 @@ function tryCreateUrl(url: string) {
|
|||||||
try {
|
try {
|
||||||
return new URL(url);
|
return new URL(url);
|
||||||
} catch (e) {
|
} 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);
|
this.redisForSub.on('message', this.onRedisMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
|
||||||
this.redisForSub.off('message', this.onRedisMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async onRedisMessage(_: string, data: string): Promise<void> {
|
private async onRedisMessage(_: string, data: string): Promise<void> {
|
||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
@@ -196,4 +191,14 @@ export class AntennaService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
return this.antennas;
|
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
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
this.redisForSub.off('message', this.onMessage);
|
this.redisForSub.off('message', this.onMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ export class CaptchaService {
|
|||||||
}, { throwErrorWhenResponseNotOk: false });
|
}, { throwErrorWhenResponseNotOk: false });
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw `${res.status}`;
|
throw new Error(`${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await res.json() as CaptchaResponse;
|
return await res.json() as CaptchaResponse;
|
||||||
@@ -39,48 +39,48 @@ export class CaptchaService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||||
if (response == null) {
|
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 => {
|
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) {
|
if (result.success !== true) {
|
||||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||||
throw `recaptcha-failed: ${errorCodes}`;
|
throw new Error(`recaptcha-failed: ${errorCodes}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
|
||||||
if (response == null) {
|
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 => {
|
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) {
|
if (result.success !== true) {
|
||||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
||||||
throw `hcaptcha-failed: ${errorCodes}`;
|
throw new Error(`hcaptcha-failed: ${errorCodes}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
|
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
|
||||||
if (response == null) {
|
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 => {
|
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) {
|
if (result.success !== true) {
|
||||||
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
|
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')
|
const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.statusCode === 404) {
|
if (err.statusCode === 404) {
|
||||||
throw 'No nodeinfo provided';
|
throw new Error('No nodeinfo provided');
|
||||||
} else {
|
} else {
|
||||||
throw err.statusCode ?? err.message;
|
throw err.statusCode ?? err.message;
|
||||||
}
|
}
|
||||||
}) as Record<string, unknown>;
|
}) as Record<string, unknown>;
|
||||||
|
|
||||||
if (wellknown.links == null || !Array.isArray(wellknown.links)) {
|
if (wellknown.links == null || !Array.isArray(wellknown.links)) {
|
||||||
throw 'No wellknown links';
|
throw new Error('No wellknown links');
|
||||||
}
|
}
|
||||||
|
|
||||||
const links = wellknown.links as any[];
|
const links = wellknown.links as any[];
|
||||||
@@ -134,7 +134,7 @@ export class FetchInstanceMetadataService {
|
|||||||
const link = lnik2_1 ?? lnik2_0 ?? lnik1_0;
|
const link = lnik2_1 ?? lnik2_0 ?? lnik1_0;
|
||||||
|
|
||||||
if (link == null) {
|
if (link == null) {
|
||||||
throw 'No nodeinfo link provided';
|
throw new Error('No nodeinfo link provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = await this.httpRequestService.getJson(link.href)
|
const info = await this.httpRequestService.getJson(link.href)
|
||||||
|
@@ -120,8 +120,13 @@ export class MetaService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
this.redisForSub.off('message', this.onMessage);
|
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) {
|
if (data.poll && data.poll.expiresAt) {
|
||||||
const delay = data.poll.expiresAt.getTime() - Date.now();
|
const delay = data.poll.expiresAt.getTime() - Date.now();
|
||||||
this.queueService.endedPollNotificationQueue.add({
|
this.queueService.endedPollNotificationQueue.add(note.id, {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
}, {
|
}, {
|
||||||
delay,
|
delay,
|
||||||
@@ -790,7 +790,13 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
return mentionedUsers;
|
return mentionedUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
onApplicationShutdown(signal?: string | undefined) {
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
this.#shutdownController.abort();
|
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();
|
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();
|
this.#shutdownController.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,42 +1,11 @@
|
|||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import Bull from 'bull';
|
import * as Bull from 'bullmq';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, DbJobMap } from '../queue/types.js';
|
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||||
@@ -49,49 +18,49 @@ export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
|||||||
|
|
||||||
const $system: Provider = {
|
const $system: Provider = {
|
||||||
provide: 'queue:system',
|
provide: 'queue:system',
|
||||||
useFactory: (config: Config) => q(config, 'system'),
|
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)),
|
||||||
inject: [DI.config],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $endedPollNotification: Provider = {
|
const $endedPollNotification: Provider = {
|
||||||
provide: 'queue:endedPollNotification',
|
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],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $deliver: Provider = {
|
const $deliver: Provider = {
|
||||||
provide: 'queue:deliver',
|
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],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $inbox: Provider = {
|
const $inbox: Provider = {
|
||||||
provide: 'queue:inbox',
|
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],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $db: Provider = {
|
const $db: Provider = {
|
||||||
provide: 'queue:db',
|
provide: 'queue:db',
|
||||||
useFactory: (config: Config) => q(config, 'db'),
|
useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)),
|
||||||
inject: [DI.config],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $relationship: Provider = {
|
const $relationship: Provider = {
|
||||||
provide: 'queue:relationship',
|
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],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $objectStorage: Provider = {
|
const $objectStorage: Provider = {
|
||||||
provide: 'queue:objectStorage',
|
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],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
const $webhookDeliver: Provider = {
|
const $webhookDeliver: Provider = {
|
||||||
provide: 'queue:webhookDeliver',
|
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],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,7 +100,7 @@ export class QueueModule implements OnApplicationShutdown {
|
|||||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async onApplicationShutdown(signal: string): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
// XXX:
|
// XXX:
|
||||||
// Shutting down the existing connections causes errors on Jest as
|
// Shutting down the existing connections causes errors on Jest as
|
||||||
@@ -151,4 +120,8 @@ export class QueueModule implements OnApplicationShutdown {
|
|||||||
this.webhookDeliverQueue.close(),
|
this.webhookDeliverQueue.close(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onApplicationShutdown(signal: string): Promise<void> {
|
||||||
|
await this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import Bull from 'bull';
|
|
||||||
import type { IActivity } from '@/core/activitypub/type.js';
|
import type { IActivity } from '@/core/activitypub/type.js';
|
||||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||||
import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.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 { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
|
||||||
import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
|
import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
|
||||||
import type httpSignature from '@peertube/http-signature';
|
import type httpSignature from '@peertube/http-signature';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueService {
|
export class QueueService {
|
||||||
@@ -26,7 +26,43 @@ export class QueueService {
|
|||||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
@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
|
@bindThis
|
||||||
public deliver(user: ThinUser, content: IActivity | null, to: string | null, isSharedInbox: boolean) {
|
public deliver(user: ThinUser, content: IActivity | null, to: string | null, isSharedInbox: boolean) {
|
||||||
@@ -42,11 +78,10 @@ export class QueueService {
|
|||||||
isSharedInbox,
|
isSharedInbox,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.deliverQueue.add(data, {
|
return this.deliverQueue.add(to, data, {
|
||||||
attempts: this.config.deliverJobMaxAttempts ?? 12,
|
attempts: this.config.deliverJobMaxAttempts ?? 12,
|
||||||
timeout: 1 * 60 * 1000, // 1min
|
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'apBackoff',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: true,
|
removeOnFail: true,
|
||||||
@@ -60,11 +95,10 @@ export class QueueService {
|
|||||||
signature,
|
signature,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.inboxQueue.add(data, {
|
return this.inboxQueue.add('', data, {
|
||||||
attempts: this.config.inboxJobMaxAttempts ?? 8,
|
attempts: this.config.inboxJobMaxAttempts ?? 8,
|
||||||
timeout: 5 * 60 * 1000, // 5min
|
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'apBackoff',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: true,
|
removeOnFail: true,
|
||||||
@@ -212,7 +246,7 @@ export class QueueService {
|
|||||||
private generateToDbJobData<T extends 'importFollowingToDb' | 'importBlockingToDb', D extends DbJobData<T>>(name: T, data: D): {
|
private generateToDbJobData<T extends 'importFollowingToDb' | 'importBlockingToDb', D extends DbJobData<T>>(name: T, data: D): {
|
||||||
name: string,
|
name: string,
|
||||||
data: D,
|
data: D,
|
||||||
opts: Bull.JobOptions,
|
opts: Bull.JobsOptions,
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -299,10 +333,10 @@ export class QueueService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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,
|
name: string,
|
||||||
data: RelationshipJobData,
|
data: RelationshipJobData,
|
||||||
opts: Bull.JobOptions,
|
opts: Bull.JobsOptions,
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -351,11 +385,10 @@ export class QueueService {
|
|||||||
eventId: uuid(),
|
eventId: uuid(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.webhookDeliverQueue.add(data, {
|
return this.webhookDeliverQueue.add(webhook.id, data, {
|
||||||
attempts: 4,
|
attempts: 4,
|
||||||
timeout: 1 * 60 * 1000, // 1min
|
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'apBackoff',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: true,
|
removeOnFail: true,
|
||||||
@@ -367,11 +400,11 @@ export class QueueService {
|
|||||||
this.deliverQueue.once('cleaned', (jobs, status) => {
|
this.deliverQueue.once('cleaned', (jobs, status) => {
|
||||||
//deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
//deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
||||||
});
|
});
|
||||||
this.deliverQueue.clean(0, 'delayed');
|
this.deliverQueue.clean(0, Infinity, 'delayed');
|
||||||
|
|
||||||
this.inboxQueue.once('cleaned', (jobs, status) => {
|
this.inboxQueue.once('cleaned', (jobs, status) => {
|
||||||
//inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
//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;
|
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 = '❤️';
|
reaction = '❤️';
|
||||||
} else if (_reaction) {
|
} else if (_reaction) {
|
||||||
const custom = reaction.match(isCustomEmojiRegexp);
|
const custom = reaction.match(isCustomEmojiRegexp);
|
||||||
@@ -124,6 +124,11 @@ export class ReactionService {
|
|||||||
if (emoji) {
|
if (emoji) {
|
||||||
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
|
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
|
||||||
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
||||||
|
|
||||||
|
// センシティブ
|
||||||
|
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
|
||||||
|
reaction = FALLBACK;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// リアクションとして使う権限がない
|
// リアクションとして使う権限がない
|
||||||
reaction = FALLBACK;
|
reaction = FALLBACK;
|
||||||
|
@@ -306,6 +306,14 @@ export class RoleService implements OnApplicationShutdown {
|
|||||||
return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
|
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
|
@bindThis
|
||||||
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
||||||
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
@@ -425,7 +433,12 @@ export class RoleService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
this.redisForSub.off('message', this.onMessage);
|
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
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
this.redisForSub.off('message', this.onMessage);
|
this.redisForSub.off('message', this.onMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -94,7 +94,7 @@ class LdSignature {
|
|||||||
@bindThis
|
@bindThis
|
||||||
private getLoader() {
|
private getLoader() {
|
||||||
return async (url: string): Promise<any> => {
|
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 (this.preLoad) {
|
||||||
if (url in CONTEXTS) {
|
if (url in CONTEXTS) {
|
||||||
@@ -126,7 +126,7 @@ class LdSignature {
|
|||||||
timeout: this.loderTimeout,
|
timeout: this.loderTimeout,
|
||||||
}, { throwErrorWhenResponseNotOk: false }).then(res => {
|
}, { throwErrorWhenResponseNotOk: false }).then(res => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw `${res.status} ${res.statusText}`;
|
throw new Error(`${res.status} ${res.statusText}`);
|
||||||
} else {
|
} else {
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ import { PollService } from '@/core/PollService.js';
|
|||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { checkHttps } from '@/misc/check-https.js';
|
||||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||||
import { ApLoggerService } from '../ApLoggerService.js';
|
import { ApLoggerService } from '../ApLoggerService.js';
|
||||||
@@ -32,7 +33,6 @@ import { ApQuestionService } from './ApQuestionService.js';
|
|||||||
import { ApImageService } from './ApImageService.js';
|
import { ApImageService } from './ApImageService.js';
|
||||||
import type { Resolver } from '../ApResolverService.js';
|
import type { Resolver } from '../ApResolverService.js';
|
||||||
import type { IObject, IPost } from '../type.js';
|
import type { IObject, IPost } from '../type.js';
|
||||||
import { checkHttps } from '@/misc/check-https.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApNoteService {
|
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);
|
quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x);
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
if (results.some(x => x.status === 'temperror')) {
|
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();
|
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);
|
const unlock = await this.appLockService.getApLock(uri);
|
||||||
|
|
||||||
|
@@ -60,7 +60,8 @@ export class ChartManagementService implements OnApplicationShutdown {
|
|||||||
}, 1000 * 60 * 20);
|
}, 1000 * 60 * 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onApplicationShutdown(signal: string): Promise<void> {
|
@bindThis
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
clearInterval(this.saveIntervalId);
|
clearInterval(this.saveIntervalId);
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
await Promise.all(
|
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(),
|
createdAt: userList.createdAt.toISOString(),
|
||||||
name: userList.name,
|
name: userList.name,
|
||||||
userIds: users.map(x => x.userId),
|
userIds: users.map(x => x.userId),
|
||||||
|
isPublic: userList.isPublic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,12 @@ export class JanitorService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
clearInterval(this.intervalId);
|
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 Xev from 'xev';
|
||||||
|
import * as Bull from 'bullmq';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { bindThis } from '@/decorators.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';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
@@ -13,6 +17,9 @@ export class QueueStatsService implements OnApplicationShutdown {
|
|||||||
private intervalId: NodeJS.Timer;
|
private intervalId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -31,11 +38,14 @@ export class QueueStatsService implements OnApplicationShutdown {
|
|||||||
let activeDeliverJobs = 0;
|
let activeDeliverJobs = 0;
|
||||||
let activeInboxJobs = 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++;
|
activeDeliverJobs++;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queueService.inboxQueue.on('global:active', () => {
|
inboxQueueEvents.on('active', () => {
|
||||||
activeInboxJobs++;
|
activeInboxJobs++;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,9 +81,14 @@ export class QueueStatsService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
this.intervalId = setInterval(tick, interval);
|
this.intervalId = setInterval(tick, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -63,9 +63,14 @@ export class ServerStatsService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPU STAT
|
// CPU STAT
|
||||||
|
@@ -25,6 +25,7 @@ export const DI = {
|
|||||||
userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
|
userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
|
||||||
userPublickeysRepository: Symbol('userPublickeysRepository'),
|
userPublickeysRepository: Symbol('userPublickeysRepository'),
|
||||||
userListsRepository: Symbol('userListsRepository'),
|
userListsRepository: Symbol('userListsRepository'),
|
||||||
|
userListFavoritesRepository: Symbol('userListFavoritesRepository'),
|
||||||
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
|
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
|
||||||
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
||||||
userIpsRepository: Symbol('userIpsRepository'),
|
userIpsRepository: Symbol('userIpsRepository'),
|
||||||
|
@@ -21,7 +21,7 @@ function getNoise(): string {
|
|||||||
|
|
||||||
export function genAid(date: Date): string {
|
export function genAid(date: Date): string {
|
||||||
const t = date.getTime();
|
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++;
|
counter++;
|
||||||
return getTime(t) + getNoise();
|
return getTime(t) + getNoise();
|
||||||
}
|
}
|
||||||
|
@@ -5,15 +5,16 @@ const dateTimeIntervals = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function dateUTC(time: number[]): Date {
|
export function dateUTC(time: number[]): Date {
|
||||||
const d = time.length === 2 ? Date.UTC(time[0], time[1])
|
const d =
|
||||||
: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
|
time.length === 2 ? Date.UTC(time[0], time[1])
|
||||||
: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
|
: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
|
||||||
: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
|
: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
|
||||||
: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
|
: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
|
||||||
: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
|
: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
|
||||||
: null;
|
: 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);
|
return new Date(d);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
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 { DataSource } from 'typeorm';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
@@ -112,6 +112,12 @@ const $userListsRepository: Provider = {
|
|||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $userListFavoritesRepository: Provider = {
|
||||||
|
provide: DI.userListFavoritesRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(UserListFavorite),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $userListJoiningsRepository: Provider = {
|
const $userListJoiningsRepository: Provider = {
|
||||||
provide: DI.userListJoiningsRepository,
|
provide: DI.userListJoiningsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(UserListJoining),
|
useFactory: (db: DataSource) => db.getRepository(UserListJoining),
|
||||||
@@ -416,6 +422,7 @@ const $userMemosRepository: Provider = {
|
|||||||
$userSecurityKeysRepository,
|
$userSecurityKeysRepository,
|
||||||
$userPublickeysRepository,
|
$userPublickeysRepository,
|
||||||
$userListsRepository,
|
$userListsRepository,
|
||||||
|
$userListFavoritesRepository,
|
||||||
$userListJoiningsRepository,
|
$userListJoiningsRepository,
|
||||||
$userNotePiningsRepository,
|
$userNotePiningsRepository,
|
||||||
$userIpsRepository,
|
$userIpsRepository,
|
||||||
@@ -483,6 +490,7 @@ const $userMemosRepository: Provider = {
|
|||||||
$userSecurityKeysRepository,
|
$userSecurityKeysRepository,
|
||||||
$userPublickeysRepository,
|
$userPublickeysRepository,
|
||||||
$userListsRepository,
|
$userListsRepository,
|
||||||
|
$userListFavoritesRepository,
|
||||||
$userListJoiningsRepository,
|
$userListJoiningsRepository,
|
||||||
$userNotePiningsRepository,
|
$userNotePiningsRepository,
|
||||||
$userIpsRepository,
|
$userIpsRepository,
|
||||||
|
@@ -90,7 +90,7 @@ export class Note {
|
|||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 64, nullable: true,
|
length: 64, nullable: true,
|
||||||
})
|
})
|
||||||
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
|
public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
|
||||||
|
|
||||||
@Column('smallint', {
|
@Column('smallint', {
|
||||||
default: 0,
|
default: 0,
|
||||||
|
@@ -19,6 +19,12 @@ export class UserList {
|
|||||||
})
|
})
|
||||||
public userId: User['id'];
|
public userId: User['id'];
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isPublic: boolean;
|
||||||
|
|
||||||
@ManyToOne(type => User, {
|
@ManyToOne(type => User, {
|
||||||
onDelete: 'CASCADE',
|
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 { UserIp } from '@/models/entities/UserIp.js';
|
||||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||||
import { UserList } from '@/models/entities/UserList.js';
|
import { UserList } from '@/models/entities/UserList.js';
|
||||||
|
import { UserListFavorite } from './entities/UserListFavorite.js';
|
||||||
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
||||||
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
||||||
import { UserPending } from '@/models/entities/UserPending.js';
|
import { UserPending } from '@/models/entities/UserPending.js';
|
||||||
@@ -117,6 +118,7 @@ export {
|
|||||||
UserIp,
|
UserIp,
|
||||||
UserKeypair,
|
UserKeypair,
|
||||||
UserList,
|
UserList,
|
||||||
|
UserListFavorite,
|
||||||
UserListJoining,
|
UserListJoining,
|
||||||
UserNotePining,
|
UserNotePining,
|
||||||
UserPending,
|
UserPending,
|
||||||
@@ -184,6 +186,7 @@ export type UsersRepository = Repository<User>;
|
|||||||
export type UserIpsRepository = Repository<UserIp>;
|
export type UserIpsRepository = Repository<UserIp>;
|
||||||
export type UserKeypairsRepository = Repository<UserKeypair>;
|
export type UserKeypairsRepository = Repository<UserKeypair>;
|
||||||
export type UserListsRepository = Repository<UserList>;
|
export type UserListsRepository = Repository<UserList>;
|
||||||
|
export type UserListFavoritesRepository = Repository<UserListFavorite>;
|
||||||
export type UserListJoiningsRepository = Repository<UserListJoining>;
|
export type UserListJoiningsRepository = Repository<UserListJoining>;
|
||||||
export type UserNotePiningsRepository = Repository<UserNotePining>;
|
export type UserNotePiningsRepository = Repository<UserNotePining>;
|
||||||
export type UserPendingsRepository = Repository<UserPending>;
|
export type UserPendingsRepository = Repository<UserPending>;
|
||||||
|
@@ -25,5 +25,10 @@ export const packedUserListSchema = {
|
|||||||
format: 'id',
|
format: 'id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
isPublic: {
|
||||||
|
type: 'boolean',
|
||||||
|
nullable: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@@ -57,6 +57,7 @@ import { User } from '@/models/entities/User.js';
|
|||||||
import { UserIp } from '@/models/entities/UserIp.js';
|
import { UserIp } from '@/models/entities/UserIp.js';
|
||||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||||
import { UserList } from '@/models/entities/UserList.js';
|
import { UserList } from '@/models/entities/UserList.js';
|
||||||
|
import { UserListFavorite } from '@/models/entities/UserListFavorite.js';
|
||||||
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
import { UserListJoining } from '@/models/entities/UserListJoining.js';
|
||||||
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
import { UserNotePining } from '@/models/entities/UserNotePining.js';
|
||||||
import { UserPending } from '@/models/entities/UserPending.js';
|
import { UserPending } from '@/models/entities/UserPending.js';
|
||||||
@@ -132,6 +133,7 @@ export const entities = [
|
|||||||
UserKeypair,
|
UserKeypair,
|
||||||
UserPublickey,
|
UserPublickey,
|
||||||
UserList,
|
UserList,
|
||||||
|
UserListFavorite,
|
||||||
UserListJoining,
|
UserListJoining,
|
||||||
UserNotePining,
|
UserNotePining,
|
||||||
UserSecurityKey,
|
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 type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { getJobInfo } from './get-job-info.js';
|
|
||||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||||
@@ -35,17 +34,51 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
|
|||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.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()
|
@Injectable()
|
||||||
export class QueueProcessorService {
|
export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
private logger: Logger;
|
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(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private queueService: QueueService,
|
|
||||||
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
||||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||||
private deliverProcessorService: DeliverProcessorService,
|
private deliverProcessorService: DeliverProcessorService,
|
||||||
@@ -77,10 +110,7 @@ export class QueueProcessorService {
|
|||||||
private cleanProcessorService: CleanProcessorService,
|
private cleanProcessorService: CleanProcessorService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger;
|
this.logger = this.queueLoggerService.logger;
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start() {
|
|
||||||
function renderError(e: Error): any {
|
function renderError(e: Error): any {
|
||||||
if (e) { // 何故かeがundefinedで来ることがある
|
if (e) { // 何故かeがundefinedで来ることがある
|
||||||
return {
|
return {
|
||||||
@@ -97,146 +127,232 @@ export class QueueProcessorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemLogger = this.logger.createSubLogger('system');
|
//#region system
|
||||||
const deliverLogger = this.logger.createSubLogger('deliver');
|
this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
|
||||||
const webhookLogger = this.logger.createSubLogger('webhook');
|
switch (job.name) {
|
||||||
const inboxLogger = this.logger.createSubLogger('inbox');
|
case 'tickCharts': return this.tickChartsProcessorService.process();
|
||||||
const dbLogger = this.logger.createSubLogger('db');
|
case 'resyncCharts': return this.resyncChartsProcessorService.process();
|
||||||
const relationshipLogger = this.logger.createSubLogger('relationship');
|
case 'cleanCharts': return this.cleanChartsProcessorService.process();
|
||||||
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
|
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
|
const systemLogger = this.logger.createSubLogger('system');
|
||||||
.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
|
|
||||||
|
this.systemQueueWorker
|
||||||
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
|
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
|
||||||
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) 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('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||||
.on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
.on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||||
.on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`));
|
.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
|
||||||
|
//#endregion
|
||||||
|
|
||||||
this.queueService.deliverQueue
|
//#region db
|
||||||
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
|
this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
|
||||||
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
switch (job.name) {
|
||||||
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
|
||||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
|
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
|
||||||
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
case 'exportNotes': return this.exportNotesProcessorService.process(job);
|
||||||
.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
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
|
const dbLogger = this.logger.createSubLogger('db');
|
||||||
.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'}`));
|
|
||||||
|
|
||||||
this.queueService.dbQueue
|
this.dbQueueWorker
|
||||||
.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))
|
|
||||||
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
|
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
|
||||||
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) 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('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||||
.on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
.on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||||
.on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`));
|
.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
|
||||||
|
//#endregion
|
||||||
|
|
||||||
this.queueService.relationshipQueue
|
//#region deliver
|
||||||
.on('waiting', (jobId) => relationshipLogger.debug(`waiting id=${jobId}`))
|
this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), {
|
||||||
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
...baseQueueOptions(this.config, QUEUE.DELIVER),
|
||||||
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
autorun: false,
|
||||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
|
concurrency: this.config.deliverJobConcurrency ?? 128,
|
||||||
.on('error', (job: any, err: Error) => relationshipLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
limiter: {
|
||||||
.on('stalled', (job) => relationshipLogger.warn(`stalled id=${job.id}`));
|
max: this.config.deliverJobPerSec ?? 128,
|
||||||
|
duration: 1000,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
backoffStrategy: httpRelatedBackoff,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.queueService.objectStorageQueue
|
const deliverLogger = this.logger.createSubLogger('deliver');
|
||||||
.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}`));
|
|
||||||
|
|
||||||
this.queueService.webhookDeliverQueue
|
this.deliverQueueWorker
|
||||||
.on('waiting', (jobId) => webhookLogger.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 ? 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('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('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('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||||
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
.on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
.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 * * * *' },
|
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
||||||
removeOnComplete: true,
|
autorun: false,
|
||||||
|
concurrency: this.config.relashionshipJobConcurrency ?? 16,
|
||||||
|
limiter: {
|
||||||
|
max: this.config.relashionshipJobPerSec ?? 64,
|
||||||
|
duration: 1000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queueService.systemQueue.add('resyncCharts', {
|
const relationshipLogger = this.logger.createSubLogger('relationship');
|
||||||
}, {
|
|
||||||
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));
|
|
||||||
|
|
||||||
{
|
this.relationshipQueueWorker
|
||||||
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
|
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
||||||
this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
|
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
||||||
this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
|
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||||
this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
|
.on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||||
this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
|
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
|
||||||
}
|
//#endregion
|
||||||
|
|
||||||
this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
|
//#region object storage
|
||||||
this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
|
this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
|
||||||
this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
|
switch (job.name) {
|
||||||
this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
|
case 'deleteFile': return this.deleteFileProcessorService.process(job);
|
||||||
this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
|
case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job);
|
||||||
this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
|
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 { IdService } from '@/core/IdService.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AggregateRetentionProcessorService {
|
export class AggregateRetentionProcessorService {
|
||||||
@@ -32,7 +32,7 @@ export class AggregateRetentionProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(): Promise<void> {
|
||||||
this.logger.info('Aggregating retention...');
|
this.logger.info('Aggregating retention...');
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -62,7 +62,6 @@ export class AggregateRetentionProcessorService {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isDuplicateKeyValueError(err)) {
|
if (isDuplicateKeyValueError(err)) {
|
||||||
this.logger.succ('Skip because it has already been processed by another worker.');
|
this.logger.succ('Skip because it has already been processed by another worker.');
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
@@ -88,6 +87,5 @@ export class AggregateRetentionProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ('Retention aggregated.');
|
this.logger.succ('Retention aggregated.');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CheckExpiredMutingsProcessorService {
|
export class CheckExpiredMutingsProcessorService {
|
||||||
@@ -27,7 +27,7 @@ export class CheckExpiredMutingsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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...');
|
this.logger.info('Checking expired mutings...');
|
||||||
|
|
||||||
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
||||||
@@ -41,6 +41,5 @@ export class CheckExpiredMutingsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ('All expired mutings checked.');
|
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 ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CleanChartsProcessorService {
|
export class CleanChartsProcessorService {
|
||||||
@@ -45,7 +45,7 @@ export class CleanChartsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(): Promise<void> {
|
||||||
this.logger.info('Clean charts...');
|
this.logger.info('Clean charts...');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -64,6 +64,5 @@ export class CleanChartsProcessorService {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
this.logger.succ('All charts successfully cleaned.');
|
this.logger.succ('All charts successfully cleaned.');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CleanProcessorService {
|
export class CleanProcessorService {
|
||||||
@@ -36,7 +36,7 @@ export class CleanProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(): Promise<void> {
|
||||||
this.logger.info('Cleaning...');
|
this.logger.info('Cleaning...');
|
||||||
|
|
||||||
this.userIpsRepository.delete({
|
this.userIpsRepository.delete({
|
||||||
@@ -72,6 +72,5 @@ export class CleanProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ('Cleaned.');
|
this.logger.succ('Cleaned.');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,9 @@ import type { DriveFilesRepository } from '@/models/index.js';
|
|||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CleanRemoteFilesProcessorService {
|
export class CleanRemoteFilesProcessorService {
|
||||||
@@ -27,7 +27,7 @@ export class CleanRemoteFilesProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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...');
|
this.logger.info('Deleting cached remote files...');
|
||||||
|
|
||||||
let deletedCount = 0;
|
let deletedCount = 0;
|
||||||
@@ -47,7 +47,7 @@ export class CleanRemoteFilesProcessorService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
job.progress(100);
|
job.updateProgress(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,10 +62,9 @@ export class CleanRemoteFilesProcessorService {
|
|||||||
isLink: false,
|
isLink: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
job.progress(deletedCount / total);
|
job.updateProgress(deletedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ('All cached remote files has been deleted.');
|
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 { DriveFile } from '@/models/entities/DriveFile.js';
|
||||||
import type { Note } from '@/models/entities/Note.js';
|
import type { Note } from '@/models/entities/Note.js';
|
||||||
import { EmailService } from '@/core/EmailService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbUserDeleteJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountProcessorService {
|
export class DeleteAccountProcessorService {
|
||||||
|
@@ -5,10 +5,10 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js';
|
|||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteDriveFilesProcessorService {
|
export class DeleteDriveFilesProcessorService {
|
||||||
@@ -31,12 +31,11 @@ export class DeleteDriveFilesProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ export class DeleteDriveFilesProcessorService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
job.progress(100);
|
job.updateProgress(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,10 +70,9 @@ export class DeleteDriveFilesProcessorService {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
job.progress(deletedCount / total);
|
job.updateProgress(deletedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
|
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 { Config } from '@/config.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { ObjectStorageFileJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteFileProcessorService {
|
export class DeleteFileProcessorService {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import * as Bull from 'bullmq';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js';
|
import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js';
|
||||||
import type { Config } from '@/config.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 { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
|
||||||
import type { DeliverJobData } from '../types.js';
|
import type { DeliverJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -121,15 +121,13 @@ export class DeliverProcessorService {
|
|||||||
isSuspended: true,
|
isSuspended: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return `${host} is gone`;
|
throw new Bull.UnrecoverableError(`${host} is gone`);
|
||||||
}
|
}
|
||||||
// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
|
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
|
||||||
// 何回再送しても成功することはないということなのでエラーにはしないでおく
|
|
||||||
return `${res.statusCode} ${res.statusMessage}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5xx etc.
|
// 5xx etc.
|
||||||
throw `${res.statusCode} ${res.statusMessage}`;
|
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
||||||
} else {
|
} else {
|
||||||
// DNS error, socket error, timeout ...
|
// DNS error, socket error, timeout ...
|
||||||
throw res;
|
throw res;
|
||||||
|
@@ -6,7 +6,7 @@ import type Logger from '@/logger.js';
|
|||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
import type { EndedPollNotificationJobData } from '../types.js';
|
import type { EndedPollNotificationJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -30,10 +30,9 @@ export class EndedPollNotificationProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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 });
|
const note = await this.notesRepository.findOneBy({ id: job.data.noteId });
|
||||||
if (note == null || !note.hasPoll) {
|
if (note == null || !note.hasPoll) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +50,5 @@ export class EndedPollNotificationProcessorService {
|
|||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import { createTemp } from '@/misc/create-temp.js';
|
|||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type { DBExportAntennasData } from '../types.js';
|
import type { DBExportAntennasData } from '../types.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportAntennasProcessorService {
|
export class ExportAntennasProcessorService {
|
||||||
@@ -39,10 +39,9 @@ export class ExportAntennasProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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 });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
@@ -96,7 +95,6 @@ export class ExportAntennasProcessorService {
|
|||||||
this.logger.succ('Exported to: ' + driveFile.id);
|
this.logger.succ('Exported to: ' + driveFile.id);
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,10 +9,10 @@ import type Logger from '@/logger.js';
|
|||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportBlockingProcessorService {
|
export class ExportBlockingProcessorService {
|
||||||
@@ -36,12 +36,11 @@ export class ExportBlockingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Exporting blocking of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ export class ExportBlockingProcessorService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (blockings.length === 0) {
|
if (blockings.length === 0) {
|
||||||
job.progress(100);
|
job.updateProgress(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ export class ExportBlockingProcessorService {
|
|||||||
blockerId: user.id,
|
blockerId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
job.progress(exportedCount / total);
|
job.updateProgress(exportedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
@@ -112,7 +111,5 @@ export class ExportBlockingProcessorService {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ import { createTemp, createTempDir } from '@/misc/create-temp.js';
|
|||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportCustomEmojisProcessorService {
|
export class ExportCustomEmojisProcessorService {
|
||||||
@@ -37,12 +37,11 @@ export class ExportCustomEmojisProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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 ...');
|
this.logger.info('Exporting custom emojis ...');
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,24 +116,26 @@ export class ExportCustomEmojisProcessorService {
|
|||||||
metaStream.end();
|
metaStream.end();
|
||||||
|
|
||||||
// Create archive
|
// Create archive
|
||||||
const [archivePath, archiveCleanup] = await createTemp();
|
await new Promise<void>(async (resolve) => {
|
||||||
const archiveStream = fs.createWriteStream(archivePath);
|
const [archivePath, archiveCleanup] = await createTemp();
|
||||||
const archive = archiver('zip', {
|
const archiveStream = fs.createWriteStream(archivePath);
|
||||||
zlib: { level: 0 },
|
const archive = archiver('zip', {
|
||||||
});
|
zlib: { level: 0 },
|
||||||
archiveStream.on('close', async () => {
|
});
|
||||||
this.logger.succ(`Exported to: ${archivePath}`);
|
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 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 driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
|
||||||
|
|
||||||
this.logger.succ(`Exported to: ${driveFile.id}`);
|
this.logger.succ(`Exported to: ${driveFile.id}`);
|
||||||
cleanup();
|
cleanup();
|
||||||
archiveCleanup();
|
archiveCleanup();
|
||||||
done();
|
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 type { Note } from '@/models/entities/Note.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbJobDataWithUser } from '../types.js';
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -42,12 +42,11 @@ export class ExportFavoritesProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Exporting favorites of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ export class ExportFavoritesProcessorService {
|
|||||||
}) as (NoteFavorite & { note: Note & { user: User } })[];
|
}) as (NoteFavorite & { note: Note & { user: User } })[];
|
||||||
|
|
||||||
if (favorites.length === 0) {
|
if (favorites.length === 0) {
|
||||||
job.progress(100);
|
job.updateProgress(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +111,7 @@ export class ExportFavoritesProcessorService {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
job.progress(exportedFavoritesCount / total);
|
job.updateProgress(exportedFavoritesCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
await write(']');
|
await write(']');
|
||||||
@@ -127,8 +126,6 @@ export class ExportFavoritesProcessorService {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,10 +10,10 @@ import { DriveService } from '@/core/DriveService.js';
|
|||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import type { Following } from '@/models/entities/Following.js';
|
import type { Following } from '@/models/entities/Following.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbExportFollowingData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportFollowingProcessorService {
|
export class ExportFollowingProcessorService {
|
||||||
@@ -40,12 +40,11 @@ export class ExportFollowingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Exporting following of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +115,5 @@ export class ExportFollowingProcessorService {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,10 +9,10 @@ import type Logger from '@/logger.js';
|
|||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportMutingProcessorService {
|
export class ExportMutingProcessorService {
|
||||||
@@ -39,12 +39,11 @@ export class ExportMutingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Exporting muting of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +72,7 @@ export class ExportMutingProcessorService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (mutes.length === 0) {
|
if (mutes.length === 0) {
|
||||||
job.progress(100);
|
job.updateProgress(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ export class ExportMutingProcessorService {
|
|||||||
muterId: user.id,
|
muterId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
job.progress(exportedCount / total);
|
job.updateProgress(exportedCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.end();
|
stream.end();
|
||||||
@@ -116,7 +115,5 @@ export class ExportMutingProcessorService {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js';
|
|||||||
import type { Note } from '@/models/entities/Note.js';
|
import type { Note } from '@/models/entities/Note.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbJobDataWithUser } from '../types.js';
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -39,12 +39,11 @@ export class ExportNotesProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Exporting notes of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +86,7 @@ export class ExportNotesProcessorService {
|
|||||||
}) as Note[];
|
}) as Note[];
|
||||||
|
|
||||||
if (notes.length === 0) {
|
if (notes.length === 0) {
|
||||||
job.progress(100);
|
job.updateProgress(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +107,7 @@ export class ExportNotesProcessorService {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
job.progress(exportedNotesCount / total);
|
job.updateProgress(exportedNotesCount / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
await write(']');
|
await write(']');
|
||||||
@@ -123,8 +122,6 @@ export class ExportNotesProcessorService {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,10 +9,10 @@ import type Logger from '@/logger.js';
|
|||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { createTemp } from '@/misc/create-temp.js';
|
import { createTemp } from '@/misc/create-temp.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportUserListsProcessorService {
|
export class ExportUserListsProcessorService {
|
||||||
@@ -39,12 +39,11 @@ export class ExportUserListsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Exporting user lists of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +91,5 @@ export class ExportUserListsProcessorService {
|
|||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import { DBAntennaImportJobData } from '../types.js';
|
import { DBAntennaImportJobData } from '../types.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
const validate = new Ajv().compile({
|
const validate = new Ajv().compile({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -59,7 +59,7 @@ export class ImportAntennasProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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();
|
const now = new Date();
|
||||||
try {
|
try {
|
||||||
for (const antenna of job.data.antenna) {
|
for (const antenna of job.data.antenna) {
|
||||||
@@ -89,8 +89,6 @@ export class ImportAntennasProcessorService {
|
|||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
} finally {
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js';
|
|||||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
import { QueueService } from '@/core/QueueService.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()
|
@Injectable()
|
||||||
export class ImportBlockingProcessorService {
|
export class ImportBlockingProcessorService {
|
||||||
@@ -34,12 +34,11 @@ export class ImportBlockingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Importing blocking of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +46,6 @@ export class ImportBlockingProcessorService {
|
|||||||
id: job.data.fileId,
|
id: job.data.fileId,
|
||||||
});
|
});
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +54,6 @@ export class ImportBlockingProcessorService {
|
|||||||
this.queueService.createImportBlockingToDbJob({ id: user.id }, targets);
|
this.queueService.createImportBlockingToDbJob({ id: user.id }, targets);
|
||||||
|
|
||||||
this.logger.succ('Import jobs created');
|
this.logger.succ('Import jobs created');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@@ -85,7 +82,7 @@ export class ImportBlockingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
throw `Unable to resolve user: @${username}@${host}`;
|
throw new Error(`Unable to resolve user: @${username}@${host}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip myself
|
// skip myself
|
||||||
|
@@ -12,7 +12,7 @@ import { DriveService } from '@/core/DriveService.js';
|
|||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbUserImportJobData } from '../types.js';
|
import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
// TODO: 名前衝突時の動作を選べるようにする
|
// TODO: 名前衝突時の動作を選べるようにする
|
||||||
@@ -45,14 +45,13 @@ export class ImportCustomEmojisProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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 ...');
|
this.logger.info('Importing custom emojis ...');
|
||||||
|
|
||||||
const file = await this.driveFilesRepository.findOneBy({
|
const file = await this.driveFilesRepository.findOneBy({
|
||||||
id: job.data.fileId,
|
id: job.data.fileId,
|
||||||
});
|
});
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +115,6 @@ export class ImportCustomEmojisProcessorService {
|
|||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
this.logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
unzipStream.pipe(extractor);
|
unzipStream.pipe(extractor);
|
||||||
this.logger.succ(`Unzipping to ${outputPath}`);
|
this.logger.succ(`Unzipping to ${outputPath}`);
|
||||||
|
@@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js';
|
|||||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
import { QueueService } from '@/core/QueueService.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()
|
@Injectable()
|
||||||
export class ImportFollowingProcessorService {
|
export class ImportFollowingProcessorService {
|
||||||
@@ -34,12 +34,11 @@ export class ImportFollowingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Importing following of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +46,6 @@ export class ImportFollowingProcessorService {
|
|||||||
id: job.data.fileId,
|
id: job.data.fileId,
|
||||||
});
|
});
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +54,6 @@ export class ImportFollowingProcessorService {
|
|||||||
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets);
|
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets);
|
||||||
|
|
||||||
this.logger.succ('Import jobs created');
|
this.logger.succ('Import jobs created');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@@ -85,7 +82,7 @@ export class ImportFollowingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
throw `Unable to resolve user: @${username}@${host}`;
|
throw new Error(`Unable to resolve user: @${username}@${host}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip myself
|
// skip myself
|
||||||
|
@@ -9,10 +9,10 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
|||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.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 { bindThis } from '@/decorators.js';
|
||||||
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
|
import type * as Bull from 'bullmq';
|
||||||
|
import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportMutingProcessorService {
|
export class ImportMutingProcessorService {
|
||||||
@@ -38,12 +38,11 @@ export class ImportMutingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Importing muting of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +50,6 @@ export class ImportMutingProcessorService {
|
|||||||
id: job.data.fileId,
|
id: job.data.fileId,
|
||||||
});
|
});
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +81,7 @@ export class ImportMutingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
throw `cannot resolve user: @${username}@${host}`;
|
throw new Error(`cannot resolve user: @${username}@${host}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip myself
|
// skip myself
|
||||||
@@ -98,6 +96,5 @@ export class ImportMutingProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import { IdService } from '@/core/IdService.js';
|
|||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbUserImportJobData } from '../types.js';
|
import type { DbUserImportJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -46,12 +46,11 @@ export class ImportUserListsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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} ...`);
|
this.logger.info(`Importing user lists of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +58,6 @@ export class ImportUserListsProcessorService {
|
|||||||
id: job.data.fileId,
|
id: job.data.fileId,
|
||||||
});
|
});
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
done();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +107,5 @@ export class ImportUserListsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.succ('Imported');
|
this.logger.succ('Imported');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import httpSignature from '@peertube/http-signature';
|
import httpSignature from '@peertube/http-signature';
|
||||||
|
import * as Bull from 'bullmq';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { InstancesRepository, DriveFilesRepository } from '@/models/index.js';
|
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { MetaService } from '@/core/MetaService.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 { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
|
||||||
import type { InboxJobData } from '../types.js';
|
import type { InboxJobData } from '../types.js';
|
||||||
|
|
||||||
// ユーザーのinboxにアクティビティが届いた時の処理
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InboxProcessorService {
|
export class InboxProcessorService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@@ -35,12 +33,6 @@ export class InboxProcessorService {
|
|||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
@Inject(DI.instancesRepository)
|
|
||||||
private instancesRepository: InstancesRepository,
|
|
||||||
|
|
||||||
@Inject(DI.driveFilesRepository)
|
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private apInboxService: ApInboxService,
|
private apInboxService: ApInboxService,
|
||||||
@@ -93,24 +85,24 @@ export class InboxProcessorService {
|
|||||||
try {
|
try {
|
||||||
authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor));
|
authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 対象が4xxならスキップ
|
// 対象が4xxならスキップ
|
||||||
if (err instanceof StatusError) {
|
if (err instanceof StatusError) {
|
||||||
if (err.isClientError) {
|
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) {
|
if (authUser == null) {
|
||||||
return 'skip: failed to resolve user';
|
throw new Bull.UnrecoverableError('skip: failed to resolve user');
|
||||||
}
|
}
|
||||||
|
|
||||||
// publicKey がなくても終了
|
// publicKey がなくても終了
|
||||||
if (authUser.key == null) {
|
if (authUser.key == null) {
|
||||||
return 'skip: failed to resolve user publicKey';
|
throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
@@ -118,10 +110,10 @@ export class InboxProcessorService {
|
|||||||
|
|
||||||
// また、signatureのsignerは、activity.actorと一致する必要がある
|
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||||
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||||
if (activity.signature) {
|
if (activity.signature) {
|
||||||
if (activity.signature.type !== 'RsaSignature2017') {
|
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
|
// activity.signature.creator: https://example.oom/users/user#main-key
|
||||||
@@ -134,32 +126,32 @@ export class InboxProcessorService {
|
|||||||
// keyIdからLD-Signatureのユーザーを取得
|
// keyIdからLD-Signatureのユーザーを取得
|
||||||
authUser = await this.apDbResolverService.getAuthUserFromKeyId(activity.signature.creator);
|
authUser = await this.apDbResolverService.getAuthUserFromKeyId(activity.signature.creator);
|
||||||
if (authUser == null) {
|
if (authUser == null) {
|
||||||
return 'skip: LD-Signatureのユーザーが取得できませんでした';
|
throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authUser.key == null) {
|
if (authUser.key == null) {
|
||||||
return 'skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした';
|
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
|
||||||
}
|
}
|
||||||
|
|
||||||
// LD-Signature検証
|
// LD-Signature検証
|
||||||
const ldSignature = this.ldSignatureService.use();
|
const ldSignature = this.ldSignatureService.use();
|
||||||
const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
|
const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
return 'skip: LD-Signatureの検証に失敗しました';
|
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
|
||||||
}
|
}
|
||||||
|
|
||||||
// もう一度actorチェック
|
// もう一度actorチェック
|
||||||
if (authUser.user.uri !== activity.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);
|
const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
|
||||||
if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) {
|
if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) {
|
||||||
return `Blocked request: ${ldHost}`;
|
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 signerHost = this.utilityService.extractDbHost(authUser.user.uri!);
|
||||||
const activityIdHost = this.utilityService.extractDbHost(activity.id);
|
const activityIdHost = this.utilityService.extractDbHost(activity.id);
|
||||||
if (signerHost !== activityIdHost) {
|
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 { Inject, Injectable } from '@nestjs/common';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||||
import { UserBlockingService } from '@/core/UserBlockingService.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 ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResyncChartsProcessorService {
|
export class ResyncChartsProcessorService {
|
||||||
@@ -43,7 +43,7 @@ export class ResyncChartsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(): Promise<void> {
|
||||||
this.logger.info('Resync charts...');
|
this.logger.info('Resync charts...');
|
||||||
|
|
||||||
// TODO: ユーザーごとのチャートも更新する
|
// TODO: ユーザーごとのチャートも更新する
|
||||||
@@ -55,6 +55,5 @@ export class ResyncChartsProcessorService {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
this.logger.succ('All charts successfully resynced.');
|
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 ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TickChartsProcessorService {
|
export class TickChartsProcessorService {
|
||||||
@@ -45,7 +45,7 @@ export class TickChartsProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
|
public async process(): Promise<void> {
|
||||||
this.logger.info('Tick charts...');
|
this.logger.info('Tick charts...');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -64,6 +64,5 @@ export class TickChartsProcessorService {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
this.logger.succ('All charts successfully ticked.');
|
this.logger.succ('All charts successfully ticked.');
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import * as Bull from 'bullmq';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { WebhooksRepository } from '@/models/index.js';
|
import type { WebhooksRepository } from '@/models/index.js';
|
||||||
import type { Config } from '@/config.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 { StatusError } from '@/misc/status-error.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type Bull from 'bull';
|
|
||||||
import type { WebhookDeliverJobData } from '../types.js';
|
import type { WebhookDeliverJobData } from '../types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -66,11 +66,11 @@ export class WebhookDeliverProcessorService {
|
|||||||
if (res instanceof StatusError) {
|
if (res instanceof StatusError) {
|
||||||
// 4xx
|
// 4xx
|
||||||
if (res.isClientError) {
|
if (res.isClientError) {
|
||||||
return `${res.statusCode} ${res.statusMessage}`;
|
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5xx etc.
|
// 5xx etc.
|
||||||
throw `${res.statusCode} ${res.statusMessage}`;
|
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
||||||
} else {
|
} else {
|
||||||
// DNS error, socket error, timeout ...
|
// DNS error, socket error, timeout ...
|
||||||
throw res;
|
throw res;
|
||||||
|
@@ -194,7 +194,7 @@ export class ServerService implements OnApplicationShutdown {
|
|||||||
|
|
||||||
fastify.register(this.clientServerService.createServer);
|
fastify.register(this.clientServerService.createServer);
|
||||||
|
|
||||||
this.streamingApiServerService.attachStreamingApi(fastify.server);
|
this.streamingApiServerService.attach(fastify.server);
|
||||||
|
|
||||||
fastify.server.on('error', err => {
|
fastify.server.on('error', err => {
|
||||||
switch ((err as any).code) {
|
switch ((err as any).code) {
|
||||||
@@ -222,7 +222,14 @@ export class ServerService implements OnApplicationShutdown {
|
|||||||
await fastify.ready();
|
await fastify.ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onApplicationShutdown(signal: string): Promise<void> {
|
@bindThis
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
await this.streamingApiServerService.detach();
|
||||||
await this.#fastify.close();
|
await this.#fastify.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
async onApplicationShutdown(signal: string): Promise<void> {
|
||||||
|
await this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -359,7 +359,12 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public onApplicationShutdown(signal?: string | undefined) {
|
public dispose(): void {
|
||||||
clearInterval(this.userIpHistoriesClearIntervalId);
|
clearInterval(this.userIpHistoriesClearIntervalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ export class AuthenticateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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) {
|
if (token == null) {
|
||||||
return [null, 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_push from './endpoints/users/lists/push.js';
|
||||||
import * as ep___users_lists_show from './endpoints/users/lists/show.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_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_notes from './endpoints/users/notes.js';
|
||||||
import * as ep___users_pages from './endpoints/users/pages.js';
|
import * as ep___users_pages from './endpoints/users/pages.js';
|
||||||
import * as ep___users_reactions from './endpoints/users/reactions.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_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_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_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_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_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
|
||||||
const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.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_push,
|
||||||
$users_lists_show,
|
$users_lists_show,
|
||||||
$users_lists_update,
|
$users_lists_update,
|
||||||
|
$users_lists_favorite,
|
||||||
|
$users_lists_unfavorite,
|
||||||
|
$users_lists_create_from_public,
|
||||||
$users_notes,
|
$users_notes,
|
||||||
$users_pages,
|
$users_pages,
|
||||||
$users_reactions,
|
$users_reactions,
|
||||||
@@ -1335,6 +1344,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||||||
$users_lists_push,
|
$users_lists_push,
|
||||||
$users_lists_show,
|
$users_lists_show,
|
||||||
$users_lists_update,
|
$users_lists_update,
|
||||||
|
$users_lists_favorite,
|
||||||
|
$users_lists_unfavorite,
|
||||||
|
$users_lists_create_from_public,
|
||||||
$users_notes,
|
$users_notes,
|
||||||
$users_pages,
|
$users_pages,
|
||||||
$users_reactions,
|
$users_reactions,
|
||||||
|
@@ -1,23 +1,25 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import * as websocket from 'websocket';
|
import * as WebSocket from 'ws';
|
||||||
import { DI } from '@/di-symbols.js';
|
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 type { Config } from '@/config.js';
|
||||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { NotificationService } from '@/core/NotificationService.js';
|
import { NotificationService } from '@/core/NotificationService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CacheService } from '@/core/CacheService.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 MainStreamConnection from './stream/index.js';
|
||||||
import { ChannelsService } from './stream/ChannelsService.js';
|
import { ChannelsService } from './stream/ChannelsService.js';
|
||||||
import type { ParsedUrlQuery } from 'querystring';
|
|
||||||
import type * as http from 'node:http';
|
import type * as http from 'node:http';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StreamingApiServerService {
|
export class StreamingApiServerService {
|
||||||
|
#wss: WebSocket.WebSocketServer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
@@ -28,24 +30,6 @@ export class StreamingApiServerService {
|
|||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: 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 cacheService: CacheService,
|
||||||
private noteReadService: NoteReadService,
|
private noteReadService: NoteReadService,
|
||||||
private authenticateService: AuthenticateService,
|
private authenticateService: AuthenticateService,
|
||||||
@@ -55,25 +39,65 @@ export class StreamingApiServerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public attachStreamingApi(server: http.Server) {
|
public attach(server: http.Server): void {
|
||||||
// Init websocket server
|
this.#wss = new WebSocket.WebSocketServer({
|
||||||
const ws = new websocket.server({
|
noServer: true,
|
||||||
httpServer: server,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('request', async (request) => {
|
server.on('upgrade', async (request, socket, head) => {
|
||||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
if (request.url == null) {
|
||||||
|
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
|
||||||
// TODO: トークンが間違ってるなどしてauthenticateに失敗したら
|
socket.destroy();
|
||||||
// コネクション切断するなりエラーメッセージ返すなりする
|
|
||||||
// (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので)
|
|
||||||
const [user, miapp] = await this.authenticateService.authenticate(q.i as string);
|
|
||||||
|
|
||||||
if (user?.isSuspended) {
|
|
||||||
request.reject(400);
|
|
||||||
return;
|
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();
|
const ev = new EventEmitter();
|
||||||
|
|
||||||
async function onRedisMessage(_: string, data: string): Promise<void> {
|
async function onRedisMessage(_: string, data: string): Promise<void> {
|
||||||
@@ -83,19 +107,7 @@ export class StreamingApiServerService {
|
|||||||
|
|
||||||
this.redisForSub.on('message', onRedisMessage);
|
this.redisForSub.on('message', onRedisMessage);
|
||||||
|
|
||||||
const main = new MainStreamConnection(
|
await stream.listen(ev, connection);
|
||||||
this.channelsService,
|
|
||||||
this.noteReadService,
|
|
||||||
this.notificationService,
|
|
||||||
this.cacheService,
|
|
||||||
ev, user, miapp,
|
|
||||||
);
|
|
||||||
|
|
||||||
await main.init();
|
|
||||||
|
|
||||||
const connection = request.accept();
|
|
||||||
|
|
||||||
main.init2(connection);
|
|
||||||
|
|
||||||
const intervalId = user ? setInterval(() => {
|
const intervalId = user ? setInterval(() => {
|
||||||
this.usersRepository.update(user.id, {
|
this.usersRepository.update(user.id, {
|
||||||
@@ -110,16 +122,23 @@ export class StreamingApiServerService {
|
|||||||
|
|
||||||
connection.once('close', () => {
|
connection.once('close', () => {
|
||||||
ev.removeAllListeners();
|
ev.removeAllListeners();
|
||||||
main.dispose();
|
stream.dispose();
|
||||||
this.redisForSub.off('message', onRedisMessage);
|
this.redisForSub.off('message', onRedisMessage);
|
||||||
if (intervalId) clearInterval(intervalId);
|
if (intervalId) clearInterval(intervalId);
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('message', async (data) => {
|
connection.on('message', async (data) => {
|
||||||
if (data.type === 'utf8' && data.utf8Data === 'ping') {
|
if (data.toString() === 'ping') {
|
||||||
connection.send('pong');
|
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_pull from './endpoints/users/lists/pull.js';
|
||||||
import * as ep___users_lists_push from './endpoints/users/lists/push.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_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_lists_update from './endpoints/users/lists/update.js';
|
||||||
import * as ep___users_notes from './endpoints/users/notes.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_pages from './endpoints/users/pages.js';
|
||||||
@@ -656,7 +659,10 @@ const eps = [
|
|||||||
['users/lists/pull', ep___users_lists_pull],
|
['users/lists/pull', ep___users_lists_pull],
|
||||||
['users/lists/push', ep___users_lists_push],
|
['users/lists/push', ep___users_lists_push],
|
||||||
['users/lists/show', ep___users_lists_show],
|
['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/update', ep___users_lists_update],
|
||||||
|
['users/lists/create-from-public', ep___users_lists_create_from_public],
|
||||||
['users/notes', ep___users_notes],
|
['users/notes', ep___users_notes],
|
||||||
['users/pages', ep___users_pages],
|
['users/pages', ep___users_pages],
|
||||||
['users/reactions', ep___users_reactions],
|
['users/reactions', ep___users_reactions],
|
||||||
|
@@ -25,7 +25,7 @@ export const paramDef = {
|
|||||||
id: { type: 'string', format: 'misskey:id' },
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
title: { type: 'string', minLength: 1 },
|
title: { type: 'string', minLength: 1 },
|
||||||
text: { 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'],
|
required: ['id', 'title', 'text', 'imageUrl'],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -46,7 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
title: ps.title,
|
title: ps.title,
|
||||||
text: ps.text,
|
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) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
try {
|
try {
|
||||||
if (new URL(ps.inbox).protocol !== 'https:') throw 'https only';
|
if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only');
|
||||||
} catch {
|
} catch {
|
||||||
throw new ApiError(meta.errors.invalidUrl);
|
throw new ApiError(meta.errors.invalidUrl);
|
||||||
}
|
}
|
||||||
|
@@ -99,7 +99,7 @@ export const paramDef = {
|
|||||||
} },
|
} },
|
||||||
cw: { type: 'string', nullable: true, maxLength: 100 },
|
cw: { type: 'string', nullable: true, maxLength: 100 },
|
||||||
localOnly: { type: 'boolean', default: false },
|
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 },
|
noExtractMentions: { type: 'boolean', default: false },
|
||||||
noExtractHashtags: { type: 'boolean', default: false },
|
noExtractHashtags: { type: 'boolean', default: false },
|
||||||
noExtractEmojis: { type: 'boolean', default: false },
|
noExtractEmojis: { type: 'boolean', default: false },
|
||||||
|
@@ -82,14 +82,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (ps.tag) {
|
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`);
|
query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
|
||||||
} else {
|
} else {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
for (const tags of ps.query!) {
|
for (const tags of ps.query!) {
|
||||||
qb.orWhere(new Brackets(qb => {
|
qb.orWhere(new Brackets(qb => {
|
||||||
for (const tag of tags) {
|
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`);
|
qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
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 redisClient.flushdb();
|
||||||
await resetDb(this.db);
|
await resetDb(this.db);
|
||||||
|
@@ -93,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
|
|
||||||
const query = this.notesRepository.createQueryBuilder('note')
|
const query = this.notesRepository.createQueryBuilder('note')
|
||||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||||
|
.andWhere('(note.visibility = \'public\')')
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
.leftJoinAndSelect('note.renote', 'renote')
|
.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 { 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 { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['lists', 'account'],
|
tags: ['lists', 'account'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: false,
|
||||||
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
|
||||||
@@ -22,26 +23,58 @@ export const meta = {
|
|||||||
ref: 'UserList',
|
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;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {},
|
properties: {
|
||||||
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@Inject(DI.userListsRepository)
|
@Inject(DI.userListsRepository)
|
||||||
private userListsRepository: UserListsRepository,
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
private userListEntityService: UserListEntityService,
|
private userListEntityService: UserListEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
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: me.id,
|
||||||
|
} : {
|
||||||
|
userId: ps.userId,
|
||||||
|
isPublic: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
|
return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
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 { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
@@ -8,7 +8,7 @@ import { ApiError } from '../../../error.js';
|
|||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['lists', 'account'],
|
tags: ['lists', 'account'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: false,
|
||||||
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
|
||||||
@@ -33,31 +33,54 @@ export const paramDef = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
listId: { type: 'string', format: 'misskey:id' },
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
|
forPublic: { type: 'boolean', default: false },
|
||||||
},
|
},
|
||||||
required: ['listId'],
|
required: ['listId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
@Injectable() // eslint-disable-next-line import/no-default-export
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.userListsRepository)
|
@Inject(DI.userListsRepository)
|
||||||
private userListsRepository: UserListsRepository,
|
private userListsRepository: UserListsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userListFavoritesRepository)
|
||||||
|
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||||
|
|
||||||
private userListEntityService: UserListEntityService,
|
private userListEntityService: UserListEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const additionalProperties: Partial<{ likedCount: number, isLiked: boolean }> = {};
|
||||||
// Fetch the list
|
// Fetch the list
|
||||||
const userList = await this.userListsRepository.findOneBy({
|
const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
} : {
|
||||||
|
id: ps.listId,
|
||||||
|
isPublic: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userList == null) {
|
if (userList == null) {
|
||||||
throw new ApiError(meta.errors.noSuchList);
|
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: {
|
properties: {
|
||||||
listId: { type: 'string', format: 'misskey:id' },
|
listId: { type: 'string', format: 'misskey:id' },
|
||||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||||
|
isPublic: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
required: ['listId', 'name'],
|
required: ['listId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
@@ -48,7 +49,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
private userListEntityService: UserListEntityService,
|
private userListEntityService: UserListEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
// Fetch the list
|
|
||||||
const userList = await this.userListsRepository.findOneBy({
|
const userList = await this.userListsRepository.findOneBy({
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
@@ -60,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
|
|
||||||
await this.userListsRepository.update(userList.id, {
|
await this.userListsRepository.update(userList.id, {
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
|
isPublic: ps.isPublic,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await this.userListEntityService.pack(userList.id);
|
return await this.userListEntityService.pack(userList.id);
|
||||||
|
@@ -5,15 +5,17 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import Channel from '../channel.js';
|
import Channel from '../channel.js';
|
||||||
import { StreamMessages } from '../types.js';
|
import { StreamMessages } from '../types.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
|
||||||
class RoleTimelineChannel extends Channel {
|
class RoleTimelineChannel extends Channel {
|
||||||
public readonly chName = 'roleTimeline';
|
public readonly chName = 'roleTimeline';
|
||||||
public static shouldShare = false;
|
public static shouldShare = false;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
private roleId: string;
|
private roleId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
private roleservice: RoleService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
connection: Channel['connection'],
|
connection: Channel['connection'],
|
||||||
@@ -34,6 +36,11 @@ class RoleTimelineChannel extends Channel {
|
|||||||
if (data.type === 'note') {
|
if (data.type === 'note') {
|
||||||
const note = data.body;
|
const note = data.body;
|
||||||
|
|
||||||
|
if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (note.visibility !== 'public') return;
|
||||||
|
|
||||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
||||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||||
@@ -61,6 +68,7 @@ export class RoleTimelineChannelService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
private roleservice: RoleService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +76,7 @@ export class RoleTimelineChannelService {
|
|||||||
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
||||||
return new RoleTimelineChannel(
|
return new RoleTimelineChannel(
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
|
this.roleservice,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
);
|
);
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import * as WebSocket from 'ws';
|
||||||
import type { User } from '@/models/entities/User.js';
|
import type { User } from '@/models/entities/User.js';
|
||||||
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
||||||
import type { Packed } from '@/misc/json-schema.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 { CacheService } from '@/core/CacheService.js';
|
||||||
import { UserProfile } from '@/models/index.js';
|
import { UserProfile } from '@/models/index.js';
|
||||||
import type { ChannelsService } from './ChannelsService.js';
|
import type { ChannelsService } from './ChannelsService.js';
|
||||||
import type * as websocket from 'websocket';
|
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
import type Channel from './channel.js';
|
import type Channel from './channel.js';
|
||||||
import type { StreamEventEmitter, StreamMessages } from './types.js';
|
import type { StreamEventEmitter, StreamMessages } from './types.js';
|
||||||
@@ -18,7 +18,7 @@ import type { StreamEventEmitter, StreamMessages } from './types.js';
|
|||||||
export default class Connection {
|
export default class Connection {
|
||||||
public user?: User;
|
public user?: User;
|
||||||
public token?: AccessToken;
|
public token?: AccessToken;
|
||||||
private wsConnection: websocket.connection;
|
private wsConnection: WebSocket.WebSocket;
|
||||||
public subscriber: StreamEventEmitter;
|
public subscriber: StreamEventEmitter;
|
||||||
private channels: Channel[] = [];
|
private channels: Channel[] = [];
|
||||||
private subscribingNotes: any = {};
|
private subscribingNotes: any = {};
|
||||||
@@ -37,11 +37,9 @@ export default class Connection {
|
|||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
|
|
||||||
subscriber: EventEmitter,
|
|
||||||
user: User | null | undefined,
|
user: User | null | undefined,
|
||||||
token: AccessToken | null | undefined,
|
token: AccessToken | null | undefined,
|
||||||
) {
|
) {
|
||||||
this.subscriber = subscriber;
|
|
||||||
if (user) this.user = user;
|
if (user) this.user = user;
|
||||||
if (token) this.token = token;
|
if (token) this.token = token;
|
||||||
}
|
}
|
||||||
@@ -70,12 +68,16 @@ export default class Connection {
|
|||||||
if (this.user != null) {
|
if (this.user != null) {
|
||||||
await this.fetch();
|
await this.fetch();
|
||||||
|
|
||||||
this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
|
if (!this.fetchIntervalId) {
|
||||||
|
this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init2(wsConnection: websocket.connection) {
|
public async listen(subscriber: EventEmitter, wsConnection: WebSocket.WebSocket) {
|
||||||
|
this.subscriber = subscriber;
|
||||||
|
|
||||||
this.wsConnection = wsConnection;
|
this.wsConnection = wsConnection;
|
||||||
this.wsConnection.on('message', this.onWsConnectionMessage);
|
this.wsConnection.on('message', this.onWsConnectionMessage);
|
||||||
|
|
||||||
@@ -88,14 +90,11 @@ export default class Connection {
|
|||||||
* クライアントからメッセージ受信時
|
* クライアントからメッセージ受信時
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async onWsConnectionMessage(data: websocket.Message) {
|
private async onWsConnectionMessage(data: WebSocket.RawData) {
|
||||||
if (data.type !== 'utf8') return;
|
|
||||||
if (data.utf8Data == null) return;
|
|
||||||
|
|
||||||
let obj: Record<string, any>;
|
let obj: Record<string, any>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
obj = JSON.parse(data.utf8Data);
|
obj = JSON.parse(data.toString());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -160,37 +160,41 @@
|
|||||||
<path d="M12 9v2m0 4v.01"></path>
|
<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>
|
<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>
|
</svg>
|
||||||
<h1>An error has occurred!</h1>
|
<h1>Failed to load<br>読み込みに失敗しました</h1>
|
||||||
<button class="button-big" onclick="location.reload();">
|
<button class="button-big" onclick="location.reload(true);">
|
||||||
<span class="button-label-big">Refresh</span>
|
<span class="button-label-big">Reload / リロード</span>
|
||||||
</button>
|
</button>
|
||||||
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
|
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります。</b></p>
|
||||||
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
|
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
|
||||||
<p>Update your os and browser.</p>
|
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
|
||||||
<p>Disable an adblocker.</p>
|
<p>Disable an adblocker / アドブロッカーを無効にする</p>
|
||||||
<a href="/flush">
|
<details style="color: #86b300;">
|
||||||
<button class="button-small">
|
<summary>Other options / その他のオプション</summary>
|
||||||
<span class="button-label-small">Clear preferences and cache</span>
|
<a href="/flush">
|
||||||
</button>
|
<button class="button-small">
|
||||||
</a>
|
<span class="button-label-small">Clear preferences and cache</span>
|
||||||
<br>
|
</button>
|
||||||
<a href="/cli">
|
</a>
|
||||||
<button class="button-small">
|
<br>
|
||||||
<span class="button-label-small">Start the simple client</span>
|
<a href="/cli">
|
||||||
</button>
|
<button class="button-small">
|
||||||
</a>
|
<span class="button-label-small">Start the simple client</span>
|
||||||
<br>
|
</button>
|
||||||
<a href="/bios">
|
</a>
|
||||||
<button class="button-small">
|
<br>
|
||||||
<span class="button-label-small">Start the repair tool</span>
|
<a href="/bios">
|
||||||
</button>
|
<button class="button-small">
|
||||||
</a>
|
<span class="button-label-small">Start the repair tool</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</details>
|
||||||
<br>
|
<br>
|
||||||
<div id="errors"></div>
|
<div id="errors"></div>
|
||||||
`;
|
`;
|
||||||
errorsElement = document.getElementById('errors');
|
errorsElement = document.getElementById('errors');
|
||||||
}
|
}
|
||||||
const detailsElement = document.createElement('details');
|
const detailsElement = document.createElement('details');
|
||||||
|
detailsElement.id = 'errorInfo';
|
||||||
detailsElement.innerHTML = `
|
detailsElement.innerHTML = `
|
||||||
<br>
|
<br>
|
||||||
<summary>
|
<summary>
|
||||||
@@ -247,7 +251,7 @@
|
|||||||
.button-label-big {
|
.button-label-big {
|
||||||
color: #222;
|
color: #222;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 1.2em;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,11 +271,6 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dont-worry,
|
|
||||||
#msg {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-warning {
|
.icon-warning {
|
||||||
color: #dec340;
|
color: #dec340;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
@@ -279,14 +278,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 32px;
|
font-size: 1.5em;
|
||||||
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: Fira, FiraCode, monospace;
|
font-family: Fira, FiraCode, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
details {
|
#errorInfo {
|
||||||
background: #333;
|
background: #333;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
@@ -296,16 +296,16 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary {
|
#errorInfo summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary > * {
|
#errorInfo summary > * {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
details {
|
#errorInfo {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
@@ -25,7 +25,6 @@ html
|
|||||||
meta(name='referrer' content='origin')
|
meta(name='referrer' content='origin')
|
||||||
meta(name='theme-color' content= themeColor || '#86b300')
|
meta(name='theme-color' content= themeColor || '#86b300')
|
||||||
meta(name='theme-color-orig' 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(property='og:site_name' content= instanceName || 'Misskey')
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||||
link(rel='icon' href= icon || '/favicon.ico')
|
link(rel='icon' href= icon || '/favicon.ico')
|
||||||
@@ -59,6 +58,7 @@ html
|
|||||||
meta(property='og:title' content= title || 'Misskey')
|
meta(property='og:title' content= title || 'Misskey')
|
||||||
meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
||||||
meta(property='og:image' content= img)
|
meta(property='og:image' content= img)
|
||||||
|
meta(property='twitter:card' content='summary')
|
||||||
|
|
||||||
style
|
style
|
||||||
include ../style.css
|
include ../style.css
|
||||||
|
@@ -16,3 +16,4 @@ block og
|
|||||||
meta(property='og:description' content= channel.description)
|
meta(property='og:description' content= channel.description)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:url' content= url)
|
||||||
meta(property='og:image' content= channel.bannerUrl)
|
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:description' content= clip.description)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:url' content= url)
|
||||||
meta(property='og:image' content= avatarUrl)
|
meta(property='og:image' content= avatarUrl)
|
||||||
|
meta(property='twitter:card' content='summary')
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
if profile.noCrawle
|
||||||
|
@@ -17,6 +17,7 @@ block og
|
|||||||
meta(property='og:description' content= flash.summary)
|
meta(property='og:description' content= flash.summary)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:url' content= url)
|
||||||
meta(property='og:image' content= avatarUrl)
|
meta(property='og:image' content= avatarUrl)
|
||||||
|
meta(property='twitter:card' content='summary')
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
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