Compare commits

..

4 Commits

Author SHA1 Message Date
Kagami Sascha Rosylight d8434a206f Update CHANGELOG.md 2023-12-30 17:22:30 +01:00
Kagami Sascha Rosylight 83159600ea use div for all url preview form 2023-12-30 16:45:35 +01:00
Kagami Sascha Rosylight 12b82aca5f remove margin from MkNoteSimple root 2023-12-30 16:31:34 +01:00
Kagami Sascha Rosylight 73842166ee feat(frontend/MkUrlPreview): support expanding ActivityPub notes 2023-12-30 15:32:59 +01:00
544 changed files with 3449 additions and 22932 deletions
-1
View File
@@ -2,4 +2,3 @@
POSTGRES_PASSWORD=example-misskey-pass
POSTGRES_USER=example-misskey-user
POSTGRES_DB=misskey
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
+7 -23
View File
@@ -17,32 +17,16 @@ updates:
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
# List dependencies required to be updated together, sharing the same version numbers.
# Those who simply have the common owner (e.g. @fastify) don't need to be listed.
# PNPM has an issue with dependabot. See:
# https://github.com/dependabot/dependabot-core/issues/7258
# https://github.com/pnpm/pnpm/issues/6530
# TODO: Restore this when the issue is solved
open-pull-requests-limit: 0
groups:
aws-sdk:
swc:
patterns:
- "@aws-sdk/*"
bull-board:
patterns:
- "@bull-board/*"
nestjs:
patterns:
- "@nestjs/*"
slacc:
patterns:
- "slacc-*"
- "@swc/*"
storybook:
patterns:
- "storybook*"
- "@storybook/*"
swc-core:
patterns:
- "@swc/core*"
typescript-eslint:
patterns:
- "@typescript-eslint/*"
tensorflow:
patterns:
- "@tensorflow/*"
+1 -7
View File
@@ -1,12 +1,6 @@
name: API report (misskey.js)
on:
push:
paths:
- packages/misskey-js/**
pull_request:
paths:
- packages/misskey-js/**
on: [push, pull_request]
jobs:
report:
-43
View File
@@ -1,43 +0,0 @@
name: Check the description in CHANGELOG.md
on:
pull_request:
branches:
- master
- develop
jobs:
check-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout head
uses: actions/checkout@v4.1.1
- name: Setup Node.js
uses: actions/setup-node@v4.0.1
with:
node-version-file: '.node-version'
- name: Checkout base
run: |
mkdir _base
cp -r .git _base/.git
cd _base
git fetch --depth 1 origin ${{ github.base_ref }}
git checkout origin/${{ github.base_ref }} CHANGELOG.md
- name: Copy to Checker directory for CHANGELOG-base.md
run: cp _base/CHANGELOG.md scripts/changelog-checker/CHANGELOG-base.md
- name: Copy to Checker directory for CHANGELOG-head.md
run: cp CHANGELOG.md scripts/changelog-checker/CHANGELOG-head.md
- name: diff
continue-on-error: true
run: diff -u CHANGELOG-base.md CHANGELOG-head.md
working-directory: scripts/changelog-checker
- name: Setup Checker
run: npm install
working-directory: scripts/changelog-checker
- name: Run Checker
run: npm run run
working-directory: scripts/changelog-checker
@@ -1,127 +0,0 @@
name: Check Misskey JS autogen
on:
pull_request:
branches:
- master
- develop
paths:
- packages/backend/**
jobs:
check-misskey-js-autogen:
runs-on: ubuntu-latest
permissions:
pull-requests: write
env:
api_json_names: "api-base.json api-head.json"
steps:
- name: checkout
uses: actions/checkout@v4
with:
submodules: true
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: setup node
id: setup-node
uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: pnpm
- name: install dependencies
run: pnpm i --frozen-lockfile
- name: wait get-api-diff
uses: lewagon/wait-on-check-action@v1.3.3
with:
ref: ${{ github.event.pull_request.head.sha }}
check-regexp: get-from-misskey .+
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 30
- name: Download artifact
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const workflows = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: `${{ github.event.pull_request.head.sha }}`
}).then(x => x.data.workflow_runs);
console.log(workflows.map(x => ({name: x.name, title: x.display_title})));
const run_id = workflows.find(x => x.name.includes("Get api.json from Misskey")).id;
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run_id,
});
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact"
});
await Promise.all(matchArtifacts.map(async (artifact) => {
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: unzip artifacts
run: |-
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d . ';'
ls -la
- name: build autogen
run: |-
for name in $(echo $api_json_names)
do
checksum=$(mktemp)
mv $name packages/misskey-js/generator/api.json
cd packages/misskey-js/generator
pnpm run generate
find built -type f -exec sh -c 'echo $(sed -E "s/^\s+\*\s+generatedAt:.+$//" {} | sha256sum | cut -d" " -f 1) {}' \; > $checksum
cd ../../..
cp $checksum ${name}_checksum
done
- name: check update for type definitions
run: diff $(echo -n ${api_json_names} | awk -v RS=" " '{ printf "%s_checksum ", $0 }')
- name: send message
if: failure()
uses: thollander/actions-comment-pull-request@v2
with:
comment_tag: check-misskey-js-autogen
message: |-
Thank you for sending us a great Pull Request! 👍
Please regenerate misskey-js type definitions! 🙏
example:
```sh
pnpm run build-misskey-js-with-types
```
- name: send message
if: success()
uses: thollander/actions-comment-pull-request@v2
with:
comment_tag: check-misskey-js-autogen
mode: delete
message: "Thank you!"
-12
View File
@@ -5,19 +5,7 @@ on:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/frontend/**
- packages/sw/**
- packages/misskey-js/**
- packages/shared/.eslintrc.js
pull_request:
paths:
- packages/backend/**
- packages/frontend/**
- packages/sw/**
- packages/misskey-js/**
- packages/shared/.eslintrc.js
jobs:
pnpm_install:
+3 -61
View File
@@ -5,18 +5,10 @@ on:
branches:
- master
- develop
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
pull_request:
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
jobs:
unit:
jest:
runs-on: ubuntu-latest
strategy:
@@ -59,59 +51,9 @@ jobs:
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage
- name: Upload to Codecov
run: pnpm jest-and-coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json
e2e:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.10.0]
services:
postgres:
image: postgres:15
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:7
ports:
- 56312:6379
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage:e2e
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json
-13
View File
@@ -5,20 +5,7 @@ on:
branches:
- master
- develop
paths:
- packages/frontend/**
# for permissions
- packages/misskey-js/**
# for e2e
- packages/backend/**
pull_request:
paths:
- packages/frontend/**
# for permissions
- packages/misskey-js/**
# for e2e
- packages/backend/**
jobs:
vitest:
-4
View File
@@ -6,12 +6,8 @@ name: Test (misskey.js)
on:
push:
branches: [ develop ]
paths:
- packages/misskey-js/**
pull_request:
branches: [ develop ]
paths:
- packages/misskey-js/**
jobs:
test:
-47
View File
@@ -1,47 +0,0 @@
name: Test (backend)
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
pull_request:
paths:
- packages/backend/**
jobs:
validate-api-json:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.10.0]
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install Redocly CLI
run: npm i -g @redocly/cli
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .config/example.yml .config/default.yml
- name: Build and generate
run: pnpm build && pnpm --filter backend generate-api-json
- name: Validation
run: npx @redocly/cli lint --extends=minimal ./packages/backend/built/api.json
-1
View File
@@ -41,7 +41,6 @@ docker-compose.yml
# misskey
/build
built
built-test
/data
/.cache-loader
/db
+1 -35
View File
@@ -14,43 +14,9 @@
## 202x.x.x (Unreleased)
### General
- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
### Client
- Feat: 新しいゲームを追加
- Feat: 音声・映像プレイヤーを追加
- Feat: 絵文字の詳細ダイアログを追加
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
- デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
- Feat: スワイプでタブを切り替えられるように
- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
- Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md)
- 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意
- Enhance: 絵文字ピッカー・オートコンプリートで、完全一致した絵文字を優先的に表示するように
- Enhance: Playの説明欄にMFMを使えるように
- Enhance: チャンネルノートの場合は詳細ページからその前後のノートを見れるように
- Fix: ネイティブモードの絵文字がモノクロにならないように
- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正
- Enhance: ページ遷移時にPlayerを閉じるように
### Server
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
- Enhance: クリップをエクスポートできるように
- Enhance: `/files`のファイルに対してHTTP Rangeリクエストを行えるように
- Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新
- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正
- Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text``""`から`null`になるように変更
- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
- Fix: properly handle cc followers
- Enhance: ActivityPubをサポートしているウェブリンクを展開できるように
## 2023.12.2
+1 -1
View File
@@ -1,5 +1,5 @@
Unless otherwise stated this repository is
Copyright © 2014-2024 syuilo and contributors
Copyright © 2014-2023 syuilo and contributors
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
-1
View File
@@ -6,7 +6,6 @@ Also, the later tasks are more indefinite and are subject to change as developme
This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
- ~~Make the number of type errors zero (backend)~~ → Done ✔️
- Make the number of type errors zero (frontend)
- Improve CI
- ~~Fix tests~~ → Done ✔️
- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986
-31
View File
@@ -7,7 +7,6 @@ services:
links:
- db
- redis
# - mcaptcha
# - meilisearch
depends_on:
db:
@@ -49,36 +48,6 @@ services:
interval: 5s
retries: 20
# mcaptcha:
# restart: always
# image: mcaptcha/mcaptcha:latest
# networks:
# internal_network:
# external_network:
# aliases:
# - localhost
# ports:
# - 7493:7493
# env_file:
# - .config/docker.env
# environment:
# PORT: 7493
# MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
# depends_on:
# db:
# condition: service_healthy
# mcaptcha_redis:
# condition: service_healthy
#
# mcaptcha_redis:
# image: mcaptcha/cache:latest
# networks:
# - internal_network
# healthcheck:
# test: "redis-cli ping"
# interval: 5s
# retries: 20
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4
+36 -165
View File
@@ -6,171 +6,54 @@ import ts from 'typescript';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const parameterRegExp = /\{(\w+)\}/g;
function createMemberType(item) {
if (typeof item !== 'string') {
return ts.factory.createTypeLiteralNode(createMembers(item));
}
const parameters = Array.from(
item.matchAll(parameterRegExp),
([, parameter]) => parameter,
);
if (!parameters.length) {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
}
return ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('ParameterizedString'),
[
ts.factory.createUnionTypeNode(
parameters.map((parameter) =>
ts.factory.createLiteralTypeNode(
ts.factory.createStringLiteral(parameter),
),
),
),
],
);
}
function createMembers(record) {
return Object.entries(record).map(([k, v]) =>
ts.factory.createPropertySignature(
return Object.entries(record)
.map(([k, v]) => ts.factory.createPropertySignature(
undefined,
ts.factory.createStringLiteral(k),
undefined,
createMemberType(v),
),
);
typeof v === 'string'
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
: ts.factory.createTypeLiteralNode(createMembers(v)),
));
}
export default function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const members = createMembers(locale);
const elements = [
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('kParameters'),
undefined,
ts.factory.createTypeOperatorNode(
ts.SyntaxKind.UniqueKeyword,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword),
),
undefined,
),
],
ts.NodeFlags.Const,
),
),
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('ParameterizedString'),
[
ts.factory.createTypeParameterDeclaration(
undefined,
ts.factory.createIdentifier('T'),
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('string'),
undefined,
),
),
],
undefined,
[
ts.factory.createPropertySignature(
undefined,
ts.factory.createComputedPropertyName(
ts.factory.createIdentifier('kParameters'),
),
undefined,
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('T'),
undefined,
),
),
],
),
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('ILocale'),
undefined,
undefined,
[
ts.factory.createIndexSignature(
undefined,
[
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier('_'),
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
),
],
ts.factory.createUnionTypeNode([
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('ParameterizedString'),
[ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)],
),
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('ILocale'),
undefined,
),
]),
),
],
),
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('Locale'),
undefined,
[
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
ts.factory.createExpressionWithTypeArguments(
ts.factory.createIdentifier('ILocale'),
undefined,
),
]),
],
undefined,
members,
),
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('locales'),
[ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('locales'),
undefined,
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
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.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.createFunctionDeclaration(
@@ -187,28 +70,16 @@ export default function generateDTS() {
),
ts.factory.createExportDefault(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,
),
);
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 */
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',
);
${printed}`, 'utf-8');
}
+111 -189
View File
@@ -1,19 +1,12 @@
/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
declare const kParameters: unique symbol;
export interface ParameterizedString<T extends string> {
[kParameters]: T;
}
export interface ILocale {
[_: string]: string | ParameterizedString<string> | ILocale;
}
export interface Locale extends ILocale {
export interface Locale {
"_lang_": string;
"headlineMisskey": string;
"introMisskey": string;
"poweredByMisskeyDescription": ParameterizedString<"name">;
"monthAndDay": ParameterizedString<"month" | "day">;
"poweredByMisskeyDescription": string;
"monthAndDay": string;
"search": string;
"notifications": string;
"username": string;
@@ -25,7 +18,7 @@ export interface Locale extends ILocale {
"cancel": string;
"noThankYou": string;
"enterUsername": string;
"renotedBy": ParameterizedString<"user">;
"renotedBy": string;
"noNotes": string;
"noNotifications": string;
"instance": string;
@@ -85,8 +78,8 @@ export interface Locale extends ILocale {
"export": string;
"files": string;
"download": string;
"driveFileDeleteConfirm": ParameterizedString<"name">;
"unfollowConfirm": ParameterizedString<"name">;
"driveFileDeleteConfirm": string;
"unfollowConfirm": string;
"exportRequested": string;
"importRequested": string;
"lists": string;
@@ -190,9 +183,9 @@ export interface Locale extends ILocale {
"wallpaper": string;
"setWallpaper": string;
"removeWallpaper": string;
"searchWith": ParameterizedString<"q">;
"searchWith": string;
"youHaveNoLists": string;
"followConfirm": ParameterizedString<"name">;
"followConfirm": string;
"proxyAccount": string;
"proxyAccountDescription": string;
"host": string;
@@ -215,7 +208,7 @@ export interface Locale extends ILocale {
"software": string;
"version": string;
"metadata": string;
"withNFiles": ParameterizedString<"n">;
"withNFiles": string;
"monitor": string;
"jobQueue": string;
"cpuAndMemory": string;
@@ -244,7 +237,7 @@ export interface Locale extends ILocale {
"processing": string;
"preview": string;
"default": string;
"defaultValueIs": ParameterizedString<"value">;
"defaultValueIs": string;
"noCustomEmojis": string;
"noJobs": string;
"federating": string;
@@ -273,8 +266,8 @@ export interface Locale extends ILocale {
"imageUrl": string;
"remove": string;
"removed": string;
"removeAreYouSure": ParameterizedString<"x">;
"deleteAreYouSure": ParameterizedString<"x">;
"removeAreYouSure": string;
"deleteAreYouSure": string;
"resetAreYouSure": string;
"areYouSure": string;
"saved": string;
@@ -292,8 +285,8 @@ export interface Locale extends ILocale {
"messageRead": string;
"noMoreHistory": string;
"startMessaging": string;
"nUsersRead": ParameterizedString<"n">;
"agreeTo": ParameterizedString<"0">;
"nUsersRead": string;
"agreeTo": string;
"agree": string;
"agreeBelow": string;
"basicNotesBeforeCreateAccount": string;
@@ -305,7 +298,7 @@ export interface Locale extends ILocale {
"images": string;
"image": string;
"birthday": string;
"yearsOld": ParameterizedString<"age">;
"yearsOld": string;
"registeredDate": string;
"location": string;
"theme": string;
@@ -360,9 +353,9 @@ export interface Locale extends ILocale {
"thisYear": string;
"thisMonth": string;
"today": string;
"dayX": ParameterizedString<"day">;
"monthX": ParameterizedString<"month">;
"yearX": ParameterizedString<"year">;
"dayX": string;
"monthX": string;
"yearX": string;
"pages": string;
"integration": string;
"connectService": string;
@@ -389,11 +382,6 @@ export interface Locale extends ILocale {
"enableHcaptcha": string;
"hcaptchaSiteKey": string;
"hcaptchaSecretKey": string;
"mcaptcha": string;
"enableMcaptcha": string;
"mcaptchaSiteKey": string;
"mcaptchaSecretKey": string;
"mcaptchaInstanceUrl": string;
"recaptcha": string;
"enableRecaptcha": string;
"recaptchaSiteKey": string;
@@ -427,7 +415,7 @@ export interface Locale extends ILocale {
"recentlyUpdatedUsers": string;
"recentlyRegisteredUsers": string;
"recentlyDiscoveredUsers": string;
"exploreUsersCount": ParameterizedString<"count">;
"exploreUsersCount": string;
"exploreFediverse": string;
"popularTags": string;
"userList": string;
@@ -444,16 +432,16 @@ export interface Locale extends ILocale {
"moderationNote": string;
"addModerationNote": string;
"moderationLogs": string;
"nUsersMentioned": ParameterizedString<"n">;
"nUsersMentioned": string;
"securityKeyAndPasskey": string;
"securityKey": string;
"lastUsed": string;
"lastUsedAt": ParameterizedString<"t">;
"lastUsedAt": string;
"unregister": string;
"passwordLessLogin": string;
"passwordLessLoginDescription": string;
"resetPassword": string;
"newPasswordIs": ParameterizedString<"password">;
"newPasswordIs": string;
"reduceUiAnimation": string;
"share": string;
"notFound": string;
@@ -473,7 +461,7 @@ export interface Locale extends ILocale {
"enable": string;
"next": string;
"retype": string;
"noteOf": ParameterizedString<"user">;
"noteOf": string;
"quoteAttached": string;
"quoteQuestion": string;
"noMessagesYet": string;
@@ -493,12 +481,12 @@ export interface Locale extends ILocale {
"strongPassword": string;
"passwordMatched": string;
"passwordNotMatched": string;
"signinWith": ParameterizedString<"x">;
"signinWith": string;
"signinFailed": string;
"or": string;
"language": string;
"uiLanguage": string;
"aboutX": ParameterizedString<"x">;
"aboutX": string;
"emojiStyle": string;
"native": string;
"disableDrawer": string;
@@ -516,7 +504,7 @@ export interface Locale extends ILocale {
"regenerate": string;
"fontSize": string;
"mediaListWithOneImageAppearance": string;
"limitTo": ParameterizedString<"x">;
"limitTo": string;
"noFollowRequests": string;
"openImageInNewTab": string;
"dashboard": string;
@@ -594,7 +582,7 @@ export interface Locale extends ILocale {
"deleteAllFiles": string;
"deleteAllFilesConfirm": string;
"removeAllFollowing": string;
"removeAllFollowingDescription": ParameterizedString<"host">;
"removeAllFollowingDescription": string;
"userSuspended": string;
"userSilenced": string;
"yourAccountSuspendedTitle": string;
@@ -621,6 +609,7 @@ export interface Locale extends ILocale {
"enablePlayer": string;
"disablePlayer": string;
"expandTweet": string;
"expandNote": string;
"themeEditor": string;
"description": string;
"describeFile": string;
@@ -641,7 +630,6 @@ export interface Locale extends ILocale {
"small": string;
"generateAccessToken": string;
"permission": string;
"adminPermission": string;
"enableAll": string;
"disableAll": string;
"tokenRequested": string;
@@ -665,9 +653,9 @@ export interface Locale extends ILocale {
"wordMute": string;
"hardWordMute": string;
"regexpError": string;
"regexpErrorDescription": ParameterizedString<"tab" | "line">;
"regexpErrorDescription": string;
"instanceMute": string;
"userSaysSomething": ParameterizedString<"name">;
"userSaysSomething": string;
"makeActive": string;
"display": string;
"copy": string;
@@ -685,7 +673,6 @@ export interface Locale extends ILocale {
"other": string;
"regenerateLoginToken": string;
"regenerateLoginTokenDescription": string;
"theKeywordWhenSearchingForCustomEmoji": string;
"setMultipleBySeparatingWithSpace": string;
"fileIdOrUrl": string;
"behavior": string;
@@ -693,7 +680,7 @@ export interface Locale extends ILocale {
"abuseReports": string;
"reportAbuse": string;
"reportAbuseRenote": string;
"reportAbuseOf": ParameterizedString<"name">;
"reportAbuseOf": string;
"fillAbuseReportDescription": string;
"abuseReported": string;
"reporter": string;
@@ -708,7 +695,7 @@ export interface Locale extends ILocale {
"defaultNavigationBehaviour": string;
"editTheseSettingsMayBreakAccount": string;
"instanceTicker": string;
"waitingFor": ParameterizedString<"x">;
"waitingFor": string;
"random": string;
"system": string;
"switchUi": string;
@@ -718,10 +705,10 @@ export interface Locale extends ILocale {
"optional": string;
"createNewClip": string;
"unclip": string;
"confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">;
"confirmToUnclipAlreadyClippedNote": string;
"public": string;
"private": string;
"i18nInfo": ParameterizedString<"link">;
"i18nInfo": string;
"manageAccessTokens": string;
"accountInfo": string;
"notesCount": string;
@@ -771,9 +758,9 @@ export interface Locale extends ILocale {
"needReloadToApply": string;
"showTitlebar": string;
"clearCache": string;
"onlineUsersCount": ParameterizedString<"n">;
"nUsers": ParameterizedString<"n">;
"nNotes": ParameterizedString<"n">;
"onlineUsersCount": string;
"nUsers": string;
"nNotes": string;
"sendErrorReports": string;
"sendErrorReportsDescription": string;
"myTheme": string;
@@ -805,7 +792,7 @@ export interface Locale extends ILocale {
"publish": string;
"inChannelSearch": string;
"useReactionPickerForContextMenu": string;
"typingUsers": ParameterizedString<"users">;
"typingUsers": string;
"jumpToSpecifiedDate": string;
"showingPastTimeline": string;
"clear": string;
@@ -872,7 +859,7 @@ export interface Locale extends ILocale {
"misskeyUpdated": string;
"whatIsNew": string;
"translate": string;
"translatedFrom": ParameterizedString<"x">;
"translatedFrom": string;
"accountDeletionInProgress": string;
"usernameInfo": string;
"aiChanMode": string;
@@ -903,11 +890,11 @@ export interface Locale extends ILocale {
"continueThread": string;
"deleteAccountConfirm": string;
"incorrectPassword": string;
"voteConfirm": ParameterizedString<"choice">;
"voteConfirm": string;
"hide": string;
"useDrawerReactionPickerForMobile": string;
"welcomeBackWithName": ParameterizedString<"name">;
"clickToFinishEmailVerification": ParameterizedString<"ok">;
"welcomeBackWithName": string;
"clickToFinishEmailVerification": string;
"overridedDeviceKind": string;
"smartphone": string;
"tablet": string;
@@ -935,8 +922,8 @@ export interface Locale extends ILocale {
"cropYes": string;
"cropNo": string;
"file": string;
"recentNHours": ParameterizedString<"n">;
"recentNDays": ParameterizedString<"n">;
"recentNHours": string;
"recentNDays": string;
"noEmailServerWarning": string;
"thereIsUnresolvedAbuseReportWarning": string;
"recommended": string;
@@ -945,7 +932,7 @@ export interface Locale extends ILocale {
"driveCapOverrideCaption": string;
"requireAdminForView": string;
"isSystemAccount": string;
"typeToConfirm": ParameterizedString<"x">;
"typeToConfirm": string;
"deleteAccount": string;
"document": string;
"numberOfPageCache": string;
@@ -999,7 +986,7 @@ export interface Locale extends ILocale {
"neverShow": string;
"remindMeLater": string;
"didYouLikeMisskey": string;
"pleaseDonate": ParameterizedString<"host">;
"pleaseDonate": string;
"roles": string;
"role": string;
"noRole": string;
@@ -1068,8 +1055,6 @@ export interface Locale extends ILocale {
"noteIdOrUrl": string;
"video": string;
"videos": string;
"audio": string;
"audioFiles": string;
"dataSaver": string;
"accountMigration": string;
"accountMoved": string;
@@ -1097,7 +1082,7 @@ export interface Locale extends ILocale {
"preservedUsernamesDescription": string;
"createNoteFromTheFile": string;
"archive": string;
"channelArchiveConfirmTitle": ParameterizedString<"name">;
"channelArchiveConfirmTitle": string;
"channelArchiveConfirmDescription": string;
"thisChannelArchived": string;
"displayOfNote": string;
@@ -1127,8 +1112,8 @@ export interface Locale extends ILocale {
"createCount": string;
"inviteCodeCreated": string;
"inviteLimitExceeded": string;
"createLimitRemaining": ParameterizedString<"limit">;
"inviteLimitResetCycle": ParameterizedString<"time" | "limit">;
"createLimitRemaining": string;
"inviteLimitResetCycle": string;
"expirationDate": string;
"noExpirationDate": string;
"inviteCodeUsedAt": string;
@@ -1141,7 +1126,7 @@ export interface Locale extends ILocale {
"expired": string;
"doYouAgree": string;
"beSureToReadThisAsItIsImportant": string;
"iHaveReadXCarefullyAndAgree": ParameterizedString<"x">;
"iHaveReadXCarefullyAndAgree": string;
"dialog": string;
"icon": string;
"forYou": string;
@@ -1196,30 +1181,12 @@ export interface Locale extends ILocale {
"doReaction": string;
"code": string;
"reloadRequiredToApplySettings": string;
"remainingN": ParameterizedString<"n">;
"remainingN": string;
"overwriteContentConfirm": string;
"seasonalScreenEffect": string;
"decorate": string;
"addMfmFunction": string;
"enableQuickAddMfmFunction": string;
"bubbleGame": string;
"sfx": string;
"soundWillBePlayed": string;
"showReplay": string;
"replay": string;
"replaying": string;
"ranking": string;
"lastNDays": ParameterizedString<"n">;
"backToTitle": string;
"enableHorizontalSwipe": string;
"_bubbleGame": {
"howToPlay": string;
"_howToPlay": {
"section1": string;
"section2": string;
"section3": string;
};
};
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;
@@ -1228,7 +1195,7 @@ export interface Locale extends ILocale {
"end": string;
"tooManyActiveAnnouncementDescription": string;
"readConfirmTitle": string;
"readConfirmText": ParameterizedString<"title">;
"readConfirmText": string;
"shouldNotBeUsedToPresentPermanentInfo": string;
"dialogAnnouncementUxWarn": string;
"silence": string;
@@ -1243,10 +1210,10 @@ export interface Locale extends ILocale {
"theseSettingsCanEditLater": string;
"youCanEditMoreSettingsInSettingsPageLater": string;
"followUsers": string;
"pushNotificationDescription": ParameterizedString<"name">;
"pushNotificationDescription": string;
"initialAccountSettingCompleted": string;
"haveFun": ParameterizedString<"name">;
"youCanContinueTutorial": ParameterizedString<"name">;
"haveFun": string;
"youCanContinueTutorial": string;
"startTutorial": string;
"skipAreYouSure": string;
"laterAreYouSure": string;
@@ -1284,7 +1251,7 @@ export interface Locale extends ILocale {
"social": string;
"global": string;
"description2": string;
"description3": ParameterizedString<"link">;
"description3": string;
};
"_postNote": {
"title": string;
@@ -1322,7 +1289,7 @@ export interface Locale extends ILocale {
};
"_done": {
"title": string;
"description": ParameterizedString<"link">;
"description": string;
};
};
"_timelineDescription": {
@@ -1336,10 +1303,10 @@ export interface Locale extends ILocale {
};
"_serverSettings": {
"iconUrl": string;
"appIconDescription": ParameterizedString<"host">;
"appIconDescription": string;
"appIconUsageExample": string;
"appIconStyleRecommendation": string;
"appIconResolutionMustBe": ParameterizedString<"resolution">;
"appIconResolutionMustBe": string;
"manifestJsonOverride": string;
"shortName": string;
"shortNameDescription": string;
@@ -1350,7 +1317,7 @@ export interface Locale extends ILocale {
"_accountMigration": {
"moveFrom": string;
"moveFromSub": string;
"moveFromLabel": ParameterizedString<"n">;
"moveFromLabel": string;
"moveFromDescription": string;
"moveTo": string;
"moveToLabel": string;
@@ -1358,7 +1325,7 @@ export interface Locale extends ILocale {
"moveAccountDescription": string;
"moveAccountHowTo": string;
"startMigration": string;
"migrationConfirm": ParameterizedString<"account">;
"migrationConfirm": string;
"movedAndCannotBeUndone": string;
"postMigrationNote": string;
"movedTo": string;
@@ -1684,15 +1651,6 @@ export interface Locale extends ILocale {
"title": string;
"description": string;
};
"_bubbleGameExplodingHead": {
"title": string;
"description": string;
};
"_bubbleGameDoubleExplodingHead": {
"title": string;
"description": string;
"flavor": string;
};
};
};
"_role": {
@@ -1800,7 +1758,7 @@ export interface Locale extends ILocale {
"_signup": {
"almostThere": string;
"emailAddressInfo": string;
"emailSent": ParameterizedString<"email">;
"emailSent": string;
};
"_accountDelete": {
"accountDelete": string;
@@ -1853,14 +1811,14 @@ export interface Locale extends ILocale {
"save": string;
"inputName": string;
"cannotSave": string;
"nameAlreadyExists": ParameterizedString<"name">;
"applyConfirm": ParameterizedString<"name">;
"saveConfirm": ParameterizedString<"name">;
"deleteConfirm": ParameterizedString<"name">;
"renameConfirm": ParameterizedString<"old" | "new">;
"nameAlreadyExists": string;
"applyConfirm": string;
"saveConfirm": string;
"deleteConfirm": string;
"renameConfirm": string;
"noBackups": string;
"createdAt": ParameterizedString<"date" | "time">;
"updatedAt": ParameterizedString<"date" | "time">;
"createdAt": string;
"updatedAt": string;
"cannotLoad": string;
"invalidFile": string;
};
@@ -1905,8 +1863,8 @@ export interface Locale extends ILocale {
"featured": string;
"owned": string;
"following": string;
"usersCount": ParameterizedString<"n">;
"notesCount": ParameterizedString<"n">;
"usersCount": string;
"notesCount": string;
"nameAndDescription": string;
"nameOnly": string;
"allowRenoteToExternal": string;
@@ -1934,7 +1892,7 @@ export interface Locale extends ILocale {
"manage": string;
"code": string;
"description": string;
"installed": ParameterizedString<"name">;
"installed": string;
"installedThemes": string;
"builtinThemes": string;
"alreadyInstalled": string;
@@ -1957,7 +1915,7 @@ export interface Locale extends ILocale {
"lighten": string;
"inputConstantName": string;
"importInfo": string;
"deleteConstantConfirm": ParameterizedString<"const">;
"deleteConstantConfirm": string;
"keys": {
"accent": string;
"bg": string;
@@ -2020,23 +1978,23 @@ export interface Locale extends ILocale {
"_ago": {
"future": string;
"justNow": string;
"secondsAgo": ParameterizedString<"n">;
"minutesAgo": ParameterizedString<"n">;
"hoursAgo": ParameterizedString<"n">;
"daysAgo": ParameterizedString<"n">;
"weeksAgo": ParameterizedString<"n">;
"monthsAgo": ParameterizedString<"n">;
"yearsAgo": ParameterizedString<"n">;
"secondsAgo": string;
"minutesAgo": string;
"hoursAgo": string;
"daysAgo": string;
"weeksAgo": string;
"monthsAgo": string;
"yearsAgo": string;
"invalid": string;
};
"_timeIn": {
"seconds": ParameterizedString<"n">;
"minutes": ParameterizedString<"n">;
"hours": ParameterizedString<"n">;
"days": ParameterizedString<"n">;
"weeks": ParameterizedString<"n">;
"months": ParameterizedString<"n">;
"years": ParameterizedString<"n">;
"seconds": string;
"minutes": string;
"hours": string;
"days": string;
"weeks": string;
"months": string;
"years": string;
};
"_time": {
"second": string;
@@ -2047,7 +2005,7 @@ export interface Locale extends ILocale {
"_2fa": {
"alreadyRegistered": string;
"registerTOTP": string;
"step1": ParameterizedString<"a" | "b">;
"step1": string;
"step2": string;
"step2Click": string;
"step2Uri": string;
@@ -2062,7 +2020,7 @@ export interface Locale extends ILocale {
"securityKeyName": string;
"tapSecurityKey": string;
"removeKey": string;
"removeKeyConfirm": ParameterizedString<"name">;
"removeKeyConfirm": string;
"whyTOTPOnlyRenew": string;
"renewTOTP": string;
"renewTOTPConfirm": string;
@@ -2163,9 +2121,9 @@ export interface Locale extends ILocale {
};
"_auth": {
"shareAccessTitle": string;
"shareAccess": ParameterizedString<"name">;
"shareAccess": string;
"shareAccessAsk": string;
"permission": ParameterizedString<"name">;
"permission": string;
"permissionAsk": string;
"pleaseGoBack": string;
"callback": string;
@@ -2224,12 +2182,12 @@ export interface Locale extends ILocale {
"_cw": {
"hide": string;
"show": string;
"chars": ParameterizedString<"count">;
"files": ParameterizedString<"count">;
"chars": string;
"files": string;
};
"_poll": {
"noOnlyOneChoice": string;
"choiceN": ParameterizedString<"n">;
"choiceN": string;
"noMore": string;
"canMultipleVote": string;
"expiration": string;
@@ -2239,16 +2197,16 @@ export interface Locale extends ILocale {
"deadlineDate": string;
"deadlineTime": string;
"duration": string;
"votesCount": ParameterizedString<"n">;
"totalVotes": ParameterizedString<"n">;
"votesCount": string;
"totalVotes": string;
"vote": string;
"showResult": string;
"voted": string;
"closed": string;
"remainingDays": ParameterizedString<"d" | "h">;
"remainingHours": ParameterizedString<"h" | "m">;
"remainingMinutes": ParameterizedString<"m" | "s">;
"remainingSeconds": ParameterizedString<"s">;
"remainingDays": string;
"remainingHours": string;
"remainingMinutes": string;
"remainingSeconds": string;
};
"_visibility": {
"public": string;
@@ -2288,12 +2246,11 @@ export interface Locale extends ILocale {
"changeAvatar": string;
"changeBanner": string;
"verifiedLinkDescription": string;
"avatarDecorationMax": ParameterizedString<"max">;
"avatarDecorationMax": string;
};
"_exportOrImport": {
"allNotes": string;
"favoritedNotes": string;
"clips": string;
"followingList": string;
"muteList": string;
"blockingList": string;
@@ -2411,16 +2368,16 @@ export interface Locale extends ILocale {
};
"_notification": {
"fileUploaded": string;
"youGotMention": ParameterizedString<"name">;
"youGotReply": ParameterizedString<"name">;
"youGotQuote": ParameterizedString<"name">;
"youRenoted": ParameterizedString<"name">;
"youGotMention": string;
"youGotReply": string;
"youGotQuote": string;
"youRenoted": string;
"youWereFollowed": string;
"youReceivedFollowRequest": string;
"yourFollowRequestAccepted": string;
"pollEnded": string;
"newNote": string;
"unreadAntennaNote": ParameterizedString<"name">;
"unreadAntennaNote": string;
"roleAssigned": string;
"emptyPushNotificationMessage": string;
"achievementEarned": string;
@@ -2428,9 +2385,9 @@ export interface Locale extends ILocale {
"checkNotificationBehavior": string;
"sendTestNotification": string;
"notificationWillBeDisplayedLikeThis": string;
"reactedBySomeUsers": ParameterizedString<"n">;
"renotedBySomeUsers": ParameterizedString<"n">;
"followedBySomeUsers": ParameterizedString<"n">;
"reactedBySomeUsers": string;
"renotedBySomeUsers": string;
"followedBySomeUsers": string;
"_types": {
"all": string;
"note": string;
@@ -2487,8 +2444,8 @@ export interface Locale extends ILocale {
};
};
"_dialog": {
"charactersExceeded": ParameterizedString<"current" | "max">;
"charactersBelow": ParameterizedString<"current" | "min">;
"charactersExceeded": string;
"charactersBelow": string;
};
"_disabledTimeline": {
"title": string;
@@ -2633,41 +2590,6 @@ export interface Locale extends ILocale {
"description": string;
};
};
"_reversi": {
"reversi": string;
"gameSettings": string;
"chooseBoard": string;
"blackOrWhite": string;
"blackIs": ParameterizedString<"name">;
"rules": string;
"thisGameIsStartedSoon": string;
"waitingForOther": string;
"waitingForMe": string;
"waitingBoth": string;
"ready": string;
"cancelReady": string;
"opponentTurn": string;
"myTurn": string;
"turnOf": ParameterizedString<"name">;
"pastTurnOf": ParameterizedString<"name">;
"surrender": string;
"surrendered": string;
"drawn": string;
"won": ParameterizedString<"name">;
"black": string;
"white": string;
"total": string;
"turnCount": ParameterizedString<"count">;
"myGames": string;
"allGames": string;
"ended": string;
"playing": string;
"isLlotheo": string;
"loopedMap": string;
"canPutEverywhere": string;
"freeMatch": string;
"lookingForPlayer": string;
};
}
declare const locales: {
[lang: string]: Locale;
+1 -69
View File
@@ -379,11 +379,6 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchaを有効にする"
hcaptchaSiteKey: "サイトキー"
hcaptchaSecretKey: "シークレットキー"
mcaptcha: "mCaptcha"
enableMcaptcha: "mCaptchaを有効にする"
mcaptchaSiteKey: "サイトキー"
mcaptchaSecretKey: "シークレットキー"
mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHAを有効にする"
recaptchaSiteKey: "サイトキー"
@@ -611,6 +606,7 @@ useCw: "内容を隠す"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
expandTweet: "ポストを展開する"
expandNote: "ノートを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "キャプションを付ける"
@@ -631,7 +627,6 @@ medium: "中"
small: "小"
generateAccessToken: "アクセストークンの発行"
permission: "権限"
adminPermission: "管理者権限"
enableAll: "全て有効にする"
disableAll: "全て無効にする"
tokenRequested: "アカウントへのアクセス許可"
@@ -675,7 +670,6 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使
other: "その他"
regenerateLoginToken: "ログイントークンを再生成"
regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を検索する時のキーワードになります。"
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
fileIdOrUrl: "ファイルIDまたはURL"
behavior: "動作"
@@ -1058,8 +1052,6 @@ limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小し
noteIdOrUrl: "ノートIDまたはURL"
video: "動画"
videos: "動画"
audio: "音声"
audioFiles: "音声"
dataSaver: "データセーバー"
accountMigration: "アカウントの移行"
accountMoved: "このユーザーは新しいアカウントに移行しました:"
@@ -1192,23 +1184,6 @@ seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
addMfmFunction: "装飾を追加"
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
bubbleGame: "バブルゲーム"
sfx: "効果音"
soundWillBePlayed: "サウンドが再生されます"
showReplay: "リプレイを見る"
replay: "リプレイ"
replaying: "リプレイ中"
ranking: "ランキング"
lastNDays: "直近{n}日"
backToTitle: "タイトルへ"
enableHorizontalSwipe: "スワイプしてタブを切り替える"
_bubbleGame:
howToPlay: "遊び方"
_howToPlay:
section1: "位置を調整してハコにモノを落とします。"
section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。"
section3: "モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!"
_announcement:
forExistingUsers: "既存ユーザーのみ"
@@ -1587,13 +1562,6 @@ _achievements:
_tutorialCompleted:
title: "Misskey初心者講座 修了証"
description: "チュートリアルを完了した"
_bubbleGameExplodingHead:
title: "🤯"
description: "バブルゲームで最も大きいモノを出した"
_bubbleGameDoubleExplodingHead:
title: "ダブル🤯"
description: "バブルゲームで最も大きいモノを2つ同時に出した"
flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて"
_role:
new: "ロールの作成"
@@ -2186,7 +2154,6 @@ _profile:
_exportOrImport:
allNotes: "全てのノート"
favoritedNotes: "お気に入りにしたノート"
clips: "クリップ"
followingList: "フォロー"
muteList: "ミュート"
blockingList: "ブロック"
@@ -2506,38 +2473,3 @@ _dataSaver:
_code:
title: "コードハイライト"
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
_reversi:
reversi: "リバーシ"
gameSettings: "対局の設定"
chooseBoard: "ボードを選択"
blackOrWhite: "先行/後攻"
blackIs: "{name}が黒(先行)"
rules: "ルール"
thisGameIsStartedSoon: "対局はまもなく開始されます"
waitingForOther: "相手の準備が完了するのを待っています"
waitingForMe: "あなたの準備が完了するのを待っています"
waitingBoth: "準備してください"
ready: "準備完了"
cancelReady: "準備を再開"
opponentTurn: "相手のターンです"
myTurn: "あなたのターンです"
turnOf: "{name}のターンです"
pastTurnOf: "{name}のターン"
surrender: "投了"
surrendered: "投了により"
drawn: "引き分け"
won: "{name}の勝ち"
black: "黒"
white: "白"
total: "合計"
turnCount: "{count}ターン目"
myGames: "自分の対局"
allGames: "みんなの対局"
ended: "終了"
playing: "対局中"
isLlotheo: "石の少ない方が勝ち(ロセオ)"
loopedMap: "ループマップ"
canPutEverywhere: "どこでも置けるモード"
freeMatch: "フリーマッチ"
lookingForPlayer: "対戦相手を探しています"
+1
View File
@@ -160,6 +160,7 @@ module.exports = {
testMatch: [
"<rootDir>/test/unit/**/*.ts",
"<rootDir>/src/**/*.test.ts",
"<rootDir>/test/e2e/**/*.ts",
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
-15
View File
@@ -1,15 +0,0 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
const base = require('./jest.config.cjs')
module.exports = {
...base,
globalSetup: "<rootDir>/built-test/entry.js",
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
testMatch: [
"<rootDir>/test/e2e/**/*.ts",
],
};
-14
View File
@@ -1,14 +0,0 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
const base = require('./jest.config.cjs')
module.exports = {
...base,
testMatch: [
"<rootDir>/test/unit/**/*.ts",
"<rootDir>/src/**/*.test.ts",
],
};
@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class SupportMcaptcha1704373210054 {
name = 'SupportMcaptcha1704373210054'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`);
}
}
@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class BubbleGameRecord1704959805077 {
name = 'BubbleGameRecord1704959805077'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "bubble_game_record" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "seededAt" TIMESTAMP WITH TIME ZONE NOT NULL, "seed" character varying(1024) NOT NULL, "gameVersion" integer NOT NULL, "gameMode" character varying(128) NOT NULL, "score" integer NOT NULL, "logs" jsonb NOT NULL DEFAULT '[]', "isVerified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_a75395fe404b392e2893b50d7ea" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_75276757070d21fdfaf4c05290" ON "bubble_game_record" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_4ae7053179014915d1432d3f40" ON "bubble_game_record" ("seededAt") `);
await queryRunner.query(`CREATE INDEX "IDX_26d4ee490b5a487142d35466ee" ON "bubble_game_record" ("score") `);
await queryRunner.query(`ALTER TABLE "bubble_game_record" ADD CONSTRAINT "FK_75276757070d21fdfaf4c052909" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "bubble_game_record" DROP CONSTRAINT "FK_75276757070d21fdfaf4c052909"`);
await queryRunner.query(`DROP INDEX "public"."IDX_26d4ee490b5a487142d35466ee"`);
await queryRunner.query(`DROP INDEX "public"."IDX_4ae7053179014915d1432d3f40"`);
await queryRunner.query(`DROP INDEX "public"."IDX_75276757070d21fdfaf4c05290"`);
await queryRunner.query(`DROP TABLE "bubble_game_record"`);
}
}
@@ -1,24 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class OptimizeNoteIndexForArrayColumns1705222772858 {
name = 'OptimizeNoteIndexForArrayColumns1705222772858'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_796a8c03959361f97dc2be1d5c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_54ebcb6d27222913b908d56fd8"`);
await queryRunner.query(`DROP INDEX "public"."IDX_88937d94d7443d9a99a76fa5c0"`);
await queryRunner.query(`DROP INDEX "public"."IDX_51c063b6a133a9cb87145450f5"`);
await queryRunner.query(`CREATE INDEX "IDX_NOTE_FILE_IDS" ON "note" using gin ("fileIds")`)
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "IDX_NOTE_FILE_IDS"`)
await queryRunner.query(`CREATE INDEX "IDX_51c063b6a133a9cb87145450f5" ON "note" ("fileIds") `);
await queryRunner.query(`CREATE INDEX "IDX_88937d94d7443d9a99a76fa5c0" ON "note" ("tags") `);
await queryRunner.query(`CREATE INDEX "IDX_54ebcb6d27222913b908d56fd8" ON "note" ("mentions") `);
await queryRunner.query(`CREATE INDEX "IDX_796a8c03959361f97dc2be1d5c" ON "note" ("visibleUserIds") `);
}
}
@@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Reversi1705475608437 {
name = 'Reversi1705475608437'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_b46ec40746efceac604142be1c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b604d92d6c7aec38627f6eaf16"`);
await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "createdAt"`);
await queryRunner.query(`ALTER TABLE "reversi_matching" DROP COLUMN "createdAt"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_matching" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
await queryRunner.query(`ALTER TABLE "reversi_game" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt") `);
}
}
@@ -1,18 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Reversi21705654039457 {
name = 'Reversi21705654039457'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Accepted" TO "user1Ready"`);
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Accepted" TO "user2Ready"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Ready" TO "user1Accepted"`);
await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Ready" TO "user2Accepted"`);
}
}
+4 -15
View File
@@ -13,7 +13,6 @@
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
"build": "swc src -d built -D",
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
"watch:swc": "swc src -d built -D -w",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
@@ -22,15 +21,11 @@
"typecheck": "tsc --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit",
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
"test": "pnpm jest",
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"generate-api-json": "node ./generate_api_json.js"
},
"optionalDependencies": {
@@ -79,8 +74,6 @@
"@fastify/multipart": "8.0.0",
"@fastify/static": "6.12.0",
"@fastify/view": "8.2.0",
"@misskey-dev/sharp-read-bmp": "^1.1.1",
"@misskey-dev/summaly": "^5.0.3",
"@nestjs/common": "10.2.10",
"@nestjs/core": "10.2.10",
"@nestjs/testing": "10.2.10",
@@ -107,7 +100,6 @@
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
"crc-32": "^1.2.2",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fastify": "4.24.3",
@@ -134,7 +126,6 @@
"microformats-parser": "2.0.2",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.1",
"nanoid": "5.0.4",
"nested-property": "4.0.0",
@@ -166,9 +157,11 @@
"sanitize-html": "2.11.0",
"secure-json-parse": "2.7.0",
"sharp": "0.32.6",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
"systeminformation": "5.21.20",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
@@ -184,8 +177,6 @@
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@misskey-dev/eslint-plugin": "^1.0.0",
"@nestjs/platform-express": "^10.3.0",
"@simplewebauthn/typescript-types": "8.3.4",
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.7",
@@ -234,11 +225,9 @@
"eslint": "8.56.0",
"eslint-plugin-import": "2.29.1",
"execa": "8.0.1",
"fkill": "^9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",
"nodemon": "3.0.2",
"pid-port": "^1.0.0",
"simple-oauth2": "5.0.0"
}
}
+11 -6
View File
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Global, Inject, Module } from '@nestjs/common';
import * as Redis from 'ioredis';
import { DataSource } from 'typeorm';
@@ -11,7 +12,6 @@ import { DI } from './di-symbols.js';
import { Config, loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js';
import { allSettled } from './misc/promise-tracker.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
const $config: Provider = {
@@ -33,7 +33,7 @@ const $meilisearch: Provider = {
useFactory: (config: Config) => {
if (config.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}`,
apiKey: config.meilisearch.apiKey,
});
} else {
@@ -91,12 +91,17 @@ export class GlobalModule implements OnApplicationShutdown {
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
) { }
) {}
public async dispose(): Promise<void> {
// Wait for all potential DB queries
await allSettled();
// And then disconnect from DB
if (process.env.NODE_ENV === 'test') {
// XXX:
// Shutting down the existing connections causes errors on Jest as
// Misskey has asynchronous postgres/redis connections that are not
// awaited.
// Let's wait for some random time for them to finish.
await setTimeout(5000);
}
await Promise.all([
this.db.destroy(),
this.redisClient.disconnect(),
@@ -87,8 +87,6 @@ export const ACHIEVEMENT_TYPES = [
'brainDiver',
'smashTestNotificationButton',
'tutorialCompleted',
'bubbleGameExplodingHead',
'bubbleGameDoubleExplodingHead',
] as const;
@Injectable()
@@ -73,37 +73,6 @@ export class CaptchaService {
}
}
// https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
@bindThis
public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
if (response == null) {
throw new Error('mcaptcha-failed: no response provided');
}
const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
const result = await this.httpRequestService.send(endpointUrl.toString(), {
method: 'POST',
body: JSON.stringify({
key: siteKey,
secret: secret,
token: response,
}),
headers: {
'Content-Type': 'application/json',
},
});
if (result.status !== 200) {
throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
}
const resp = (await result.json()) as { valid: boolean };
if (!resp.valid) {
throw new Error('mcaptcha-request-failed');
}
}
@bindThis
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
-27
View File
@@ -66,8 +66,6 @@ import { FeaturedService } from './FeaturedService.js';
import { FanoutTimelineService } from './FanoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
import { RegistryApiService } from './RegistryApiService.js';
import { ReversiService } from './ReversiService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
import NotesChart from './chart/charts/notes.js';
@@ -82,7 +80,6 @@ import PerUserFollowingChart from './chart/charts/per-user-following.js';
import PerUserDriveChart from './chart/charts/per-user-drive.js';
import ApRequestChart from './chart/charts/ap-request.js';
import { ChartManagementService } from './chart/ChartManagementService.js';
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
import { AntennaEntityService } from './entities/AntennaEntityService.js';
import { AppEntityService } from './entities/AppEntityService.js';
@@ -115,8 +112,6 @@ import { UserListEntityService } from './entities/UserListEntityService.js';
import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
import { RoleEntityService } from './entities/RoleEntityService.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -204,7 +199,6 @@ const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', use
const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -253,7 +247,6 @@ const $UserListEntityService: Provider = { provide: 'UserListEntityService', use
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -343,8 +336,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FanoutTimelineEndpointService,
ChannelFollowingService,
RegistryApiService,
ReversiService,
ChartLoggerService,
FederationChart,
NotesChart,
@@ -359,7 +350,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
PerUserDriveChart,
ApRequestChart,
ChartManagementService,
AbuseUserReportEntityService,
AntennaEntityService,
AppEntityService,
@@ -392,8 +382,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FlashEntityService,
FlashLikeEntityService,
RoleEntityService,
ReversiGameEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -478,8 +466,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FanoutTimelineEndpointService,
$ChannelFollowingService,
$RegistryApiService,
$ReversiService,
$ChartLoggerService,
$FederationChart,
$NotesChart,
@@ -494,7 +480,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$PerUserDriveChart,
$ApRequestChart,
$ChartManagementService,
$AbuseUserReportEntityService,
$AntennaEntityService,
$AppEntityService,
@@ -527,8 +512,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FlashEntityService,
$FlashLikeEntityService,
$RoleEntityService,
$ReversiGameEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
@@ -614,8 +597,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FanoutTimelineEndpointService,
ChannelFollowingService,
RegistryApiService,
ReversiService,
FederationChart,
NotesChart,
UsersChart,
@@ -629,7 +610,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
PerUserDriveChart,
ApRequestChart,
ChartManagementService,
AbuseUserReportEntityService,
AntennaEntityService,
AppEntityService,
@@ -662,8 +642,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FlashEntityService,
FlashLikeEntityService,
RoleEntityService,
ReversiGameEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -748,8 +726,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FanoutTimelineEndpointService,
$ChannelFollowingService,
$RegistryApiService,
$ReversiService,
$FederationChart,
$NotesChart,
$UsersChart,
@@ -763,7 +739,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$PerUserDriveChart,
$ApRequestChart,
$ChartManagementService,
$AbuseUserReportEntityService,
$AntennaEntityService,
$AppEntityService,
@@ -796,8 +771,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FlashEntityService,
$FlashLikeEntityService,
$RoleEntityService,
$ReversiGameEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
+1 -2
View File
@@ -145,8 +145,7 @@ export class DownloadService {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
if (parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import sharp from 'sharp';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import { sharpBmp } from 'sharp-read-bmp';
import { IsNull } from 'typeorm';
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
import { DI } from '@/di-symbols.js';
@@ -655,7 +655,7 @@ export class DriveService {
public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
if (values.name != null && !this.driveFileEntityService.validateFileName(values.name)) {
if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
throw new DriveService.InvalidFileNameError();
}
@@ -18,7 +18,7 @@ import type { MiSignin } from '@/models/Signin.js';
import type { MiPage } from '@/models/Page.js';
import type { MiWebhook } from '@/models/Webhook.js';
import type { MiMeta } from '@/models/Meta.js';
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
import { MiAvatarDecoration, MiRole, MiRoleAssignment } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@@ -159,43 +159,6 @@ export interface AdminEventTypes {
comment: string;
};
}
export interface ReversiEventTypes {
matched: {
game: Packed<'ReversiGameDetailed'>;
};
invited: {
user: Packed<'User'>;
};
}
export interface ReversiGameEventTypes {
changeReadyStates: {
user1: boolean;
user2: boolean;
};
updateSettings: {
userId: MiUser['id'];
key: string;
value: any;
};
putStone: {
at: number;
color: boolean;
pos: number;
next: boolean;
};
syncState: {
crc32: string;
};
started: {
game: Packed<'ReversiGameDetailed'>;
};
ended: {
winnerId: MiUser['id'] | null;
game: Packed<'ReversiGameDetailed'>;
};
}
//#endregion
// 辞書(interface or type)から{ type, body }ユニオンを定義
@@ -286,14 +249,6 @@ export type GlobalEvents = {
name: 'notesStream';
payload: Serialized<Packed<'Note'>>;
};
reversi: {
name: `reversiStream:${MiUser['id']}`;
payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
};
reversiGame: {
name: `reversiGameStream:${MiReversiGame['id']}`;
payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
};
};
// API event definitions
@@ -383,14 +338,4 @@ export class GlobalEventService {
public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishReversiStream<K extends keyof ReversiEventTypes>(userId: MiUser['id'], type: K, value?: ReversiEventTypes[K]): void {
this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishReversiGameStream<K extends keyof ReversiGameEventTypes>(gameId: MiReversiGame['id'], type: K, value?: ReversiGameEventTypes[K]): void {
this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value);
}
}
@@ -58,7 +58,6 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -325,9 +324,6 @@ export class NoteCreateService implements OnApplicationShutdown {
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
}
data.text = data.text.trim();
if (data.text === '') {
data.text = null;
}
} else {
data.text = null;
}
@@ -680,7 +676,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.relayService.deliverToRelays(user, noteActivity);
}
trackPromise(dm.execute());
dm.execute();
})();
}
//#endregion
+4 -5
View File
@@ -14,7 +14,6 @@ import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { trackPromise } from '@/misc/promise-tracker.js';
@Injectable()
export class NoteReadService implements OnApplicationShutdown {
@@ -108,7 +107,7 @@ export class NoteReadService implements OnApplicationShutdown {
// TODO: ↓まとめてクエリしたい
trackPromise(this.noteUnreadsRepository.countBy({
this.noteUnreadsRepository.countBy({
userId: userId,
isMentioned: true,
}).then(mentionsCount => {
@@ -116,9 +115,9 @@ export class NoteReadService implements OnApplicationShutdown {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
}
}));
});
trackPromise(this.noteUnreadsRepository.countBy({
this.noteUnreadsRepository.countBy({
userId: userId,
isSpecified: true,
}).then(specifiedCount => {
@@ -126,7 +125,7 @@ export class NoteReadService implements OnApplicationShutdown {
// 全て既読になったイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
}
}));
});
}
}
@@ -20,7 +20,6 @@ import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import { UserListService } from '@/core/UserListService.js';
import type { FilterUnionByProperty } from '@/types.js';
import { trackPromise } from '@/misc/promise-tracker.js';
@Injectable()
export class NotificationService implements OnApplicationShutdown {
@@ -75,18 +74,7 @@ export class NotificationService implements OnApplicationShutdown {
}
@bindThis
public createNotification<T extends MiNotification['type']>(
notifieeId: MiUser['id'],
type: T,
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
notifierId?: MiUser['id'] | null,
) {
trackPromise(
this.#createNotificationInternal(notifieeId, type, data, notifierId),
);
}
async #createNotificationInternal<T extends MiNotification['type']>(
public async createNotification<T extends MiNotification['type']>(
notifieeId: MiUser['id'],
type: T,
data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
+3 -3
View File
@@ -212,8 +212,8 @@ export class QueryService {
// または 自分自身
.orWhere('note.userId = :meId')
// または 自分宛て
.orWhere(':meIdAsList <@ note.visibleUserIds')
.orWhere(':meIdAsList <@ note.mentions')
.orWhere(':meId = ANY(note.visibleUserIds)')
.orWhere(':meId = ANY(note.mentions)')
.orWhere(new Brackets(qb => {
qb
// または フォロワー宛ての投稿であり、
@@ -228,7 +228,7 @@ export class QueryService {
}));
}));
q.setParameters({ meId: me.id, meIdAsList: [me.id] });
q.setParameters({ meId: me.id });
}
}
+9 -4
View File
@@ -3,12 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
import * as Bull from 'bullmq';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
import { allSettled } from '@/misc/promise-tracker.js';
import type { Provider } from '@nestjs/common';
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js';
@@ -106,9 +106,14 @@ export class QueueModule implements OnApplicationShutdown {
) {}
public async dispose(): Promise<void> {
// Wait for all potential queue jobs
await allSettled();
// And then close all queues
if (process.env.NODE_ENV === 'test') {
// XXX:
// Shutting down the existing connections causes errors on Jest as
// Misskey has asynchronous postgres/redis connections that are not
// awaited.
// Let's wait for some random time for them to finish.
await setTimeout(5000);
}
await Promise.all([
this.systemQueue.close(),
this.endedPollNotificationQueue.close(),
+2 -20
View File
@@ -16,7 +16,6 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, Obj
import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
@Injectable()
export class QueueService {
@@ -75,15 +74,11 @@ export class QueueService {
if (content == null) return null;
if (to == null) return null;
const contentBody = JSON.stringify(content);
const digest = ApRequestCreator.createDigest(contentBody);
const data: DeliverJobData = {
user: {
id: user.id,
},
content: contentBody,
digest,
content,
to,
isSharedInbox,
};
@@ -108,8 +103,6 @@ export class QueueService {
@bindThis
public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map<string, boolean>) {
if (content == null) return null;
const contentBody = JSON.stringify(content);
const digest = ApRequestCreator.createDigest(contentBody);
const opts = {
attempts: this.config.deliverJobMaxAttempts ?? 12,
@@ -124,8 +117,7 @@ export class QueueService {
name: d[0],
data: {
user,
content: contentBody,
digest,
content,
to: d[0],
isSharedInbox: d[1],
} as DeliverJobData,
@@ -182,16 +174,6 @@ export class QueueService {
});
}
@bindThis
public createExportClipsJob(user: ThinUser) {
return this.dbQueue.add('exportClips', {
user: { id: user.id },
}, {
removeOnComplete: true,
removeOnFail: true,
});
}
@bindThis
public createExportFavoritesJob(user: ThinUser) {
return this.dbQueue.add('exportFavorites', {
+2 -3
View File
@@ -28,7 +28,6 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { RoleService } from '@/core/RoleService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { trackPromise } from '@/misc/promise-tracker.js';
const FALLBACK = '❤';
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
@@ -269,7 +268,7 @@ export class ReactionService {
}
}
trackPromise(dm.execute());
dm.execute();
}
//#endregion
}
@@ -317,7 +316,7 @@ export class ReactionService {
dm.addDirectRecipe(reactee as MiRemoteUser);
}
dm.addFollowersRecipe();
trackPromise(dm.execute());
dm.execute();
}
//#endregion
}
-411
View File
@@ -1,411 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import CRC32 from 'crc-32';
import { ModuleRef } from '@nestjs/core';
import * as Reversi from 'misskey-reversi';
import { IsNull } from 'typeorm';
import type {
MiReversiGame,
ReversiGamesRepository,
UsersRepository,
} from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import type { Packed } from '@/misc/json-schema.js';
import { NotificationService } from '@/core/NotificationService.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec
@Injectable()
export class ReversiService implements OnApplicationShutdown, OnModuleInit {
private notificationService: NotificationService;
constructor(
private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
private cacheService: CacheService,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private reversiGameEntityService: ReversiGameEntityService,
private idService: IdService,
) {
}
async onModuleInit() {
this.notificationService = this.moduleRef.get(NotificationService.name);
}
@bindThis
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
if (targetUser.id === me.id) {
throw new Error('You cannot match yourself.');
}
const invitations = await this.redisClient.zrange(
`reversi:matchSpecific:${me.id}`,
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
if (invitations.includes(targetUser.id)) {
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: targetUser.id,
user2Id: me.id,
user1Ready: false,
user2Ready: false,
isStarted: false,
isEnded: false,
logs: [],
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.reversiGameEntityService.packDetail(game, { id: targetUser.id });
this.globalEventService.publishReversiStream(targetUser.id, 'matched', { game: packed });
return game;
} else {
this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
user: await this.userEntityService.pack(me, targetUser),
});
return null;
}
}
@bindThis
public async matchAnyUser(me: MiUser): Promise<MiReversiGame | null> {
//#region まず自分宛ての招待を探す
const invitations = await this.redisClient.zrange(
`reversi:matchSpecific:${me.id}`,
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
if (invitations.length > 0) {
const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: invitorId,
user2Id: me.id,
user1Ready: false,
user2Ready: false,
isStarted: false,
isEnded: false,
logs: [],
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.reversiGameEntityService.packDetail(game, { id: invitorId });
this.globalEventService.publishReversiStream(invitorId, 'matched', { game: packed });
return game;
}
//#endregion
const matchings = await this.redisClient.zrange(
'reversi:matchAny',
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
const userIds = matchings.filter(id => id !== me.id);
if (userIds.length > 0) {
// pick random
const matchedUserId = userIds[Math.floor(Math.random() * userIds.length)];
await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: matchedUserId,
user2Id: me.id,
user1Ready: false,
user2Ready: false,
isStarted: false,
isEnded: false,
logs: [],
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
const packed = await this.reversiGameEntityService.packDetail(game, { id: matchedUserId });
this.globalEventService.publishReversiStream(matchedUserId, 'matched', { game: packed });
return game;
} else {
await this.redisClient.zadd('reversi:matchAny', Date.now(), me.id);
return null;
}
}
@bindThis
public async matchSpecificUserCancel(user: MiUser, targetUserId: MiUser['id']) {
await this.redisClient.zrem(`reversi:matchSpecific:${targetUserId}`, user.id);
}
@bindThis
public async matchAnyUserCancel(user: MiUser) {
await this.redisClient.zrem('reversi:matchAny', user.id);
}
@bindThis
public async gameReady(game: MiReversiGame, user: MiUser, ready: boolean) {
if (game.isStarted) return;
let isBothReady = false;
if (game.user1Id === user.id) {
await this.reversiGamesRepository.update(game.id, {
user1Ready: ready,
});
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
user1: ready,
user2: game.user2Ready,
});
if (ready && game.user2Ready) isBothReady = true;
} else if (game.user2Id === user.id) {
await this.reversiGamesRepository.update(game.id, {
user2Ready: ready,
});
this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
user1: game.user1Ready,
user2: ready,
});
if (ready && game.user1Ready) isBothReady = true;
} else {
return;
}
if (isBothReady) {
// 3秒後、両者readyならゲーム開始
setTimeout(async () => {
const freshGame = await this.reversiGamesRepository.findOneBy({ id: game.id });
if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return;
if (!freshGame.user1Ready || !freshGame.user2Ready) return;
let bw: number;
if (freshGame.bw === 'random') {
bw = Math.random() > 0.5 ? 1 : 2;
} else {
bw = parseInt(freshGame.bw, 10);
}
function getRandomMap() {
const mapCount = Object.entries(Reversi.maps).length;
const rnd = Math.floor(Math.random() * mapCount);
return Object.values(Reversi.maps)[rnd].data;
}
const map = freshGame.map != null ? freshGame.map : getRandomMap();
await this.reversiGamesRepository.update(game.id, {
startedAt: new Date(),
isStarted: true,
black: bw,
map: map,
});
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
const o = new Reversi.Game(map, {
isLlotheo: freshGame.isLlotheo,
canPutEverywhere: freshGame.canPutEverywhere,
loopedBoard: freshGame.loopedBoard,
});
if (o.isEnded) {
let winner;
if (o.winner === true) {
winner = freshGame.black === 1 ? freshGame.user1Id : freshGame.user2Id;
} else if (o.winner === false) {
winner = freshGame.black === 1 ? freshGame.user2Id : freshGame.user1Id;
} else {
winner = null;
}
await this.reversiGamesRepository.update(game.id, {
isEnded: true,
winnerId: winner,
});
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
winnerId: winner,
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}
//#endregion
this.globalEventService.publishReversiGameStream(game.id, 'started', {
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}, 3000);
}
}
@bindThis
public async getInvitations(user: MiUser): Promise<MiUser['id'][]> {
const invitations = await this.redisClient.zrange(
`reversi:matchSpecific:${user.id}`,
Date.now() - MATCHING_TIMEOUT_MS,
'+inf',
'BYSCORE');
return invitations;
}
@bindThis
public async updateSettings(game: MiReversiGame, user: MiUser, key: string, value: any) {
if (game.isStarted) return;
if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
if ((game.user1Id === user.id) && game.user1Ready) return;
if ((game.user2Id === user.id) && game.user2Ready) return;
if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return;
await this.reversiGamesRepository.update(game.id, {
[key]: value,
});
this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
userId: user.id,
key: key,
value: value,
});
}
@bindThis
public async putStoneToGame(game: MiReversiGame, user: MiUser, pos: number) {
if (!game.isStarted) return;
if (game.isEnded) return;
if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
const myColor =
((game.user1Id === user.id) && game.black === 1) || ((game.user2Id === user.id) && game.black === 2)
? true
: false;
const o = new Reversi.Game(game.map, {
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
});
// 盤面の状態を再生
for (const log of game.logs) {
o.put(log.color, log.pos);
}
if (o.turn !== myColor) return;
if (!o.canPut(myColor, pos)) return;
o.put(myColor, pos);
let winner;
if (o.isEnded) {
if (o.winner === true) {
winner = game.black === 1 ? game.user1Id : game.user2Id;
} else if (o.winner === false) {
winner = game.black === 1 ? game.user2Id : game.user1Id;
} else {
winner = null;
}
}
const log = {
at: Date.now(),
color: myColor,
pos,
};
const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString();
game.logs.push(log);
await this.reversiGamesRepository.update(game.id, {
crc32,
isEnded: o.isEnded,
winnerId: winner,
logs: game.logs,
});
this.globalEventService.publishReversiGameStream(game.id, 'putStone', {
...log,
next: o.turn,
});
if (o.isEnded) {
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
winnerId: winner,
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}
}
@bindThis
public async surrender(game: MiReversiGame, user: MiUser) {
if (game.isEnded) return;
if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
await this.reversiGamesRepository.update(game.id, {
surrendered: user.id,
isEnded: true,
winnerId: winnerId,
});
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
winnerId: winnerId,
game: await this.reversiGameEntityService.packDetail(game.id, user),
});
}
@bindThis
public async get(id: MiReversiGame['id']) {
return this.reversiGamesRepository.findOneBy({ id });
}
@bindThis
public dispose(): void {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}
@@ -58,7 +58,7 @@ export class ApAudienceService {
};
}
if (toGroups.followers.length > 0 || ccGroups.followers.length > 0) {
if (toGroups.followers.length > 0) {
return {
visibility: 'followers',
mentionedUsers,
@@ -144,7 +144,7 @@ class DeliverManager {
}
// deliver
await this.queueService.deliverMany(this.actor, this.activity, inboxes);
this.queueService.deliverMany(this.actor, this.activity, inboxes);
}
}
@@ -97,8 +97,6 @@ export class ApInboxService {
} catch (err) {
if (err instanceof Error || typeof err === 'string') {
this.logger.error(err);
} else {
throw err;
}
}
}
@@ -258,7 +256,7 @@ export class ApInboxService {
const targetUri = getApId(activity.object);
await this.announceNote(actor, activity, targetUri);
this.announceNote(actor, activity, targetUri);
}
@bindThis
@@ -290,7 +288,7 @@ export class ApInboxService {
} catch (err) {
// 対象が4xxならスキップ
if (err instanceof StatusError) {
if (!err.isRetryable) {
if (err.isClientError) {
this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
return;
}
@@ -375,7 +373,7 @@ export class ApInboxService {
});
if (isPost(object)) {
await this.createNote(resolver, actor, object, false, activity);
this.createNote(resolver, actor, object, false, activity);
} else {
this.logger.warn(`Unknown type: ${getApType(object)}`);
}
@@ -406,7 +404,7 @@ export class ApInboxService {
await this.apNoteService.createNote(note, resolver, silent);
return 'ok';
} catch (err) {
if (err instanceof StatusError && !err.isRetryable) {
if (err instanceof StatusError && err.isClientError) {
return `skip ${err.statusCode}`;
} else {
throw err;
@@ -34,9 +34,9 @@ type PrivateKey = {
};
export class ApRequestCreator {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Signed {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);
const digestHeader = args.digest ?? this.createDigest(args.body);
const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`;
const request: Request = {
url: u.href,
@@ -59,10 +59,6 @@ export class ApRequestCreator {
};
}
static createDigest(body: string) {
return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
}
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);
@@ -149,8 +145,8 @@ export class ApRequestService {
}
@bindThis
public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, digest?: string): Promise<void> {
const body = typeof object === 'string' ? object : JSON.stringify(object);
public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown): Promise<void> {
const body = JSON.stringify(object);
const keypair = await this.userKeypairService.getUserKeypair(user.id);
@@ -161,7 +157,6 @@ export class ApRequestService {
},
url,
body,
digest,
additionalHeaders: {
},
});
@@ -216,7 +216,7 @@ export class ApNoteService {
return { status: 'ok', res };
} catch (e) {
return {
status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror',
status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror',
};
}
};
@@ -351,7 +351,6 @@ export class NoteEntityService implements OnModuleInit {
color: channel.color,
isSensitive: channel.isSensitive,
allowRenoteToExternal: channel.allowRenoteToExternal,
userId: channel.userId,
} : undefined,
mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri ?? undefined,
@@ -1,115 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { ReversiGamesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
import type { MiReversiGame } from '@/models/ReversiGame.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class ReversiGameEntityService {
constructor(
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
private userEntityService: UserEntityService,
private idService: IdService,
) {
}
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
me?: { id: MiUser['id'] } | null | undefined,
): Promise<Packed<'ReversiGameDetailed'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Ready: game.user1Ready,
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: this.userEntityService.pack(game.user1Id, me),
user2: this.userEntityService.pack(game.user2Id, me),
winnerId: game.winnerId,
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
logs: game.logs.map(log => ({
at: log.at,
color: log.color,
pos: log.pos,
})),
map: game.map,
});
}
@bindThis
public packDetailMany(
xs: MiReversiGame[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(xs.map(x => this.packDetail(x, me)));
}
@bindThis
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
me?: { id: MiUser['id'] } | null | undefined,
): Promise<Packed<'ReversiGameLite'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Ready: game.user1Ready,
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: this.userEntityService.pack(game.user1Id, me),
user2: this.userEntityService.pack(game.user2Id, me),
winnerId: game.winnerId,
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
});
}
@bindThis
public packLiteMany(
xs: MiReversiGame[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(xs.map(x => this.packLite(x, me)));
}
}
@@ -37,7 +37,7 @@ export class ServerStatsService implements OnApplicationShutdown {
const log = [] as any[];
ev.on('requestServerStatsLog', x => {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length));
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length ?? 50));
});
const tick = async () => {
-2
View File
@@ -78,7 +78,5 @@ export const DI = {
flashsRepository: Symbol('flashsRepository'),
flashLikesRepository: Symbol('flashLikesRepository'),
userMemosRepository: Symbol('userMemosRepository'),
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
reversiGamesRepository: Symbol('reversiGamesRepository'),
//#endregion
};
+2 -5
View File
@@ -71,11 +71,8 @@ export default class Logger {
let log = `${l} ${worker}\t[${contexts.join(' ')}]\t${m}`;
if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log;
const args: unknown[] = [important ? chalk.bold(log) : log];
if (data != null) {
args.push(data);
}
console.log(...args);
console.log(important ? chalk.bold(log) : log);
if (level === 'error' && data) console.log(data);
}
@bindThis
-3
View File
@@ -39,7 +39,6 @@ import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
import { packedAdSchema } from '@/models/json-schema/ad.js';
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -79,8 +78,6 @@ export const refs = {
Signin: packedSigninSchema,
RoleLite: packedRoleLiteSchema,
Role: packedRoleSchema,
ReversiGameLite: packedReversiGameLiteSchema,
ReversiGameDetailed: packedReversiGameDetailedSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
@@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
const promiseRefs: Set<WeakRef<Promise<unknown>>> = new Set();
/**
* This tracks promises that other modules decided not to wait for,
* and makes sure they are all settled before fully closing down the server.
*/
export function trackPromise(promise: Promise<unknown>) {
if (process.env.NODE_ENV !== 'test') {
return;
}
const ref = new WeakRef(promise);
promiseRefs.add(ref);
promise.finally(() => promiseRefs.delete(ref));
}
export async function allSettled(): Promise<void> {
await Promise.allSettled([...promiseRefs].map(r => r.deref()));
}
@@ -7,7 +7,6 @@ export class StatusError extends Error {
public statusCode: number;
public statusMessage?: string;
public isClientError: boolean;
public isRetryable: boolean;
constructor(message: string, statusCode: number, statusMessage?: string) {
super(message);
@@ -15,6 +14,5 @@ export class StatusError extends Error {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500;
this.isRetryable = !this.isClientError || this.statusCode === 429;
}
}
@@ -1,57 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@Entity('bubble_game_record')
export class MiBubbleGameRecord {
@PrimaryColumn(id())
public id: string;
@Index()
@Column({
...id(),
})
public userId: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: MiUser | null;
@Index()
@Column('timestamp with time zone')
public seededAt: Date;
@Column('varchar', {
length: 1024,
})
public seed: string;
@Column('integer')
public gameVersion: number;
@Column('varchar', {
length: 128,
})
public gameMode: string;
@Index()
@Column('integer')
public score: number;
@Column('jsonb', {
default: [],
})
public logs: any[];
@Column('boolean', {
default: false,
})
public isVerified: boolean;
}
+1 -24
View File
@@ -191,29 +191,6 @@ export class MiMeta {
})
public hcaptchaSecretKey: string | null;
@Column('boolean', {
default: false,
})
public enableMcaptcha: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaSitekey: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaSecretKey: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public mcaptchaInstanceUrl: string | null;
@Column('boolean', {
default: false,
})
@@ -490,7 +467,7 @@ export class MiMeta {
nullable: true,
})
public truemailInstance: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
+7 -4
View File
@@ -11,6 +11,9 @@ import { MiChannel } from './Channel.js';
import type { MiDriveFile } from './DriveFile.js';
@Entity('note')
@Index('IDX_NOTE_TAGS', { synchronize: false })
@Index('IDX_NOTE_MENTIONS', { synchronize: false })
@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
export class MiNote {
@PrimaryColumn(id())
public id: string;
@@ -130,7 +133,7 @@ export class MiNote {
})
public url: string | null;
@Index('IDX_NOTE_FILE_IDS', { synchronize: false })
@Index()
@Column({
...id(),
array: true, default: '{}',
@@ -142,14 +145,14 @@ export class MiNote {
})
public attachedFileTypes: string[];
@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
@Index()
@Column({
...id(),
array: true, default: '{}',
})
public visibleUserIds: MiUser['id'][];
@Index('IDX_NOTE_MENTIONS', { synchronize: false })
@Index()
@Column({
...id(),
array: true, default: '{}',
@@ -171,7 +174,7 @@ export class MiNote {
})
public emojis: string[];
@Index('IDX_NOTE_TAGS', { synchronize: false })
@Index()
@Column('varchar', {
length: 128, array: true, default: '{}',
})
@@ -5,7 +5,7 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame } from './_.js';
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@@ -399,18 +399,6 @@ const $userMemosRepository: Provider = {
inject: [DI.db],
};
const $bubbleGameRecordsRepository: Provider = {
provide: DI.bubbleGameRecordsRepository,
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
inject: [DI.db],
};
const $reversiGamesRepository: Provider = {
provide: DI.reversiGamesRepository,
useFactory: (db: DataSource) => db.getRepository(MiReversiGame),
inject: [DI.db],
};
@Module({
imports: [
],
@@ -480,8 +468,6 @@ const $reversiGamesRepository: Provider = {
$flashsRepository,
$flashLikesRepository,
$userMemosRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
],
exports: [
$usersRepository,
@@ -549,8 +535,6 @@ const $reversiGamesRepository: Provider = {
$flashsRepository,
$flashLikesRepository,
$userMemosRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
],
})
export class RepositoryModule {}
-127
View File
@@ -1,127 +0,0 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@Entity('reversi_game')
export class MiReversiGame {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
nullable: true,
comment: 'The started date of the ReversiGame.',
})
public startedAt: Date | null;
@Column(id())
public user1Id: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user1: MiUser | null;
@Column(id())
public user2Id: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user2: MiUser | null;
@Column('boolean', {
default: false,
})
public user1Ready: boolean;
@Column('boolean', {
default: false,
})
public user2Ready: boolean;
/**
* ()
* 1 ... user1
* 2 ... user2
*/
@Column('integer', {
nullable: true,
})
public black: number | null;
@Column('boolean', {
default: false,
})
public isStarted: boolean;
@Column('boolean', {
default: false,
})
public isEnded: boolean;
@Column({
...id(),
nullable: true,
})
public winnerId: MiUser['id'] | null;
@Column({
...id(),
nullable: true,
})
public surrendered: MiUser['id'] | null;
@Column('jsonb', {
default: [],
})
public logs: {
at: number;
color: boolean;
pos: number;
}[];
@Column('varchar', {
array: true, length: 64,
})
public map: string[];
@Column('varchar', {
length: 32,
})
public bw: string;
@Column('boolean', {
default: false,
})
public isLlotheo: boolean;
@Column('boolean', {
default: false,
})
public canPutEverywhere: boolean;
@Column('boolean', {
default: false,
})
public loopedBoard: boolean;
@Column('jsonb', {
nullable: true, default: null,
})
public form1: any | null;
@Column('jsonb', {
nullable: true, default: null,
})
public form2: any | null;
/**
* posを文字列としてすべて連結したもののCRC32値
*/
@Column('varchar', {
length: 32, nullable: true,
})
public crc32: string | null;
}
-7
View File
@@ -68,9 +68,6 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import type { Repository } from 'typeorm';
export {
@@ -139,8 +136,6 @@ export {
MiFlash,
MiFlashLike,
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
};
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
@@ -208,5 +203,3 @@ export type RoleAssignmentsRepository = Repository<MiRoleAssignment>;
export type FlashsRepository = Repository<MiFlash>;
export type FlashLikesRepository = Repository<MiFlashLike>;
export type UserMemoRepository = Repository<MiUserMemo>;
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
export type ReversiGamesRepository = Repository<MiReversiGame>;
@@ -148,10 +148,6 @@ export const packedNoteSchema = {
type: 'boolean',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: true,
},
},
},
localOnly: {
@@ -1,234 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedReversiGameLiteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
startedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
isStarted: {
type: 'boolean',
optional: false, nullable: false,
},
isEnded: {
type: 'boolean',
optional: false, nullable: false,
},
form1: {
type: 'any',
optional: false, nullable: true,
},
form2: {
type: 'any',
optional: false, nullable: true,
},
user1Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user2Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user1Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user2Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user1: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
user2: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
winnerId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
winner: {
type: 'object',
optional: false, nullable: true,
ref: 'User',
},
surrendered: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
black: {
type: 'number',
optional: false, nullable: true,
},
bw: {
type: 'string',
optional: false, nullable: false,
},
isLlotheo: {
type: 'boolean',
optional: false, nullable: false,
},
canPutEverywhere: {
type: 'boolean',
optional: false, nullable: false,
},
loopedBoard: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;
export const packedReversiGameDetailedSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
startedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
isStarted: {
type: 'boolean',
optional: false, nullable: false,
},
isEnded: {
type: 'boolean',
optional: false, nullable: false,
},
form1: {
type: 'any',
optional: false, nullable: true,
},
form2: {
type: 'any',
optional: false, nullable: true,
},
user1Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user2Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user1Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user2Id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user1: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
user2: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
winnerId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
winner: {
type: 'object',
optional: false, nullable: true,
ref: 'User',
},
surrendered: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
black: {
type: 'number',
optional: false, nullable: true,
},
bw: {
type: 'string',
optional: false, nullable: false,
},
isLlotheo: {
type: 'boolean',
optional: false, nullable: false,
},
canPutEverywhere: {
type: 'boolean',
optional: false, nullable: false,
},
loopedBoard: {
type: 'boolean',
optional: false, nullable: false,
},
logs: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
at: {
type: 'number',
optional: false, nullable: false,
},
color: {
type: 'boolean',
optional: false, nullable: false,
},
pos: {
type: 'number',
optional: false, nullable: false,
},
},
},
},
map: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
} as const;
-4
View File
@@ -76,8 +76,6 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js';
@@ -192,8 +190,6 @@ export const entities = [
MiFlash,
MiFlashLike,
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
...charts,
];
@@ -24,7 +24,6 @@ import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmo
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js';
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
@@ -54,7 +53,6 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
DeleteDriveFilesProcessorService,
ExportCustomEmojisProcessorService,
ExportNotesProcessorService,
ExportClipsProcessorService,
ExportFavoritesProcessorService,
ExportFollowingProcessorService,
ExportMutingProcessorService,
@@ -16,7 +16,6 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js';
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
@@ -92,7 +91,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
private exportNotesProcessorService: ExportNotesProcessorService,
private exportClipsProcessorService: ExportClipsProcessorService,
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
private exportFollowingProcessorService: ExportFollowingProcessorService,
private exportMutingProcessorService: ExportMutingProcessorService,
@@ -166,7 +164,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
case 'exportNotes': return this.exportNotesProcessorService.process(job);
case 'exportClips': return this.exportClipsProcessorService.process(job);
case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
case 'exportMuting': return this.exportMutingProcessorService.process(job);
@@ -72,7 +72,7 @@ export class DeliverProcessorService {
}
try {
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
// Update stats
this.federatedInstanceService.fetch(host).then(i => {
@@ -111,7 +111,7 @@ export class DeliverProcessorService {
if (res instanceof StatusError) {
// 4xx
if (!res.isRetryable) {
if (res.isClientError) {
// 相手が閉鎖していることを明示しているため、配送停止する
if (job.data.isSharedInbox && res.statusCode === 410) {
this.federatedInstanceService.fetch(host).then(i => {
@@ -1,206 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { Writable } from 'node:stream';
import { Inject, Injectable, StreamableFile } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { format as dateFormat } from 'date-fns';
import { DI } from '@/di-symbols.js';
import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import { createTemp } from '@/misc/create-temp.js';
import type { MiPoll } from '@/models/Poll.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbJobDataWithUser } from '../types.js';
@Injectable()
export class ExportClipsProcessorService {
private logger: Logger;
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.pollsRepository)
private pollsRepository: PollsRepository,
@Inject(DI.clipsRepository)
private clipsRepository: ClipsRepository,
@Inject(DI.clipNotesRepository)
private clipNotesRepository: ClipNotesRepository,
private driveService: DriveService,
private queueLoggerService: QueueLoggerService,
private idService: IdService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
}
@bindThis
public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
this.logger.info(`Exporting clips of ${job.data.user.id} ...`);
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
if (user == null) {
return;
}
// Create temp file
const [path, cleanup] = await createTemp();
this.logger.info(`Temp file is ${path}`);
try {
const stream = Writable.toWeb(fs.createWriteStream(path, { flags: 'a' }));
const writer = stream.getWriter();
writer.closed.catch(this.logger.error);
await writer.write('[');
await this.processClips(writer, user, job);
await writer.write(']');
await writer.close();
this.logger.succ(`Exported to: ${path}`);
const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
this.logger.succ(`Exported to: ${driveFile.id}`);
} finally {
cleanup();
}
}
async processClips(writer: WritableStreamDefaultWriter, user: MiUser, job: Bull.Job<DbJobDataWithUser>) {
let exportedClipsCount = 0;
let cursor: MiClip['id'] | null = null;
while (true) {
const clips = await this.clipsRepository.find({
where: {
userId: user.id,
...(cursor ? { id: MoreThan(cursor) } : {}),
},
take: 100,
order: {
id: 1,
},
});
if (clips.length === 0) {
job.updateProgress(100);
break;
}
cursor = clips.at(-1)?.id ?? null;
for (const clip of clips) {
// Stringify but remove the last `]}`
const content = JSON.stringify(this.serializeClip(clip)).slice(0, -2);
const isFirst = exportedClipsCount === 0;
await writer.write(isFirst ? content : ',\n' + content);
await this.processClipNotes(writer, clip.id);
await writer.write(']}');
exportedClipsCount++;
}
const total = await this.clipsRepository.countBy({
userId: user.id,
});
job.updateProgress(exportedClipsCount / total);
}
}
async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise<void> {
let exportedClipNotesCount = 0;
let cursor: MiClipNote['id'] | null = null;
while (true) {
const clipNotes = await this.clipNotesRepository.find({
where: {
clipId,
...(cursor ? { id: MoreThan(cursor) } : {}),
},
take: 100,
order: {
id: 1,
},
relations: ['note', 'note.user'],
}) as (MiClipNote & { note: MiNote & { user: MiUser } })[];
if (clipNotes.length === 0) {
break;
}
cursor = clipNotes.at(-1)?.id ?? null;
for (const clipNote of clipNotes) {
let poll: MiPoll | undefined;
if (clipNote.note.hasPoll) {
poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id });
}
const content = JSON.stringify(this.serializeClipNote(clipNote, poll));
const isFirst = exportedClipNotesCount === 0;
await writer.write(isFirst ? content : ',\n' + content);
exportedClipNotesCount++;
}
}
}
private serializeClip(clip: MiClip): Record<string, unknown> {
return {
id: clip.id,
name: clip.name,
description: clip.description,
lastClippedAt: clip.lastClippedAt?.toISOString(),
clipNotes: [],
};
}
private serializeClipNote(clip: MiClipNote & { note: MiNote & { user: MiUser } }, poll: MiPoll | undefined): Record<string, unknown> {
return {
id: clip.id,
createdAt: this.idService.parse(clip.id).date.toISOString(),
note: {
id: clip.note.id,
text: clip.note.text,
createdAt: this.idService.parse(clip.note.id).date.toISOString(),
fileIds: clip.note.fileIds,
replyId: clip.note.replyId,
renoteId: clip.note.renoteId,
poll: poll,
cw: clip.note.cw,
visibility: clip.note.visibility,
visibleUserIds: clip.note.visibleUserIds,
localOnly: clip.note.localOnly,
reactionAcceptance: clip.note.reactionAcceptance,
uri: clip.note.uri,
url: clip.note.url,
user: {
id: clip.note.user.id,
name: clip.note.user.name,
username: clip.note.user.username,
host: clip.note.user.host,
uri: clip.note.user.uri,
},
},
};
}
}
@@ -85,7 +85,7 @@ export class InboxProcessorService {
} catch (err) {
// 対象が4xxならスキップ
if (err instanceof StatusError) {
if (!err.isRetryable) {
if (err.isClientError) {
throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
}
throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`);
@@ -71,7 +71,7 @@ export class WebhookDeliverProcessorService {
if (res instanceof StatusError) {
// 4xx
if (!res.isRetryable) {
if (res.isClientError) {
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
}
+1 -3
View File
@@ -15,9 +15,7 @@ export type DeliverJobData = {
/** Actor */
user: ThinUser;
/** Activity */
content: string;
/** Digest header */
digest: string;
content: unknown;
/** inbox URL to deliver */
to: string;
/** whether it is sharedInbox */
+11 -102
View File
@@ -9,7 +9,7 @@ import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import rename from 'rename';
import sharp from 'sharp';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import { sharpBmp } from 'sharp-read-bmp';
import type { Config } from '@/config.js';
import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
@@ -168,35 +168,11 @@ export class FileServerService {
}
if (!image) {
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
image = {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
};
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
} else {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
if ('pipe' in image.data && typeof image.data.pipe === 'function') {
@@ -227,54 +203,11 @@ export class FileServerService {
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream');
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', filename));
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
}
return fs.createReadStream(file.path);
} else {
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream');
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', file.filename));
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
console.log(end);
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
}
return fs.createReadStream(file.path);
}
} catch (e) {
@@ -407,35 +340,11 @@ export class FileServerService {
}
if (!image) {
if (request.headers.range && file.file && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
image = {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
};
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
} else {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
if ('cleanup' in file) {
+3 -8
View File
@@ -22,13 +22,9 @@ import { SigninApiService } from './api/SigninApiService.js';
import { SigninService } from './api/SigninService.js';
import { SignupApiService } from './api/SignupApiService.js';
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
import { ClientServerService } from './web/ClientServerService.js';
import { FeedService } from './web/FeedService.js';
import { UrlPreviewService } from './web/UrlPreviewService.js';
import { ClientLoggerService } from './web/ClientLoggerService.js';
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
import { MainChannelService } from './api/stream/channels/main.js';
import { AdminChannelService } from './api/stream/channels/admin.js';
import { AntennaChannelService } from './api/stream/channels/antenna.js';
@@ -42,9 +38,10 @@ import { LocalTimelineChannelService } from './api/stream/channels/local-timelin
import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
import { UserListChannelService } from './api/stream/channels/user-list.js';
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
import { ClientLoggerService } from './web/ClientLoggerService.js';
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
import { ReversiChannelService } from './api/stream/channels/reversi.js';
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
@Module({
imports: [
@@ -80,8 +77,6 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js
GlobalTimelineChannelService,
HashtagChannelService,
RoleTimelineChannelService,
ReversiChannelService,
ReversiGameChannelService,
HomeTimelineChannelService,
HybridTimelineChannelService,
LocalTimelineChannelService,
@@ -208,7 +208,6 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
import * as ep___i_exportMute from './endpoints/i/export-mute.js';
import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
import * as ep___i_exportClips from './endpoints/i/export-clips.js';
import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
@@ -364,14 +363,6 @@ import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
import * as ep___retention from './endpoints/retention.js';
import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
import * as ep___reversi_games from './endpoints/reversi/games.js';
import * as ep___reversi_match from './endpoints/reversi/match.js';
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
import { GetterService } from './GetterService.js';
import { ApiLoggerService } from './ApiLoggerService.js';
import type { Provider } from '@nestjs/common';
@@ -578,7 +569,6 @@ const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass:
const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default };
const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default };
const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default };
const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default };
const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default };
const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default };
const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default };
@@ -734,14 +724,6 @@ const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass:
const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default };
const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default };
const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default };
const $reversi_cancelMatch: Provider = { provide: 'ep:reversi/cancel-match', useClass: ep___reversi_cancelMatch.default };
const $reversi_games: Provider = { provide: 'ep:reversi/games', useClass: ep___reversi_games.default };
const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___reversi_match.default };
const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default };
const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
@Module({
imports: [
@@ -952,7 +934,6 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass
$i_exportFollowing,
$i_exportMute,
$i_exportNotes,
$i_exportClips,
$i_exportFavorites,
$i_exportUserLists,
$i_exportAntennas,
@@ -1108,14 +1089,6 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass
$fetchRss,
$fetchExternalResources,
$retention,
$bubbleGame_register,
$bubbleGame_ranking,
$reversi_cancelMatch,
$reversi_games,
$reversi_match,
$reversi_invitations,
$reversi_showGame,
$reversi_surrender,
],
exports: [
$admin_meta,
@@ -1320,7 +1293,6 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass
$i_exportFollowing,
$i_exportMute,
$i_exportNotes,
$i_exportClips,
$i_exportFavorites,
$i_exportUserLists,
$i_exportAntennas,
@@ -1473,14 +1445,6 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass
$fetchRss,
$fetchExternalResources,
$retention,
$bubbleGame_register,
$bubbleGame_ranking,
$reversi_cancelMatch,
$reversi_games,
$reversi_match,
$reversi_invitations,
$reversi_showGame,
$reversi_surrender,
],
})
export class EndpointsModule {}
@@ -65,7 +65,6 @@ export class SignupApiService {
'hcaptcha-response'?: string;
'g-recaptcha-response'?: string;
'turnstile-response'?: string;
'm-captcha-response'?: string;
}
}>,
reply: FastifyReply,
@@ -83,12 +82,6 @@ export class SignupApiService {
});
}
if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
throw new FastifyReplyError(400, err);
});
}
if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
throw new FastifyReplyError(400, err);
+1 -19
View File
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { permissions } from 'misskey-js';
import type { Schema } from '@/misc/json-schema.js';
import { permissions } from 'misskey-js';
import { RolePolicies } from '@/core/RoleService.js';
import * as ep___admin_meta from './endpoints/admin/meta.js';
@@ -209,7 +209,6 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
import * as ep___i_exportMute from './endpoints/i/export-mute.js';
import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
import * as ep___i_exportClips from './endpoints/i/export-clips.js';
import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
@@ -365,14 +364,6 @@ import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
import * as ep___retention from './endpoints/retention.js';
import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
import * as ep___reversi_games from './endpoints/reversi/games.js';
import * as ep___reversi_match from './endpoints/reversi/match.js';
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
const eps = [
['admin/meta', ep___admin_meta],
@@ -577,7 +568,6 @@ const eps = [
['i/export-following', ep___i_exportFollowing],
['i/export-mute', ep___i_exportMute],
['i/export-notes', ep___i_exportNotes],
['i/export-clips', ep___i_exportClips],
['i/export-favorites', ep___i_exportFavorites],
['i/export-user-lists', ep___i_exportUserLists],
['i/export-antennas', ep___i_exportAntennas],
@@ -733,14 +723,6 @@ const eps = [
['fetch-rss', ep___fetchRss],
['fetch-external-resources', ep___fetchExternalResources],
['retention', ep___retention],
['bubble-game/register', ep___bubbleGame_register],
['bubble-game/ranking', ep___bubbleGame_ranking],
['reversi/cancel-match', ep___reversi_cancelMatch],
['reversi/games', ep___reversi_games],
['reversi/match', ep___reversi_match],
['reversi/invitations', ep___reversi_invitations],
['reversi/show-game', ep___reversi_showGame],
['reversi/surrender', ep___reversi_surrender],
];
interface IEndpointMetaBase {
@@ -41,18 +41,6 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
enableMcaptcha: {
type: 'boolean',
optional: false, nullable: false,
},
mcaptchaSiteKey: {
type: 'string',
optional: false, nullable: true,
},
mcaptchaInstanceUrl: {
type: 'string',
optional: false, nullable: true,
},
enableRecaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -175,10 +163,6 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
mcaptchaSecretKey: {
type: 'string',
optional: false, nullable: true,
},
recaptchaSecretKey: {
type: 'string',
optional: false, nullable: true,
@@ -484,9 +468,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,
mcaptchaSiteKey: instance.mcaptchaSitekey,
mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
@@ -517,7 +498,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
sensitiveWords: instance.sensitiveWords,
preservedUsernames: instance.preservedUsernames,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
mcaptchaSecretKey: instance.mcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
sensitiveMediaDetection: instance.sensitiveMediaDetection,
@@ -63,10 +63,6 @@ export const paramDef = {
enableHcaptcha: { type: 'boolean' },
hcaptchaSiteKey: { type: 'string', nullable: true },
hcaptchaSecretKey: { type: 'string', nullable: true },
enableMcaptcha: { type: 'boolean' },
mcaptchaSiteKey: { type: 'string', nullable: true },
mcaptchaInstanceUrl: { type: 'string', nullable: true },
mcaptchaSecretKey: { type: 'string', nullable: true },
enableRecaptcha: { type: 'boolean' },
recaptchaSiteKey: { type: 'string', nullable: true },
recaptchaSecretKey: { type: 'string', nullable: true },
@@ -273,22 +269,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
}
if (ps.enableMcaptcha !== undefined) {
set.enableMcaptcha = ps.enableMcaptcha;
}
if (ps.mcaptchaSiteKey !== undefined) {
set.mcaptchaSitekey = ps.mcaptchaSiteKey;
}
if (ps.mcaptchaInstanceUrl !== undefined) {
set.mcaptchaInstanceUrl = ps.mcaptchaInstanceUrl;
}
if (ps.mcaptchaSecretKey !== undefined) {
set.mcaptchaSecretKey = ps.mcaptchaSecretKey;
}
if (ps.enableRecaptcha !== undefined) {
set.enableRecaptcha = ps.enableRecaptcha;
}
@@ -492,7 +472,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.verifymailAuthKey = ps.verifymailAuthKey;
}
}
if (ps.enableTruemailApi !== undefined) {
set.enableTruemailApi = ps.enableTruemailApi;
}
@@ -14,7 +14,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { IdService } from '@/core/IdService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -93,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
antenna.isActive = true;
antenna.lastUsedAt = new Date();
trackPromise(this.antennasRepository.update(antenna.id, antenna));
this.antennasRepository.update(antenna.id, antenna);
if (needPublishEvent) {
this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
@@ -1,73 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { BubbleGameRecordsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
export const meta = {
allowGet: true,
cacheSec: 60,
errors: {
},
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: { type: 'string', format: 'misskey:id' },
score: { type: 'integer' },
user: { ref: 'UserLite' },
},
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
gameMode: { type: 'string' },
},
required: ['gameMode'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.bubbleGameRecordsRepository)
private bubbleGameRecordsRepository: BubbleGameRecordsRepository,
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps) => {
const records = await this.bubbleGameRecordsRepository.find({
where: {
gameMode: ps.gameMode,
seededAt: MoreThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)),
},
order: {
score: 'DESC',
},
take: 10,
relations: ['user'],
});
const users = await this.userEntityService.packMany(records.map(r => r.user!), null, { detail: false });
return records.map(r => ({
id: r.id,
score: r.score,
user: users.find(u => u.id === r.user!.id),
}));
});
}
}
@@ -1,84 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { IdService } from '@/core/IdService.js';
import type { BubbleGameRecordsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
limit: {
duration: ms('1hour'),
max: 120,
minInterval: ms('30sec'),
},
errors: {
invalidSeed: {
message: 'Provided seed is invalid.',
code: 'INVALID_SEED',
id: 'eb627bc7-574b-4a52-a860-3c3eae772b88',
},
},
res: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
score: { type: 'integer', minimum: 0 },
seed: { type: 'string', minLength: 1, maxLength: 1024 },
logs: { type: 'array' },
gameMode: { type: 'string' },
gameVersion: { type: 'integer' },
},
required: ['score', 'seed', 'logs', 'gameMode', 'gameVersion'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.bubbleGameRecordsRepository)
private bubbleGameRecordsRepository: BubbleGameRecordsRepository,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const seedDate = new Date(parseInt(ps.seed, 10));
const now = new Date();
// シードが未来なのは通常のプレイではありえないので弾く
if (seedDate.getTime() > now.getTime()) {
throw new ApiError(meta.errors.invalidSeed);
}
// シードが古すぎる(5時間以上前)のも弾く
if (seedDate.getTime() < now.getTime() - 1000 * 60 * 60 * 5) {
throw new ApiError(meta.errors.invalidSeed);
}
await this.bubbleGameRecordsRepository.insert({
id: this.idService.gen(now.getTime()),
seed: ps.seed,
seededAt: seedDate,
userId: me.id,
score: ps.score,
logs: ps.logs,
gameMode: ps.gameMode,
gameVersion: ps.gameVersion,
isVerified: false,
});
});
}
}
@@ -36,7 +36,7 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' },
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] },
},
required: [],
} as const;
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
query.andWhere(':file <@ note.fileIds', { file: [file.id] });
query.andWhere(':file = ANY(note.fileIds)', { file: file.id });
const notes = await query.limit(ps.limit).getMany();
@@ -60,7 +60,6 @@ export const paramDef = {
'-firstRetrievedAt',
'+latestRequestReceivedAt',
'-latestRequestReceivedAt',
null,
],
},
},
@@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { safeForSql } from "@/misc/safe-for-sql.js";
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js';
@@ -48,9 +47,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps, me) => {
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
const query = this.usersRepository.createQueryBuilder('user')
.where(':tag <@ user.tags', { tag: [normalizeForSearch(ps.tag)] })
.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) })
.andWhere('user.isSuspended = FALSE');
const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5));
@@ -103,13 +103,13 @@ export const meta = {
items: {
type: 'string',
enum: [
'ble',
'cable',
'hybrid',
'internal',
'nfc',
'smart-card',
'usb',
"ble",
"cable",
"hybrid",
"internal",
"nfc",
"smart-card",
"usb",
],
},
},
@@ -123,8 +123,8 @@ export const meta = {
authenticatorAttachment: {
type: 'string',
enum: [
'cross-platform',
'platform',
"cross-platform",
"platform",
],
},
requireResidentKey: {
@@ -133,9 +133,9 @@ export const meta = {
userVerification: {
type: 'string',
enum: [
'discouraged',
'preferred',
'required',
"discouraged",
"preferred",
"required",
],
},
},
@@ -144,11 +144,10 @@ export const meta = {
type: 'string',
nullable: true,
enum: [
'direct',
'enterprise',
'indirect',
'none',
null,
"direct",
"enterprise",
"indirect",
"none",
],
},
extensions: {
@@ -1,35 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueueService } from '@/core/QueueService.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1day'),
max: 1,
},
} as const;
export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
this.queueService.createExportClipsJob(me);
});
}
}
@@ -108,18 +108,6 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
enableMcaptcha: {
type: 'boolean',
optional: false, nullable: false,
},
mcaptchaSiteKey: {
type: 'string',
optional: false, nullable: true,
},
mcaptchaInstanceUrl: {
type: 'string',
optional: false, nullable: true,
},
enableRecaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -363,9 +351,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableMcaptcha: instance.enableMcaptcha,
mcaptchaSiteKey: instance.mcaptchaSitekey,
mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
@@ -34,10 +34,11 @@ describe('api:notes/create', () => {
.toBe(VALID);
});
test('null post', () => {
expect(v({ text: null }))
.toBe(INVALID);
});
// TODO
//test('null post', () => {
// expect(v({ text: null }))
// .toBe(INVALID);
//});
test('0 characters post', () => {
expect(v({ text: '' }))
@@ -48,11 +49,6 @@ describe('api:notes/create', () => {
expect(v({ text: await tooLong }))
.toBe(INVALID);
});
test('whitespace-only post', () => {
expect(v({ text: ' ' }))
.toBe(INVALID);
});
});
describe('cw', () => {
@@ -172,33 +172,13 @@ export const paramDef = {
},
},
// (re)note with text, files and poll are optional
if: {
properties: {
renoteId: {
type: 'null',
},
fileIds: {
type: 'null',
},
mediaIds: {
type: 'null',
},
poll: {
type: 'null',
},
},
},
then: {
properties: {
text: {
type: 'string',
minLength: 1,
maxLength: MAX_NOTE_TEXT_LENGTH,
pattern: '[^\\s]+',
},
},
required: ['text'],
},
anyOf: [
{ required: ['text'] },
{ required: ['renoteId'] },
{ required: ['fileIds'] },
{ required: ['mediaIds'] },
{ required: ['poll'] },
],
} as const;
@Injectable()
@@ -61,9 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(new Brackets(qb => {
qb // このmeIdAsListパラメータはqueryServiceのgenerateVisibilityQueryでセットされる
.where(':meIdAsList <@ note.mentions')
.orWhere(':meIdAsList <@ note.visibleUserIds');
qb
.where(`'{"${me.id}"}' <@ note.mentions`)
.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
}))
// Avoid scanning primary key index
.orderBy('CONCAT(note.id)', 'DESC')
@@ -87,14 +87,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
try {
if (ps.tag) {
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] });
query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
} else {
query.andWhere(new Brackets(qb => {
for (const tags of ps.query!) {
qb.orWhere(new Brackets(qb => {
for (const tag of tags) {
if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
qb.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(tag)] });
qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
}
}));
}
@@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// Get mutee
const mutee = await this.getterService.getUser(ps.userId).catch(err => {
const mutee = await getterService.getUser(ps.userId).catch(err => {
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw err;
});
@@ -1,44 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiService } from '@/core/ReversiService.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
errors: {
},
res: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id', nullable: true },
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private reversiService: ReversiService,
) {
super(meta, paramDef, async (ps, me) => {
if (ps.userId) {
await this.reversiService.matchSpecificUserCancel(me, ps.userId);
return;
} else {
await this.reversiService.matchAnyUserCancel(me);
}
});
}
}
@@ -1,61 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { DI } from '@/di-symbols.js';
import type { ReversiGamesRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
export const meta = {
requireCredential: false,
res: {
type: 'array',
optional: false, nullable: false,
items: { ref: 'ReversiGameLite' },
},
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
my: { type: 'boolean', default: false },
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
private reversiGameEntityService: ReversiGameEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
.andWhere('game.isStarted = TRUE');
if (ps.my && me) {
query.andWhere(new Brackets(qb => {
qb
.where('game.user1Id = :userId', { userId: me.id })
.orWhere('game.user2Id = :userId', { userId: me.id });
}));
}
const games = await query.take(ps.limit).getMany();
return await this.reversiGameEntityService.packLiteMany(games, me);
});
}
}
@@ -1,39 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ReversiService } from '@/core/ReversiService.js';
export const meta = {
requireCredential: true,
kind: 'read:account',
res: {
type: 'array',
optional: false, nullable: false,
items: { ref: 'UserLite' },
},
} as const;
export const paramDef = {
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private userEntityService: UserEntityService,
private reversiService: ReversiService,
) {
super(meta, paramDef, async (ps, me) => {
const invitations = await this.reversiService.getInvitations(me);
return await this.userEntityService.packMany(invitations, me);
});
}
}
@@ -1,66 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiService } from '@/core/ReversiService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { ApiError } from '../../error.js';
import { GetterService } from '../../GetterService.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
errors: {
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '0b4f0559-b484-4e31-9581-3f73cee89b28',
},
isYourself: {
message: 'Target user is yourself.',
code: 'TARGET_IS_YOURSELF',
id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e',
},
},
res: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id', nullable: true },
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private getterService: GetterService,
private reversiService: ReversiService,
private reversiGameEntityService: ReversiGameEntityService,
) {
super(meta, paramDef, async (ps, me) => {
if (ps.userId === me.id) throw new ApiError(meta.errors.isYourself);
const target = ps.userId ? await this.getterService.getUser(ps.userId).catch(err => {
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw err;
}) : null;
const game = target ? await this.reversiService.matchSpecificUser(me, target) : await this.reversiService.matchAnyUser(me);
if (game == null) return;
return await this.reversiGameEntityService.packDetail(game, me);
});
}
}
@@ -1,54 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiService } from '@/core/ReversiService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { ApiError } from '../../error.js';
export const meta = {
requireCredential: false,
errors: {
noSuchGame: {
message: 'No such game.',
code: 'NO_SUCH_GAME',
id: 'f13a03db-fae1-46c9-87f3-43c8165419e1',
},
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'ReversiGameDetailed',
},
} as const;
export const paramDef = {
type: 'object',
properties: {
gameId: { type: 'string', format: 'misskey:id' },
},
required: ['gameId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private reversiService: ReversiService,
private reversiGameEntityService: ReversiGameEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const game = await this.reversiService.get(ps.gameId);
if (game == null) {
throw new ApiError(meta.errors.noSuchGame);
}
return await this.reversiGameEntityService.packDetail(game, me);
});
}
}
@@ -1,68 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiService } from '@/core/ReversiService.js';
import { ApiError } from '../../error.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
errors: {
noSuchGame: {
message: 'No such game.',
code: 'NO_SUCH_GAME',
id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df',
},
alreadyEnded: {
message: 'That game has already ended.',
code: 'ALREADY_ENDED',
id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d',
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '6e04164b-a992-4c93-8489-2123069973e1',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
gameId: { type: 'string', format: 'misskey:id' },
},
required: ['gameId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private reversiService: ReversiService,
) {
super(meta, paramDef, async (ps, me) => {
const game = await this.reversiService.get(ps.gameId);
if (game == null) {
throw new ApiError(meta.errors.noSuchGame);
}
if (game.isEnded) {
throw new ApiError(meta.errors.alreadyEnded);
}
if ((game.user1Id !== me.id) && (game.user2Id !== me.id)) {
throw new ApiError(meta.errors.accessDenied);
}
await this.reversiService.surrender(game, me);
});
}
}
@@ -10,7 +10,7 @@ import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
export function genOpenapiSpec(config: Config) {
const spec = {
openapi: '3.1.0',
openapi: '3.0.0',
info: {
version: config.version,
@@ -56,7 +56,7 @@ export function genOpenapiSpec(config: Config) {
}
}
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res') : {};
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
@@ -71,7 +71,7 @@ export function genOpenapiSpec(config: Config) {
}
const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param') };
const schema = { ...endpoint.params };
if (endpoint.meta.requireFile) {
schema.properties = {
@@ -210,9 +210,7 @@ export function genOpenapiSpec(config: Config) {
};
spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? {
get: info,
} : {}),
...(endpoint.meta.allowGet ? { get: info } : {}),
post: info,
};
}

Some files were not shown because too many files have changed in this diff Show More