* enhance: pizzaxでstreamingのuser storage updateイベントを監視して更新 (#8095)

* wip

* wip?

* ?

* streamingのuser storage updateイベントを監視して更新

* 必要な時以外はストレージを更新しない

* fix?

* wip

* fix

* fix

* fix pizzax (#8099)

* Update CONTRIBUTING.md

* スコープの判定を厳密に (#8100)

* enhance(client): tweak ui

* enhance(client): tweak ui

* wip (#8101)

* Revert "revert d53795184"

This reverts commit aedbab17cc.

* fix

d53795184c (r62707827)

* update deps

* tweak client

* tweak ui

* lint

* refactor(server): use insert instead of save

* refactor(server): use insert instead of save

* tweak ui

* enhance: 許可されていないファイルタイプでは、オブジェクトストレージのファイル名に拡張子を付与しないように (#8108)

* 許可されていないファイルタイプでは、オブジェクトストレージのファイル名に拡張子を付与しないように

* add comment

* tweak ui

* tweak ui

* tweak ui

* clean up

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* tweak ui

* 非ログイン時にエラーを吐くconsole.logを除去 (#8119)

* refactor(client): use composition api

* clean up

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* clean up

* refactor(client): use composition api

* refactor(client): use composition api

* refactor(client): use composition api

* remove unused components

* bye room

* refactor: Widgetのcomposition api移行 (#8125)

* wip

* wip

* wip

* wip

* wip

* wip

* fix

* fix

* bye chat ui

* wip: migrate paging components to composition api

#7681

* wip: migrate paging components to composition api

* wip: migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* fix

* refactor: Composition APIへ移行 (#8121)

* components/abuse-report-window.vue

* use <script setup>

* ✌️

* components/analog-clock.vue

* wip components/autocomplete.vue

* ✌️

* ✌️

* fix

* wip components/captcha.vue

* clean up

* components/channel-follow-button

* components/channel-preview.vue

* components/core-core.vue

* components/code.vue

* wip components/date-separated-list.vue

* fix

* fix autocomplete.vue

* ✌️

* remove global property

* use <script setup>

* components/dialog.vue

* clena up

* fix dialog.vue

* Resolve https://github.com/misskey-dev/misskey/pull/8121#discussion_r781250966

* fix

* bye reversi

* Fix The unauthenticated git protocol on port 9418 is no longer supported. (#8139)

* feat: multiple emojis editing

* feat: emojis import

* git add忘れ

* Update CHANGELOG.md

* clean up

* refactor

* wip: refactor(client): migrate paging components to composition api

* refactor

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* 🎨

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate paging components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* clean up

* refactor(client): specify global scope

* refactor: disallow some variable names

* refactor: more common name

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

* wip: refactor(client): migrate components to composition api

Fix #8155

* Fix #8151 (#8152)

* refactor

* refactor: APIエンドポイントファイルの定義を良い感じにする (#8154)

* Fix API Schema Error

* Delete SimpleSchema/SimpleObj
and Move schemas to dedicated files

* Userのスキーマを分割してみる

* define packMany type

* add ,

* Ensure enum schema and Make "as const" put once

* test?

* Revert "test?"

This reverts commit 97dc9bfa70851bfb7d1cf38e883f8df20fb78b79.

* Revert "Fix API Schema Error"

This reverts commit 21b6176d974ed8e3eb73723ad21a105c5d297323.

* ✌️

* clean up

* test?

* wip

* wip

* better schema def

* ✌️

* fix

* add minLength property

* wip

* wip

* wip

* anyOf/oneOf/allOfに対応? ~ relation.ts

* refactor!

* Define MinimumSchema

* wip

* wip

* anyOf/oneOf/allOfが動作するようにUnionSchemaTypeを修正

* anyOf/oneOf/allOfが動作するようにUnionSchemaTypeを修正

* Update packages/backend/src/misc/schema.ts

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* fix

* array oneOfをより正確な型に

* array oneOfをより正確な型に

* wip

* ✌️

* なんかもういろいろ

* remove

* very good schema

* api schema

* wip

* refactor: awaitAllの型定義を変えてみる

* fix

* specify types in awaitAll

* specify types in awaitAll

* ✌️

* wip

* ...

* ✌️

* AllowDateはやめておく

* 不必要なoptional: false, nullable: falseを廃止

* Packedが展開されないように

* 続packed

* wip

* define note type

* wip

* UserDetailedをMeDetailedかUserDetailedNotMeかを区別できるように

* wip

* wip

* wip specify user type of other schemas

* ok

* convertSchemaToOpenApiSchemaを改修

* convertSchemaToOpenApiSchemaを改修

* Fix

* fix

* ✌️

* wip

* 分割代入ではなくallOfで定義するように

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* refactor: Composition APIへ移行 (#8138)

* components/drive-file-thumbnail.vue

* components/drive-select-dialog.vue

* components/drive-window.vue

* wip

* wip drive.file.vue, drive.vue

* fix prop

* wip(

* components/drive.folder.vue

* maybe ok

* ✌️

* fix variable

* FIX FOLDER VARIABLE

* components/emoji-picker-dialog.vue

* Hate `$emit`

* hate global property

* components/emoji-picker-window.vue

* components/emoji-picker.section.vue

* fix

* fixx

* wip components/emoji-picker.vue

* fix

* defineExpose

* ユニコード絵文字の型をもっといい感じに

* components/featured-photos.vue

* components/follow-button.vue

* forgot-password.vue

* forgot-password.vue

* 🎨

* fix

* モバイル画面で表示更新直後にヘッダーメニューをタップしてもポップアップにならないようにする (#8160)

* fix #8158

* refactor

* refactor

* refactor(server): use insert instead of save

* feat(server): store mime type of webpublic

* refactor(server): use named export

* fix: proxyでsvgをpngに変換するように (#8106)

* wip

* revert send-drive-file change

* fix

* Update packages/backend/src/server/proxy/proxy-media.ts

Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>

Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>

* send-drive-file svg as png (#8107)

* post-form.vue (#8164)

* feat(server): add more metadata for emoji export

* fix: code url in documentation (#8117)

It seems this was not changed while refactoring the modules apart.

* enhance: Forward report (#8001)

* implement sending AP Flag object

Optionally allow a user to select to forward a report about a remote
user to the other instance. This is added in a backwards-compatible way.

* add locale string

* forward report only for moderators

* add switch to moderator UI to forward report

* fix report note url

* return forwarded status from API

apparently forgot to carry this over from my testing environment

* object in Flag activity has to be an array

For correct interoperability with Pleroma the "object" property of the Flag
activity has to be an array.

This array will in the future also hold the link to respective notes, so it
makes sense to correct this on our side.

* Update get-note-menu.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* enhance: e2eテストをできるだけ改良してみた (#8159)

* update docker image?

* 続

* serial run delete from "${table}" cascade

* use cypress official github action

* refuse install by cypress action

* clean up

* use wait?

* use more wait?

* Revert "use more wait?"

This reverts commit 18d0fcae9c7d8f98a4cafb4a846a031ece57350c.

* Revert "use wait?"

This reverts commit 5aa8feec9cdc3e2f79e566249f0a0eff6c0df6a0.

* fix

* test

* test

* log?

* 握りつぶしてみる

* clean up

* env?

* clean up?

* disable video

* add comment

* remove test

* 成功?

* test browser

* nodeインストール無効化

* node16.13.0-chrome95-ff94

* node.js復活

* ?

* ちょっと戻してみる

* chrome?

* cross browser test2

* --shm-size=2g

* artifact?

* misskey.local?

* firefoxはあきらめる

* not headless?

* oops

* fix

* ??

* test1

* if?

* fail-fast: false

* headless: false

* easy error ignoreing describe

* エラーの解消
とちょっとリファクター

* add browser name to artifact

* Install mplayer for FireFox

* no wait?

* タイムアウトを甘くしてみる

* firefoxをあきらめる(n回目)

* remove timeout setting

* wait復活

* Update basic.js

* Update index.js

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* update deps

* refactor

* fix(#8133): hCaptcha の reCAPTCHA 互換挙動を無効化する (#8135)

* fix(#8133): hCaptcha の reCAPTCHA 互換挙動を無効化する

* Update packages/client/src/components/captcha.vue

* fix: hCaptcha host

Co-authored-by: tamaina <tamaina@hotmail.co.jp>

* update local copy of file when describing (#8131)

* feat: increase files limit for note

#8062

* enhance: convert svg to png of custom emojis

* Update CHANGELOG.md

* update dep

* feat(client): make possible to switch account instantly in post form

* 投稿したらアカウントを元に戻すように

* chore(client): add tooltip

* wip: refactor(client): migrate components to composition api

* chore(client): add #misskey button

* fix(client): タイムラインのkeep-aliveが効かなくなっているのを修正

* NodeInfo にユーザー数と投稿数の情報を追加する (#8126)

* Unifying Misskey-specific IRIs in JSON-LD `@context` Resolve #8116  (#8178)

* Unifying Misskey-specific IRIs in JSON-LD `@context` Resolve #8116

* CHANGELOG

* refactor, enhance: ドライブ引数のオブジェクト化, 追加時のcomment指定 (#8180)

* refactor: ドライブの引数をオブジェクト化する Resolve #8177

* Resolve #8181

* fix

* archivePath

* fix: アップロードエラー時の処理を修正 (#8182)

* アップロードのエラー応答で詰むのを修正

* CHANGELOG

* fix: change keypress to keydown (#8192)

* Update docker-compose.yml (#8163)

Fix sometime es may cannot start
refer:https://m.html.cn/site/111215825993025.html

* disable animations on more transitions (#8112)

* 🎨

* Update CHANGELOG.md

* refactor(backend): use insert instead of save

* Update CONTRIBUTING.md

* enhance: Improve poll-editor UI + composition port (#8186)

* Poll editor UI changes

Use a horizontal layout when possible, wrap to vertical when constrained

* Port poll-editor to composition API

* Fix poll-editor `get` time calcs

* fix

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* refactor

* Update CONTRIBUTING.md

* 🎨

* Update extensions.json

* Fix pop-out bug (#8170)

* refactor: fix type

* refactor(backend): fix type

* refactor(backend): fix type

* New Crowdin updates (#8096)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* enhance: MediaListでは、サーバーで許可された形式しか表示しないように (#8113)

* wip

* fix

* update vue

* Update CHANGELOG.md

* 12.102.0

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: xianon <xianon@hotmail.co.jp>
Co-authored-by: Johann150 <johann.galle@protonmail.com>
Co-authored-by: nullobsi <me@nullob.si>
Co-authored-by: Hyunseung Jeon <dogdriip@gmail.com>
Co-authored-by: 老兄 <lao__xong@outlook.com>
Co-authored-by: Derek <skeh@is.nota.live>
Co-authored-by: Kainoa Kanter <44733677+ThatOneCalculator@users.noreply.github.com>
This commit is contained in:
NullCat
2022-01-27 00:50:28 +09:00
committed by GitHub
parent af6d52e4c8
commit 0fb9a438f9
955 changed files with 16618 additions and 34411 deletions

View File

@ -0,0 +1,13 @@
const { QueryRunner } = require('typeorm');
module.exports = class forwardedReport1637320813000 {
name = 'forwardedReport1637320813000';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "forwarded" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "forwarded"`);
}
};

View File

@ -0,0 +1,15 @@
const { MigrationInterface, QueryRunner } = require("typeorm");
module.exports = class emojiUrl1642611822809 {
name = 'emojiUrl1642611822809'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "url" TO "originalUrl"`);
await queryRunner.query(`ALTER TABLE "emoji" ADD "publicUrl" character varying(512) NOT NULL DEFAULT ''`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "publicUrl"`);
await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "originalUrl" TO "url"`);
}
}

View File

@ -0,0 +1,13 @@
const { MigrationInterface, QueryRunner } = require("typeorm");
module.exports = class driveFileWebpublicType1642613870898 {
name = 'driveFileWebpublicType1642613870898'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "drive_file" ADD "webpublicType" character varying(128)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "webpublicType"`);
}
}

View File

@ -22,87 +22,78 @@
"@sinonjs/fake-timers": "7.1.2",
"@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.5",
"@types/bull": "3.15.7",
"@types/cbor": "6.0.0",
"@types/dateformat": "3.0.1",
"@types/escape-regexp": "0.0.0",
"@types/escape-regexp": "0.0.1",
"@types/glob": "7.2.0",
"@types/is-url": "1.2.30",
"@types/js-yaml": "4.0.4",
"@types/jsdom": "16.2.13",
"@types/js-yaml": "4.0.5",
"@types/jsdom": "16.2.14",
"@types/jsonld": "1.5.6",
"@types/koa": "2.13.4",
"@types/koa-bodyparser": "4.3.3",
"@types/koa-bodyparser": "4.3.5",
"@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.2",
"@types/koa-mount": "4.0.1",
"@types/koa-send": "4.1.3",
"@types/koa-views": "7.0.0",
"@types/koa__cors": "3.0.3",
"@types/koa__cors": "3.1.1",
"@types/koa__multer": "2.0.4",
"@types/koa__router": "8.0.8",
"@types/koa__router": "8.0.11",
"@types/mocha": "8.2.3",
"@types/node": "16.11.7",
"@types/node-fetch": "2.5.12",
"@types/node": "17.0.10",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.4",
"@types/oauth": "0.9.1",
"@types/parse5": "6.0.3",
"@types/portscanner": "2.1.1",
"@types/pug": "2.0.5",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.4.1",
"@types/qrcode": "1.4.2",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.2",
"@types/redis": "2.8.32",
"@types/ratelimiter": "3.4.3",
"@types/redis": "4.0.11",
"@types/rename": "1.0.4",
"@types/request-stats": "3.0.0",
"@types/sanitize-html": "2.5.0",
"@types/sanitize-html": "2.6.2",
"@types/seedrandom": "2.4.28",
"@types/sharp": "0.29.3",
"@types/sharp": "0.29.5",
"@types/sinonjs__fake-timers": "6.0.4",
"@types/speakeasy": "2.0.6",
"@types/speakeasy": "2.0.7",
"@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.2",
"@types/uuid": "8.3.1",
"@types/tmp": "0.2.3",
"@types/uuid": "8.3.4",
"@types/web-push": "3.3.2",
"@types/webpack": "5.28.0",
"@types/webpack-stream": "3.2.12",
"@types/websocket": "1.0.4",
"@types/ws": "8.2.0",
"@typescript-eslint/eslint-plugin": "5.3.1",
"@typescript-eslint/parser": "5.1.0",
"@types/ws": "8.2.2",
"@typescript-eslint/eslint-plugin": "5.10.0",
"@typescript-eslint/parser": "5.10.0",
"abort-controller": "3.0.0",
"archiver": "5.3.0",
"autobind-decorator": "2.4.0",
"autosize": "4.0.4",
"autwh": "0.1.0",
"aws-sdk": "2.1013.0",
"aws-sdk": "2.1061.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.4",
"broadcast-channel": "4.5.0",
"bull": "4.1.0",
"broadcast-channel": "4.9.0",
"bull": "4.2.1",
"cacheable-lookup": "6.0.4",
"cafy": "15.2.1",
"cbor": "8.1.0",
"chalk": "4.1.2",
"chart.js": "3.6.0",
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-zoom": "1.1.1",
"cli-highlight": "2.1.11",
"compare-versions": "3.6.0",
"content-disposition": "0.5.3",
"content-disposition": "0.5.4",
"crc-32": "1.2.0",
"css-loader": "6.5.1",
"cssnano": "5.0.10",
"date-fns": "2.25.0",
"dateformat": "4.5.1",
"deep-email-validator": "0.1.18",
"deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1",
"eslint": "8.2.0",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-vue": "8.0.3",
"eslint": "8.7.0",
"eslint-plugin-import": "2.25.4",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
"file-type": "16.5.3",
@ -110,11 +101,9 @@
"glob": "7.2.0",
"got": "11.8.2",
"hpagent": "0.1.2",
"http-signature": "1.3.5",
"idb-keyval": "5.1.3",
"insert-text-at-cursor": "0.3.0",
"http-signature": "1.3.6",
"ip-cidr": "3.0.4",
"is-svg": "4.3.1",
"is-svg": "4.3.2",
"js-yaml": "4.1.0",
"jsdom": "16.7.0",
"json5": "2.2.0",
@ -131,30 +120,29 @@
"koa-slow": "2.1.0",
"koa-views": "7.0.2",
"langmap": "0.0.16",
"mfm-js": "0.20.0",
"mfm-js": "0.21.0",
"mime-types": "2.1.34",
"misskey-js": "0.0.8",
"misskey-js": "0.0.13",
"mocha": "8.4.0",
"ms": "3.0.0-canary.1",
"multer": "1.4.3",
"multer": "1.4.4",
"nested-property": "4.0.0",
"node-fetch": "2.6.1",
"nodemailer": "6.7.0",
"nodemailer": "6.7.2",
"os-utils": "0.0.14",
"parse5": "6.0.1",
"pg": "8.7.1",
"portscanner": "2.2.0",
"prismjs": "1.25.0",
"private-ip": "2.3.3",
"probe-image-size": "7.2.1",
"probe-image-size": "7.2.2",
"promise-limit": "2.7.0",
"pug": "3.0.2",
"punycode": "2.1.1",
"pureimage": "0.3.5",
"qrcode": "1.4.4",
"pureimage": "0.3.8",
"qrcode": "1.5.0",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.16.0",
"re2": "1.17.3",
"redis": "3.1.2",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
@ -163,9 +151,9 @@
"require-all": "3.0.0",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sanitize-html": "2.5.3",
"sanitize-html": "2.6.1",
"seedrandom": "3.0.5",
"sharp": "0.29.2",
"sharp": "0.29.3",
"speakeasy": "2.0.0",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
@ -179,20 +167,21 @@
"ts-loader": "9.2.6",
"ts-node": "10.4.0",
"tsc-alias": "1.4.1",
"tsconfig-paths": "3.11.0",
"tsconfig-paths": "3.12.0",
"twemoji-parser": "13.1.0",
"typeorm": "0.2.39",
"typescript": "4.4.4",
"typeorm": "0.2.41",
"typescript": "4.5.5",
"ulid": "2.3.0",
"unzipper": "0.10.11",
"uuid": "8.3.2",
"web-push": "3.4.5",
"websocket": "1.0.34",
"ws": "8.2.3",
"ws": "8.4.2",
"xev": "2.0.1"
},
"devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.54",
"@types/fluent-ffmpeg": "2.1.17",
"@redocly/openapi-core": "1.0.0-beta.79",
"@types/fluent-ffmpeg": "2.1.20",
"cross-env": "7.0.3",
"execa": "6.0.0"
}

View File

@ -1,2 +1,47 @@
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
// ブラウザで直接表示することを許可するファイルの種類のリスト
// ここに含まれないものは application/octet-stream としてレスポンスされる
// SVGはXSSを生むので許可しない
export const FILE_TYPE_BROWSERSAFE = [
// Images
'image/png',
'image/gif',
'image/jpeg',
'image/webp',
'image/apng',
'image/bmp',
'image/tiff',
'image/x-icon',
// OggS
'audio/opus',
'video/ogg',
'audio/ogg',
'application/ogg',
// ISO/IEC base media file format
'video/quicktime',
'video/mp4',
'audio/mp4',
'video/x-m4v',
'audio/x-m4a',
'video/3gpp',
'video/3gpp2',
'video/mpeg',
'audio/mpeg',
'video/webm',
'audio/webm',
'audio/aac',
'audio/x-flac',
'audio/vnd.wave',
];
/*
https://github.com/sindresorhus/file-type/blob/main/supported.js
https://github.com/sindresorhus/file-type/blob/main/core.js
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
*/

View File

@ -40,8 +40,6 @@ import { Signin } from '@/models/entities/signin';
import { AuthSession } from '@/models/entities/auth-session';
import { FollowRequest } from '@/models/entities/follow-request';
import { Emoji } from '@/models/entities/emoji';
import { ReversiGame } from '@/models/entities/games/reversi/game';
import { ReversiMatching } from '@/models/entities/games/reversi/matching';
import { UserNotePining } from '@/models/entities/user-note-pining';
import { Poll } from '@/models/entities/poll';
import { UserKeypair } from '@/models/entities/user-keypair';
@ -166,8 +164,6 @@ export const entities = [
AntennaNote,
PromoNote,
PromoRead,
ReversiGame,
ReversiMatching,
Relay,
MutedNote,
Channel,
@ -224,7 +220,9 @@ export async function resetDb() {
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind = 'r'
AND nspname !~ '^pg_toast';`);
await Promise.all(tables.map(t => t.table).map(x => conn.query(`DELETE FROM "${x}" CASCADE`)));
for (const table of tables) {
await conn.query(`DELETE FROM "${table.table}" CASCADE`);
}
};
for (let i = 1; i <= 3; i++) {

View File

@ -1,263 +0,0 @@
import { count, concat } from '@/prelude/array';
// MISSKEY REVERSI ENGINE
/**
* true ... 黒
* false ... 白
*/
export type Color = boolean;
const BLACK = true;
const WHITE = false;
export type MapPixel = 'null' | 'empty';
export type Options = {
isLlotheo: boolean;
canPutEverywhere: boolean;
loopedBoard: boolean;
};
export type Undo = {
/**
* 色
*/
color: Color;
/**
* どこに打ったか
*/
pos: number;
/**
* 反転した石の位置の配列
*/
effects: number[];
/**
* ターン
*/
turn: Color | null;
};
/**
* リバーシエンジン
*/
export default class Reversi {
public map: MapPixel[];
public mapWidth: number;
public mapHeight: number;
public board: (Color | null | undefined)[];
public turn: Color | null = BLACK;
public opts: Options;
public prevPos = -1;
public prevColor: Color | null = null;
private logs: Undo[] = [];
/**
* ゲームを初期化します
*/
constructor(map: string[], opts: Options) {
//#region binds
this.put = this.put.bind(this);
//#endregion
//#region Options
this.opts = opts;
if (this.opts.isLlotheo == null) this.opts.isLlotheo = false;
if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false;
if (this.opts.loopedBoard == null) this.opts.loopedBoard = false;
//#endregion
//#region Parse map data
this.mapWidth = map[0].length;
this.mapHeight = map.length;
const mapData = map.join('');
this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined);
this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null');
//#endregion
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
}
/**
* 黒石の数
*/
public get blackCount() {
return count(BLACK, this.board);
}
/**
* 白石の数
*/
public get whiteCount() {
return count(WHITE, this.board);
}
public transformPosToXy(pos: number): number[] {
const x = pos % this.mapWidth;
const y = Math.floor(pos / this.mapWidth);
return [x, y];
}
public transformXyToPos(x: number, y: number): number {
return x + (y * this.mapWidth);
}
/**
* 指定のマスに石を打ちます
* @param color 石の色
* @param pos 位置
*/
public put(color: Color, pos: number) {
this.prevPos = pos;
this.prevColor = color;
this.board[pos] = color;
// 反転させられる石を取得
const effects = this.effects(color, pos);
// 反転させる
for (const pos of effects) {
this.board[pos] = color;
}
const turn = this.turn;
this.logs.push({
color,
pos,
effects,
turn,
});
this.calcTurn();
}
private calcTurn() {
// ターン計算
this.turn =
this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
this.canPutSomewhere(this.prevColor!) ? this.prevColor :
null;
}
public undo() {
const undo = this.logs.pop()!;
this.prevColor = undo.color;
this.prevPos = undo.pos;
this.board[undo.pos] = null;
for (const pos of undo.effects) {
const color = this.board[pos];
this.board[pos] = !color;
}
this.turn = undo.turn;
}
/**
* 指定した位置のマップデータのマスを取得します
* @param pos 位置
*/
public mapDataGet(pos: number): MapPixel {
const [x, y] = this.transformPosToXy(pos);
return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos];
}
/**
* 打つことができる場所を取得します
*/
public puttablePlaces(color: Color): number[] {
return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
}
/**
* 打つことができる場所があるかどうかを取得します
*/
public canPutSomewhere(color: Color): boolean {
return this.puttablePlaces(color).length > 0;
}
/**
* 指定のマスに石を打つことができるかどうかを取得します
* @param color 自分の色
* @param pos 位置
*/
public canPut(color: Color, pos: number): boolean {
return (
this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
}
/**
* 指定のマスに石を置いた時の、反転させられる石を取得します
* @param color 自分の色
* @param initPos 位置
*/
public effects(color: Color, initPos: number): number[] {
const enemyColor = !color;
const diffVectors: [number, number][] = [
[ 0, -1], // 上
[ +1, -1], // 右上
[ +1, 0], // 右
[ +1, +1], // 右下
[ 0, +1], // 下
[ -1, +1], // 左下
[ -1, 0], // 左
[ -1, -1], // 左上
];
const effectsInLine = ([dx, dy]: [number, number]): number[] => {
const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
let [x, y] = this.transformPosToXy(initPos);
while (true) {
[x, y] = nextPos(x, y);
// 座標が指し示す位置がボード外に出たとき
if (this.opts.loopedBoard && this.transformXyToPos(
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) {
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
return found;
} else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) {
return []; // 挟めないことが確定 (盤面外に到達)
}
const pos = this.transformXyToPos(x, y);
if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
const stone = this.board[pos];
if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
}
};
return concat(diffVectors.map(effectsInLine));
}
/**
* ゲームが終了したか否か
*/
public get isEnded(): boolean {
return this.turn === null;
}
/**
* ゲームの勝者 (null = 引き分け)
*/
public get winner(): Color | null {
return this.isEnded ?
this.blackCount == this.whiteCount ? null :
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
undefined as never;
}
}

View File

@ -1,896 +0,0 @@
/**
* 組み込みマップ定義
*
* データ値:
* (スペース) ... マス無し
* - ... マス
* b ... 初期配置される黒石
* w ... 初期配置される白石
*/
export type Map = {
name?: string;
category?: string;
author?: string;
data: string[];
};
export const fourfour: Map = {
name: '4x4',
category: '4x4',
data: [
'----',
'-wb-',
'-bw-',
'----',
],
};
export const sixsix: Map = {
name: '6x6',
category: '6x6',
data: [
'------',
'------',
'--wb--',
'--bw--',
'------',
'------',
],
};
export const roundedSixsix: Map = {
name: '6x6 rounded',
category: '6x6',
author: 'syuilo',
data: [
' ---- ',
'------',
'--wb--',
'--bw--',
'------',
' ---- ',
],
};
export const roundedSixsix2: Map = {
name: '6x6 rounded 2',
category: '6x6',
author: 'syuilo',
data: [
' -- ',
' ---- ',
'--wb--',
'--bw--',
' ---- ',
' -- ',
],
};
export const eighteight: Map = {
name: '8x8',
category: '8x8',
data: [
'--------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'--------',
],
};
export const eighteightH1: Map = {
name: '8x8 handicap 1',
category: '8x8',
data: [
'b-------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'--------',
],
};
export const eighteightH2: Map = {
name: '8x8 handicap 2',
category: '8x8',
data: [
'b-------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'-------b',
],
};
export const eighteightH3: Map = {
name: '8x8 handicap 3',
category: '8x8',
data: [
'b------b',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'-------b',
],
};
export const eighteightH4: Map = {
name: '8x8 handicap 4',
category: '8x8',
data: [
'b------b',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'b------b',
],
};
export const eighteightH28: Map = {
name: '8x8 handicap 28',
category: '8x8',
data: [
'bbbbbbbb',
'b------b',
'b------b',
'b--wb--b',
'b--bw--b',
'b------b',
'b------b',
'bbbbbbbb',
],
};
export const roundedEighteight: Map = {
name: '8x8 rounded',
category: '8x8',
author: 'syuilo',
data: [
' ------ ',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
' ------ ',
],
};
export const roundedEighteight2: Map = {
name: '8x8 rounded 2',
category: '8x8',
author: 'syuilo',
data: [
' ---- ',
' ------ ',
'--------',
'---wb---',
'---bw---',
'--------',
' ------ ',
' ---- ',
],
};
export const roundedEighteight3: Map = {
name: '8x8 rounded 3',
category: '8x8',
author: 'syuilo',
data: [
' -- ',
' ---- ',
' ------ ',
'---wb---',
'---bw---',
' ------ ',
' ---- ',
' -- ',
],
};
export const eighteightWithNotch: Map = {
name: '8x8 with notch',
category: '8x8',
author: 'syuilo',
data: [
'--- ---',
'--------',
'--------',
' --wb-- ',
' --bw-- ',
'--------',
'--------',
'--- ---',
],
};
export const eighteightWithSomeHoles: Map = {
name: '8x8 with some holes',
category: '8x8',
author: 'syuilo',
data: [
'--- ----',
'----- --',
'-- -----',
'---wb---',
'---bw- -',
' -------',
'--- ----',
'--------',
],
};
export const circle: Map = {
name: 'Circle',
category: '8x8',
author: 'syuilo',
data: [
' -- ',
' ------ ',
' ------ ',
'---wb---',
'---bw---',
' ------ ',
' ------ ',
' -- ',
],
};
export const smile: Map = {
name: 'Smile',
category: '8x8',
author: 'syuilo',
data: [
' ------ ',
'--------',
'-- -- --',
'---wb---',
'-- bw --',
'--- ---',
'--------',
' ------ ',
],
};
export const window: Map = {
name: 'Window',
category: '8x8',
author: 'syuilo',
data: [
'--------',
'- -- -',
'- -- -',
'---wb---',
'---bw---',
'- -- -',
'- -- -',
'--------',
],
};
export const reserved: Map = {
name: 'Reserved',
category: '8x8',
author: 'Aya',
data: [
'w------b',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'b------w',
],
};
export const x: Map = {
name: 'X',
category: '8x8',
author: 'Aya',
data: [
'w------b',
'-w----b-',
'--w--b--',
'---wb---',
'---bw---',
'--b--w--',
'-b----w-',
'b------w',
],
};
export const parallel: Map = {
name: 'Parallel',
category: '8x8',
author: 'Aya',
data: [
'--------',
'--------',
'--------',
'---bb---',
'---ww---',
'--------',
'--------',
'--------',
],
};
export const lackOfBlack: Map = {
name: 'Lack of Black',
category: '8x8',
data: [
'--------',
'--------',
'--------',
'---w----',
'---bw---',
'--------',
'--------',
'--------',
],
};
export const squareParty: Map = {
name: 'Square Party',
category: '8x8',
author: 'syuilo',
data: [
'--------',
'-wwwbbb-',
'-w-wb-b-',
'-wwwbbb-',
'-bbbwww-',
'-b-bw-w-',
'-bbbwww-',
'--------',
],
};
export const minesweeper: Map = {
name: 'Minesweeper',
category: '8x8',
author: 'syuilo',
data: [
'b-b--w-w',
'-w-wb-b-',
'w-b--w-b',
'-b-wb-w-',
'-w-bw-b-',
'b-w--b-w',
'-b-bw-w-',
'w-w--b-b',
],
};
export const tenthtenth: Map = {
name: '10x10',
category: '10x10',
data: [
'----------',
'----------',
'----------',
'----------',
'----wb----',
'----bw----',
'----------',
'----------',
'----------',
'----------',
],
};
export const hole: Map = {
name: 'The Hole',
category: '10x10',
author: 'syuilo',
data: [
'----------',
'----------',
'--wb--wb--',
'--bw--bw--',
'---- ----',
'---- ----',
'--wb--wb--',
'--bw--bw--',
'----------',
'----------',
],
};
export const grid: Map = {
name: 'Grid',
category: '10x10',
author: 'syuilo',
data: [
'----------',
'- - -- - -',
'----------',
'- - -- - -',
'----wb----',
'----bw----',
'- - -- - -',
'----------',
'- - -- - -',
'----------',
],
};
export const cross: Map = {
name: 'Cross',
category: '10x10',
author: 'Aya',
data: [
' ---- ',
' ---- ',
' ---- ',
'----------',
'----wb----',
'----bw----',
'----------',
' ---- ',
' ---- ',
' ---- ',
],
};
export const charX: Map = {
name: 'Char X',
category: '10x10',
author: 'syuilo',
data: [
'--- ---',
'---- ----',
'----------',
' -------- ',
' --wb-- ',
' --bw-- ',
' -------- ',
'----------',
'---- ----',
'--- ---',
],
};
export const charY: Map = {
name: 'Char Y',
category: '10x10',
author: 'syuilo',
data: [
'--- ---',
'---- ----',
'----------',
' -------- ',
' --wb-- ',
' --bw-- ',
' ------ ',
' ------ ',
' ------ ',
' ------ ',
],
};
export const walls: Map = {
name: 'Walls',
category: '10x10',
author: 'Aya',
data: [
' bbbbbbbb ',
'w--------w',
'w--------w',
'w--------w',
'w---wb---w',
'w---bw---w',
'w--------w',
'w--------w',
'w--------w',
' bbbbbbbb ',
],
};
export const cpu: Map = {
name: 'CPU',
category: '10x10',
author: 'syuilo',
data: [
' b b b b ',
'w--------w',
' -------- ',
'w--------w',
' ---wb--- ',
' ---bw--- ',
'w--------w',
' -------- ',
'w--------w',
' b b b b ',
],
};
export const checker: Map = {
name: 'Checker',
category: '10x10',
author: 'Aya',
data: [
'----------',
'----------',
'----------',
'---wbwb---',
'---bwbw---',
'---wbwb---',
'---bwbw---',
'----------',
'----------',
'----------',
],
};
export const japaneseCurry: Map = {
name: 'Japanese curry',
category: '10x10',
author: 'syuilo',
data: [
'w-b-b-b-b-',
'-w-b-b-b-b',
'w-w-b-b-b-',
'-w-w-b-b-b',
'w-w-wwb-b-',
'-w-wbb-b-b',
'w-w-w-b-b-',
'-w-w-w-b-b',
'w-w-w-w-b-',
'-w-w-w-w-b',
],
};
export const mosaic: Map = {
name: 'Mosaic',
category: '10x10',
author: 'syuilo',
data: [
'- - - - - ',
' - - - - -',
'- - - - - ',
' - w w - -',
'- - b b - ',
' - w w - -',
'- - b b - ',
' - - - - -',
'- - - - - ',
' - - - - -',
],
};
export const arena: Map = {
name: 'Arena',
category: '10x10',
author: 'syuilo',
data: [
'- - -- - -',
' - - - - ',
'- ------ -',
' -------- ',
'- --wb-- -',
'- --bw-- -',
' -------- ',
'- ------ -',
' - - - - ',
'- - -- - -',
],
};
export const reactor: Map = {
name: 'Reactor',
category: '10x10',
author: 'syuilo',
data: [
'-w------b-',
'b- - - -w',
'- --wb-- -',
'---b w---',
'- b wb w -',
'- w bw b -',
'---w b---',
'- --bw-- -',
'w- - - -b',
'-b------w-',
],
};
export const sixeight: Map = {
name: '6x8',
category: 'Special',
data: [
'------',
'------',
'------',
'--wb--',
'--bw--',
'------',
'------',
'------',
],
};
export const spark: Map = {
name: 'Spark',
category: 'Special',
author: 'syuilo',
data: [
' - - ',
'----------',
' -------- ',
' -------- ',
' ---wb--- ',
' ---bw--- ',
' -------- ',
' -------- ',
'----------',
' - - ',
],
};
export const islands: Map = {
name: 'Islands',
category: 'Special',
author: 'syuilo',
data: [
'-------- ',
'---wb--- ',
'---bw--- ',
'-------- ',
' - - ',
' - - ',
' --------',
' --------',
' --------',
' --------',
],
};
export const galaxy: Map = {
name: 'Galaxy',
category: 'Special',
author: 'syuilo',
data: [
' ------ ',
' --www--- ',
' ------w--- ',
'---bbb--w---',
'--b---b-w-b-',
'-b--wwb-w-b-',
'-b-w-bww--b-',
'-b-w-b---b--',
'---w--bbb---',
' ---w------ ',
' ---www-- ',
' ------ ',
],
};
export const triangle: Map = {
name: 'Triangle',
category: 'Special',
author: 'syuilo',
data: [
' -- ',
' -- ',
' ---- ',
' ---- ',
' --wb-- ',
' --bw-- ',
' -------- ',
' -------- ',
'----------',
'----------',
],
};
export const iphonex: Map = {
name: 'iPhone X',
category: 'Special',
author: 'syuilo',
data: [
' -- -- ',
'--------',
'--------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'--------',
'--------',
' ------ ',
],
};
export const dealWithIt: Map = {
name: 'Deal with it!',
category: 'Special',
author: 'syuilo',
data: [
'------------',
'--w-b-------',
' --b-w------',
' --w-b---- ',
' ------- ',
],
};
export const experiment: Map = {
name: 'Let\'s experiment',
category: 'Special',
author: 'syuilo',
data: [
' ------------ ',
'------wb------',
'------bw------',
'--------------',
' - - ',
'------ ------',
'bbbbbb wwwwww',
'bbbbbb wwwwww',
'bbbbbb wwwwww',
'bbbbbb wwwwww',
'wwwwww bbbbbb',
],
};
export const bigBoard: Map = {
name: 'Big board',
category: 'Special',
data: [
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'-------wb-------',
'-------bw-------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
],
};
export const twoBoard: Map = {
name: 'Two board',
category: 'Special',
author: 'Aya',
data: [
'-------- --------',
'-------- --------',
'-------- --------',
'---wb--- ---wb---',
'---bw--- ---bw---',
'-------- --------',
'-------- --------',
'-------- --------',
],
};
export const test1: Map = {
name: 'Test1',
category: 'Test',
data: [
'--------',
'---wb---',
'---bw---',
'--------',
],
};
export const test2: Map = {
name: 'Test2',
category: 'Test',
data: [
'------',
'------',
'-b--w-',
'-w--b-',
'-w--b-',
],
};
export const test3: Map = {
name: 'Test3',
category: 'Test',
data: [
'-w-',
'--w',
'w--',
'-w-',
'--w',
'w--',
'-w-',
'--w',
'w--',
'-w-',
'---',
'b--',
],
};
export const test4: Map = {
name: 'Test4',
category: 'Test',
data: [
'-w--b-',
'-w--b-',
'------',
'-w--b-',
'-w--b-',
],
};
// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう
export const test6: Map = {
name: 'Test6',
category: 'Test',
data: [
'--wwwww-',
'wwwwwwww',
'wbbbwbwb',
'wbbbbwbb',
'wbwbbwbb',
'wwbwbbbb',
'--wbbbbb',
'-wwwww--',
],
};
// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう
export const test7: Map = {
name: 'Test7',
category: 'Test',
data: [
'b--w----',
'b-wwww--',
'bwbwwwbb',
'wbwwwwb-',
'wwwwwww-',
'-wwbbwwb',
'--wwww--',
'--wwww--',
],
};
// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう
export const test8: Map = {
name: 'Test8',
category: 'Test',
data: [
'--------',
'-----w--',
'w--www--',
'wwwwww--',
'bbbbwww-',
'wwwwww--',
'--www---',
'--ww----',
],
};

View File

@ -1,18 +0,0 @@
{
"name": "misskey-reversi",
"version": "0.0.5",
"description": "Misskey reversi engine",
"keywords": [
"misskey"
],
"author": "syuilo <i@syuilo.com>",
"license": "MIT",
"repository": "https://github.com/misskey-dev/misskey.git",
"bugs": "https://github.com/misskey-dev/misskey/issues",
"main": "./built/core.js",
"types": "./built/core.d.ts",
"scripts": {
"build": "tsc"
},
"dependencies": {}
}

View File

@ -1,21 +0,0 @@
{
"compilerOptions": {
"noEmitOnError": false,
"noImplicitAny": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
"declaration": true,
"sourceMap": false,
"target": "es2017",
"module": "commonjs",
"removeComments": false,
"noLib": false,
"outDir": "./built",
"rootDir": "./"
},
"compileOnSave": false,
"include": [
"./core.ts"
]
}

View File

@ -1,5 +1,6 @@
/**
* Random avatar generator
* Identicon generator
* https://en.wikipedia.org/wiki/Identicon
*/
import * as p from 'pureimage';
@ -34,9 +35,9 @@ const cellSize = actualSize / n;
const sideN = Math.floor(n / 2);
/**
* Generate buffer of random avatar by seed
* Generate buffer of an identicon by seed
*/
export function genAvatar(seed: string, stream: WriteStream): Promise<void> {
export function genIdenticon(seed: string, stream: WriteStream): Promise<void> {
const rand = gen.create(seed);
const canvas = p.make(size, size);
const ctx = canvas.getContext('2d');

View File

@ -62,7 +62,8 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
if (emoji == null) return null;
const isLocal = emoji.host == null;
const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({ url: emoji.url })}`;
const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため
const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`;
return {
name: emojiName,
@ -116,7 +117,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
}
const _emojis = emojisQuery.length > 0 ? await Emojis.find({
where: emojisQuery,
select: ['name', 'host', 'url'],
select: ['name', 'host', 'originalUrl', 'publicUrl'],
}) : [];
for (const emoji of _emojis) {
cache.set(`${emoji.name} ${emoji.host}`, emoji);

View File

@ -1,32 +1,44 @@
import { SimpleObj, SimpleSchema } from './simple-schema';
import { packedUserSchema } from '@/models/repositories/user';
import { packedNoteSchema } from '@/models/repositories/note';
import { packedUserListSchema } from '@/models/repositories/user-list';
import { packedAppSchema } from '@/models/repositories/app';
import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message';
import { packedNotificationSchema } from '@/models/repositories/notification';
import { packedDriveFileSchema } from '@/models/repositories/drive-file';
import { packedDriveFolderSchema } from '@/models/repositories/drive-folder';
import { packedFollowingSchema } from '@/models/repositories/following';
import { packedMutingSchema } from '@/models/repositories/muting';
import { packedBlockingSchema } from '@/models/repositories/blocking';
import { packedNoteReactionSchema } from '@/models/repositories/note-reaction';
import { packedHashtagSchema } from '@/models/repositories/hashtag';
import { packedPageSchema } from '@/models/repositories/page';
import { packedUserGroupSchema } from '@/models/repositories/user-group';
import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite';
import { packedChannelSchema } from '@/models/repositories/channel';
import { packedAntennaSchema } from '@/models/repositories/antenna';
import { packedClipSchema } from '@/models/repositories/clip';
import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance';
import { packedQueueCountSchema } from '@/models/repositories/queue';
import { packedGalleryPostSchema } from '@/models/repositories/gallery-post';
import { packedEmojiSchema } from '@/models/repositories/emoji';
import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game';
import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching';
import {
packedUserLiteSchema,
packedUserDetailedNotMeOnlySchema,
packedMeDetailedOnlySchema,
packedUserDetailedNotMeSchema,
packedMeDetailedSchema,
packedUserDetailedSchema,
packedUserSchema,
} from '@/models/schema/user';
import { packedNoteSchema } from '@/models/schema/note';
import { packedUserListSchema } from '@/models/schema/user-list';
import { packedAppSchema } from '@/models/schema/app';
import { packedMessagingMessageSchema } from '@/models/schema/messaging-message';
import { packedNotificationSchema } from '@/models/schema/notification';
import { packedDriveFileSchema } from '@/models/schema/drive-file';
import { packedDriveFolderSchema } from '@/models/schema/drive-folder';
import { packedFollowingSchema } from '@/models/schema/following';
import { packedMutingSchema } from '@/models/schema/muting';
import { packedBlockingSchema } from '@/models/schema/blocking';
import { packedNoteReactionSchema } from '@/models/schema/note-reaction';
import { packedHashtagSchema } from '@/models/schema/hashtag';
import { packedPageSchema } from '@/models/schema/page';
import { packedUserGroupSchema } from '@/models/schema/user-group';
import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite';
import { packedChannelSchema } from '@/models/schema/channel';
import { packedAntennaSchema } from '@/models/schema/antenna';
import { packedClipSchema } from '@/models/schema/clip';
import { packedFederationInstanceSchema } from '@/models/schema/federation-instance';
import { packedQueueCountSchema } from '@/models/schema/queue';
import { packedGalleryPostSchema } from '@/models/schema/gallery-post';
import { packedEmojiSchema } from '@/models/schema/emoji';
export const refs = {
UserLite: packedUserLiteSchema,
UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
MeDetailedOnly: packedMeDetailedOnlySchema,
UserDetailedNotMe: packedUserDetailedNotMeSchema,
MeDetailed: packedMeDetailedSchema,
UserDetailed: packedUserDetailedSchema,
User: packedUserSchema,
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
App: packedAppSchema,
@ -49,16 +61,52 @@ export const refs = {
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema,
ReversiGame: packedReversiGameSchema,
ReversiMatching: packedReversiMatchingSchema,
};
export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>;
// Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる
// ObjType<r['properties']>を指定するとなぜか展開されずにPacked<'Hoge'>と表示される
type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<MinimumSchema>; allOf?: ReadonlyArray<MinimumSchema> }> =
r['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> :
r['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<r['oneOf']> :
r['properties'] extends Obj ? ObjType<r['properties']> :
never;
export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>;
export interface Schema extends SimpleSchema {
items?: Schema;
properties?: Obj;
ref?: keyof typeof refs;
type TypeStringef = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
type StringDefToType<T extends TypeStringef> =
T extends 'boolean' ? boolean :
T extends 'number' ? number :
T extends 'string' ? string | Date :
T extends 'array' ? ReadonlyArray<any> :
T extends 'object' ? Record<string, any> :
any;
// https://swagger.io/specification/?sbsearch=optional#schema-object
type OfSchema = {
readonly anyOf?: ReadonlyArray<MinimumSchema>;
readonly oneOf?: ReadonlyArray<MinimumSchema>;
readonly allOf?: ReadonlyArray<MinimumSchema>;
}
export interface MinimumSchema extends OfSchema {
readonly type?: TypeStringef;
readonly nullable?: boolean;
readonly optional?: boolean;
readonly items?: MinimumSchema;
readonly properties?: Obj;
readonly description?: string;
readonly example?: any;
readonly format?: string;
readonly ref?: keyof typeof refs;
readonly enum?: ReadonlyArray<string>;
readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
readonly maxLength?: number;
readonly minLength?: number;
}
export interface Schema extends MinimumSchema {
readonly nullable: boolean;
readonly optional: boolean;
}
type NonUndefinedPropertyNames<T extends Obj> = {
@ -69,22 +117,13 @@ type UndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? K : never
}[keyof T];
type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>;
type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>;
export interface Obj extends SimpleObj { [key: string]: Schema; }
export interface Obj { [key: string]: Schema; }
export type ObjType<s extends Obj> =
{ [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } &
{ [P in keyof OnlyRequired<s>]: SchemaType<s[P]> };
{ -readonly [P in UndefinedPropertyNames<s>]?: SchemaType<s[P]> } &
{ -readonly [P in NonUndefinedPropertyNames<s>]: SchemaType<s[P]> };
// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2
type MyType<T extends Schema> = {
0: any;
1: SchemaType<T>;
}[T extends Schema ? 1 : 0];
type NullOrUndefined<p extends Schema, T> =
type NullOrUndefined<p extends MinimumSchema, T> =
p['nullable'] extends true
? p['optional'] extends true
? (T | null | undefined)
@ -93,15 +132,41 @@ type NullOrUndefined<p extends Schema, T> =
? (T | undefined)
: T;
export type SchemaType<p extends Schema> =
p['type'] extends 'number' ? NullOrUndefined<p, number> :
p['type'] extends 'string' ? NullOrUndefined<p, string> :
p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> :
p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> :
p['type'] extends 'object' ? (
p['ref'] extends keyof typeof refs
? NullOrUndefined<p, Packed<p['ref']>>
: NullOrUndefined<p, ObjType<NonNullable<p['properties']>>>
// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
// 単純にSchemaTypeDef<X>で判定するだけではダメ
type UnionSchemaType<a extends readonly any[], X extends MinimumSchema = a[number]> = X extends any ? SchemaType<X> : never;
type ArrayUnion<T> = T extends any ? Array<T> : never;
export type SchemaTypeDef<p extends MinimumSchema> =
p['type'] extends 'number' ? number :
p['type'] extends 'string' ? (
p['enum'] extends readonly string[] ?
p['enum'][number] :
p['format'] extends 'date-time' ? string : // Dateにする
string
) :
p['type'] extends 'any' ? NullOrUndefined<p, any> :
p['type'] extends 'boolean' ? boolean :
p['type'] extends 'object' ? (
p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
p['properties'] extends NonNullable<Obj> ? ObjType<p['properties']> :
p['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> :
p['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
any
) :
p['type'] extends 'array' ? (
p['items'] extends OfSchema ? (
p['items']['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
p['items']['oneOf'] extends ReadonlyArray<MinimumSchema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
p['items']['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
never
) :
p['items'] extends NonNullable<MinimumSchema> ? SchemaTypeDef<p['items']>[] :
any[]
) :
p['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['oneOf']> :
any;
export type SchemaType<p extends MinimumSchema> = NullOrUndefined<p, SchemaTypeDef<p>>;

View File

@ -1,15 +0,0 @@
export interface SimpleSchema {
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
nullable: boolean;
optional: boolean;
items?: SimpleSchema;
properties?: SimpleObj;
description?: string;
example?: any;
format?: string;
ref?: string;
enum?: string[];
default?: boolean | null;
}
export interface SimpleObj { [key: string]: SimpleSchema; }

View File

@ -51,6 +51,11 @@ export class AbuseUserReport {
})
public resolved: boolean;
@Column('boolean', {
default: false
})
public forwarded: boolean;
@Column('varchar', {
length: 2048,
})

View File

@ -101,6 +101,11 @@ export class DriveFile {
})
public webpublicUrl: string | null;
@Column('varchar', {
length: 128, nullable: true,
})
public webpublicType: string | null;
@Index({ unique: true })
@Column('varchar', {
length: 256, nullable: true,

View File

@ -32,13 +32,19 @@ export class Emoji {
@Column('varchar', {
length: 512,
})
public url: string;
public originalUrl: string;
@Column('varchar', {
length: 512,
})
public publicUrl: string;
@Column('varchar', {
length: 512, nullable: true,
})
public uri: string | null;
// publicUrlの方のtypeが入る
@Column('varchar', {
length: 64, nullable: true,
})

View File

@ -1,133 +0,0 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from '../../user';
import { id } from '../../../id';
@Entity()
export class ReversiGame {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the ReversiGame.',
})
public createdAt: Date;
@Column('timestamp with time zone', {
nullable: true,
comment: 'The started date of the ReversiGame.',
})
public startedAt: Date | null;
@Column(id())
public user1Id: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user1: User | null;
@Column(id())
public user2Id: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user2: User | null;
@Column('boolean', {
default: false,
})
public user1Accepted: boolean;
@Column('boolean', {
default: false,
})
public user2Accepted: 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: User['id'] | null;
@Column({
...id(),
nullable: true,
})
public surrendered: User['id'] | null;
@Column('jsonb', {
default: [],
})
public logs: {
at: Date;
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;
}

View File

@ -1,35 +0,0 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from '../../user';
import { id } from '../../../id';
@Entity()
export class ReversiMatching {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the ReversiMatching.',
})
public createdAt: Date;
@Index()
@Column(id())
public parentId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public parent: User | null;
@Index()
@Column(id())
public childId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public child: User | null;
}

View File

@ -124,6 +124,7 @@ export class UserProfile {
})
public clientData: Record<string, any>;
// TODO: そのうち消す
@Column('jsonb', {
default: {},
comment: 'The room data of the User.',

View File

@ -18,7 +18,6 @@ import { AccessToken } from './entities/access-token';
import { UserNotePining } from './entities/user-note-pining';
import { SigninRepository } from './repositories/signin';
import { MessagingMessageRepository } from './repositories/messaging-message';
import { ReversiGameRepository } from './repositories/games/reversi/game';
import { UserListRepository } from './repositories/user-list';
import { UserListJoining } from './entities/user-list-joining';
import { UserGroupRepository } from './repositories/user-group';
@ -30,7 +29,6 @@ import { BlockingRepository } from './repositories/blocking';
import { NoteReactionRepository } from './repositories/note-reaction';
import { NotificationRepository } from './repositories/notification';
import { NoteFavoriteRepository } from './repositories/note-favorite';
import { ReversiMatchingRepository } from './repositories/games/reversi/matching';
import { UserPublickey } from './entities/user-publickey';
import { UserKeypair } from './entities/user-keypair';
import { AppRepository } from './repositories/app';
@ -107,8 +105,6 @@ export const AuthSessions = getCustomRepository(AuthSessionRepository);
export const AccessTokens = getRepository(AccessToken);
export const Signins = getCustomRepository(SigninRepository);
export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
export const ReversiGames = getCustomRepository(ReversiGameRepository);
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
export const Pages = getCustomRepository(PageRepository);
export const PageLikes = getCustomRepository(PageLikeRepository);
export const GalleryPosts = getCustomRepository(GalleryPostRepository);

View File

@ -27,6 +27,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, {
detail: true,
}) : null,
forwarded: report.forwarded,
});
}

View File

@ -31,94 +31,3 @@ export class AntennaRepository extends Repository<Antenna> {
};
}
}
export const packedAntennaSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
keywords: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
},
excludeKeywords: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
},
src: {
type: 'string' as const,
optional: false as const, nullable: false as const,
enum: ['home', 'all', 'users', 'list', 'group'],
},
userListId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
userGroupId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
users: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
caseSensitive: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
default: false,
},
notify: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
withReplies: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
default: false,
},
withFile: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
hasUnreadNote: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
default: false,
},
},
};

View File

@ -38,38 +38,3 @@ export class AppRepository extends Repository<App> {
};
}
}
export const packedAppSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
callbackUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
permission: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
secret: {
type: 'string' as const,
optional: true as const, nullable: false as const,
},
isAuthorized: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
},
};

View File

@ -30,31 +30,3 @@ export class BlockingRepository extends Repository<Blocking> {
return Promise.all(blockings.map(x => this.pack(x, me)));
}
}
export const packedBlockingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
blockeeId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
blockee: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -40,56 +40,3 @@ export class ChannelRepository extends Repository<Channel> {
};
}
}
export const packedChannelSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
lastNotedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
description: {
type: 'string' as const,
nullable: true as const, optional: false as const,
},
bannerUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: false as const,
},
notesCount: {
type: 'number' as const,
nullable: false as const, optional: false as const,
},
usersCount: {
type: 'number' as const,
nullable: false as const, optional: false as const,
},
isFollowing: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
userId: {
type: 'string' as const,
nullable: true as const, optional: false as const,
format: 'id',
},
},
};

View File

@ -29,42 +29,3 @@ export class ClipRepository extends Repository<Clip> {
}
}
export const packedClipSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
isPublic: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -3,7 +3,7 @@ import { DriveFile } from '@/models/entities/drive-file';
import { Users, DriveFolders } from '../index';
import { User } from '@/models/entities/user';
import { toPuny } from '@/misc/convert-host';
import { awaitAll } from '@/prelude/await-all';
import { awaitAll, Promiseable } from '@/prelude/await-all';
import { Packed } from '@/misc/schema';
import config from '@/config/index';
import { query, appendQuery } from '@/prelude/url';
@ -126,7 +126,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
const meta = await fetchMeta();
return await awaitAll({
return await awaitAll<Packed<'DriveFile'>>({
id: file.id,
createdAt: file.createdAt.toISOString(),
name: file.name,
@ -156,112 +156,3 @@ export class DriveFileRepository extends Repository<DriveFile> {
return items.filter(x => x != null);
}
}
export const packedDriveFileSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'lenna.jpg',
},
type: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'image/jpeg',
},
md5: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'md5',
example: '15eca7fba0480996e2245f5185bf39f2',
},
size: {
type: 'number' as const,
optional: false as const, nullable: false as const,
example: 51469,
},
isSensitive: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
blurhash: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
properties: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
width: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 1280,
},
height: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 720,
},
orientation: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 8,
},
avgColor: {
type: 'string' as const,
optional: true as const, nullable: false as const,
example: 'rgb(40,65,87)',
},
},
},
url: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'url',
},
thumbnailUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'url',
},
comment: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
folderId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
folder: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'DriveFolder' as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'User' as const,
},
},
};

View File

@ -48,44 +48,3 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
});
}
}
export const packedDriveFolderSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
foldersCount: {
type: 'number' as const,
optional: true as const, nullable: false as const,
},
filesCount: {
type: 'number' as const,
optional: true as const, nullable: false as const,
},
parentId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'DriveFolder' as const,
},
},
};

View File

@ -15,7 +15,8 @@ export class EmojiRepository extends Repository<Emoji> {
name: emoji.name,
category: emoji.category,
host: emoji.host,
url: emoji.url,
// || emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl || emoji.originalUrl,
};
}
@ -25,41 +26,3 @@ export class EmojiRepository extends Repository<Emoji> {
return Promise.all(emojis.map(x => this.pack(x)));
}
}
export const packedEmojiSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
aliases: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
category: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
host: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
url: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -1,106 +1,2 @@
import config from '@/config/index';
export const packedFederationInstanceSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
caughtAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
host: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'misskey.example.com',
},
usersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
notesCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
followingCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
followersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
driveUsage: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
driveFiles: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
latestRequestSentAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
lastCommunicatedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
isNotResponding: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
isSuspended: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
softwareName: {
type: 'string' as const,
optional: false as const, nullable: true as const,
example: 'misskey',
},
softwareVersion: {
type: 'string' as const,
optional: false as const, nullable: true as const,
example: config.version,
},
openRegistrations: {
type: 'boolean' as const,
optional: false as const, nullable: true as const,
example: true,
},
name: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
maintainerName: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
maintainerEmail: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
iconUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'url',
},
infoUpdatedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
},
};

View File

@ -84,41 +84,3 @@ export class FollowingRepository extends Repository<Following> {
return Promise.all(followings.map(x => this.pack(x, me, opts)));
}
}
export const packedFollowingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
followeeId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
followee: {
type: 'object' as const,
optional: true as const, nullable: false as const,
ref: 'User' as const,
},
followerId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
follower: {
type: 'object' as const,
optional: true as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -38,74 +38,3 @@ export class GalleryPostRepository extends Repository<GalleryPost> {
return Promise.all(posts.map(x => this.pack(x, me)));
}
}
export const packedGalleryPostSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
title: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
fileIds: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
files: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'DriveFile' as const,
},
},
tags: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
isSensitive: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -1,191 +0,0 @@
import { User } from '@/models/entities/user';
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../../../index';
import { ReversiGame } from '@/models/entities/games/reversi/game';
import { Packed } from '@/misc/schema';
@EntityRepository(ReversiGame)
export class ReversiGameRepository extends Repository<ReversiGame> {
public async pack(
src: ReversiGame['id'] | ReversiGame,
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean
}
): Promise<Packed<'ReversiGame'>> {
const opts = Object.assign({
detail: true,
}, options);
const game = typeof src === 'object' ? src : await this.findOneOrFail(src);
return {
id: game.id,
createdAt: game.createdAt.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Accepted: game.user1Accepted,
user2Accepted: game.user2Accepted,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: await Users.pack(game.user1Id, me),
user2: await Users.pack(game.user2Id, me),
winnerId: game.winnerId,
winner: game.winnerId ? await Users.pack(game.winnerId, me) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
...(opts.detail ? {
logs: game.logs.map(log => ({
at: log.at.toISOString(),
color: log.color,
pos: log.pos,
})),
map: game.map,
} : {}),
};
}
}
export const packedReversiGameSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
startedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
isStarted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
isEnded: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
form1: {
type: 'any' as const,
optional: false as const, nullable: true as const,
},
form2: {
type: 'any' as const,
optional: false as const, nullable: true as const,
},
user1Accepted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
user2Accepted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
user1Id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user2Id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user1: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
user2: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
winnerId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
winner: {
type: 'object' as const,
optional: false as const, nullable: true as const,
ref: 'User' as const,
},
surrendered: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
black: {
type: 'number' as const,
optional: false as const, nullable: true as const,
},
bw: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
isLlotheo: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
canPutEverywhere: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
loopedBoard: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
logs: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: true as const, nullable: false as const,
properties: {
at: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
color: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
pos: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
},
},
},
map: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
},
};

View File

@ -1,69 +0,0 @@
import { EntityRepository, Repository } from 'typeorm';
import { ReversiMatching } from '@/models/entities/games/reversi/matching';
import { Users } from '../../../index';
import { awaitAll } from '@/prelude/await-all';
import { User } from '@/models/entities/user';
import { Packed } from '@/misc/schema';
@EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
public async pack(
src: ReversiMatching['id'] | ReversiMatching,
me: { id: User['id'] }
): Promise<Packed<'ReversiMatching'>> {
const matching = typeof src === 'object' ? src : await this.findOneOrFail(src);
return await awaitAll({
id: matching.id,
createdAt: matching.createdAt.toISOString(),
parentId: matching.parentId,
parent: Users.pack(matching.parentId, me, {
detail: true,
}),
childId: matching.childId,
child: Users.pack(matching.childId, me, {
detail: true,
}),
});
}
}
export const packedReversiMatchingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
parentId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object' as const,
optional: false as const, nullable: true as const,
ref: 'User' as const,
},
childId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
child: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -24,39 +24,3 @@ export class HashtagRepository extends Repository<Hashtag> {
return Promise.all(hashtags.map(x => this.pack(x)));
}
}
export const packedHashtagSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
tag: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'misskey',
},
mentionedUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
mentionedLocalUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
mentionedRemoteUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
attachedUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
attachedLocalUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
attachedRemoteUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -42,78 +42,3 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
};
}
}
export const packedMessagingMessageSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: true as const, nullable: false as const,
},
text: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
fileId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
},
file: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'DriveFile' as const,
},
recipientId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
recipient: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'User' as const,
},
groupId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
group: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'UserGroup' as const,
},
isRead: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
reads: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
},
};

View File

@ -30,31 +30,3 @@ export class MutingRepository extends Repository<Muting> {
return Promise.all(mutings.map(x => this.pack(x, me)));
}
}
export const packedMutingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
muteeId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
mutee: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -26,31 +26,3 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
return Promise.all(favorites.map(x => this.pack(x, me)));
}
}
export const packedNoteFavoriteSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
note: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Note' as const,
},
noteId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
};

View File

@ -31,30 +31,3 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
};
}
}
export const packedNoteReactionSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
user: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
type: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -218,7 +218,7 @@ export class NoteRepository extends Repository<Note> {
const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, ''));
const packed = await awaitAll({
const packed: Packed<'Note'> = await awaitAll({
id: note.id,
createdAt: note.createdAt.toISOString(),
userId: note.userId,
@ -320,188 +320,3 @@ export class NoteRepository extends Repository<Note> {
})));
}
}
export const packedNoteSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
text: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
cw: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
replyId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
renoteId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
reply: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'Note' as const,
},
renote: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'Note' as const,
},
isHidden: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
visibility: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
mentions: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
visibleUserIds: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
fileIds: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
files: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'DriveFile' as const,
},
},
tags: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
poll: {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
channelId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
channel: {
type: 'object' as const,
optional: true as const, nullable: true as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
},
},
},
localOnly: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
emojis: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
url: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
},
},
},
reactions: {
type: 'object' as const,
optional: false as const, nullable: false as const,
},
renoteCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
repliesCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
uri: {
type: 'string' as const,
optional: true as const, nullable: false as const,
},
url: {
type: 'string' as const,
optional: true as const, nullable: false as const,
},
myReaction: {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
},
};

View File

@ -107,69 +107,3 @@ export class NotificationRepository extends Repository<Notification> {
})));
}
}
export const packedNotificationSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
isRead: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
type: {
type: 'string' as const,
optional: false as const, nullable: false as const,
enum: [...notificationTypes],
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: true as const, nullable: true as const,
},
userId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
},
note: {
type: 'object' as const,
ref: 'Note' as const,
optional: true as const, nullable: true as const,
},
reaction: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
choice: {
type: 'number' as const,
optional: true as const, nullable: true as const,
},
invitation: {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
body: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
header: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
icon: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
},
};

View File

@ -87,56 +87,3 @@ export class PageRepository extends Repository<Page> {
return Promise.all(pages.map(x => this.pack(x, me)));
}
}
export const packedPageSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
title: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
summary: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
content: {
type: 'array' as const,
optional: false as const, nullable: false as const,
},
variables: {
type: 'array' as const,
optional: false as const, nullable: false as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -1,30 +0,0 @@
export const packedQueueCountSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
waiting: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
active: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
completed: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
failed: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
delayed: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
paused: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -23,39 +23,3 @@ export class UserGroupRepository extends Repository<UserGroup> {
};
}
}
export const packedUserGroupSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
ownerId: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
userIds: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
},
};

View File

@ -22,34 +22,3 @@ export class UserListRepository extends Repository<UserList> {
};
}
}
export const packedUserListSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
userIds: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
},
};

View File

@ -4,11 +4,19 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user';
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index';
import config from '@/config/index';
import { Packed } from '@/misc/schema';
import { awaitAll } from '@/prelude/await-all';
import { awaitAll, Promiseable } from '@/prelude/await-all';
import { populateEmojis } from '@/misc/populate-emojis';
import { getAntennas } from '@/misc/antenna-cache';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const';
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
Detailed extends true ?
ExpectsMe extends true ? Packed<'MeDetailed'> :
ExpectsMe extends false ? Packed<'UserDetailedNotMe'> :
Packed<'UserDetailed'> :
Packed<'UserLite'>;
@EntityRepository(User)
export class UserRepository extends Repository<User> {
public async getRelation(me: User['id'], target: User['id']) {
@ -144,7 +152,7 @@ export class UserRepository extends Repository<User> {
return count > 0;
}
public getOnlineStatus(user: User): string {
public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
if (user.hideOnlineStatus) return 'unknown';
if (user.lastActiveDate == null) return 'unknown';
const elapsed = Date.now() - user.lastActiveDate.getTime();
@ -159,18 +167,18 @@ export class UserRepository extends Repository<User> {
if (user.avatarUrl) {
return user.avatarUrl;
} else {
return `${config.url}/random-avatar/${user.id}`;
return `${config.url}/identicon/${user.id}`;
}
}
public async pack(
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User,
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean,
detail?: D,
includeSecrets?: boolean,
}
): Promise<Packed<'User'>> {
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
const opts = Object.assign({
detail: false,
includeSecrets: false,
@ -178,8 +186,9 @@ export class UserRepository extends Repository<User> {
const user = typeof src === 'object' ? src : await this.findOneOrFail(src);
const meId = me ? me.id : null;
const isMe = meId === user.id;
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin')
.where('pin.userId = :userId', { userId: user.id })
.innerJoinAndSelect('pin.note', 'note')
@ -188,12 +197,12 @@ export class UserRepository extends Repository<User> {
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null;
const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount :
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null;
const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount :
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;
@ -227,12 +236,11 @@ export class UserRepository extends Repository<User> {
uri: user.uri,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
lastFetchedAt: user.lastFetchedAt?.toISOString(),
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash,
bannerColor: null, // 後方互換性のため
isLocked: user.isLocked,
isModerator: user.isModerator || falsy,
isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy,
description: profile!.description,
@ -260,7 +268,7 @@ export class UserRepository extends Repository<User> {
: false,
} : {}),
...(opts.detail && meId === user.id ? {
...(opts.detail && isMe ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
injectFeaturedNote: profile!.injectFeaturedNote,
@ -315,19 +323,19 @@ export class UserRepository extends Repository<User> {
isBlocked: relation.isBlocked,
isMuted: relation.isMuted,
} : {}),
};
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
return await awaitAll(packed);
}
public packMany(
public packMany<D extends boolean = false>(
users: (User['id'] | User)[],
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean,
detail?: D,
includeSecrets?: boolean,
}
) {
): Promise<IsUserDetailed<D>[]> {
return Promise.all(users.map(u => this.pack(u, me, options)));
}
@ -352,313 +360,3 @@ export class UserRepository extends Repository<User> {
public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/);
//#endregion
}
export const packedUserSchema = {
type: 'object' as const,
nullable: false as const, optional: false as const,
properties: {
id: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
name: {
type: 'string' as const,
nullable: true as const, optional: false as const,
example: '藍',
},
username: {
type: 'string' as const,
nullable: false as const, optional: false as const,
example: 'ai',
},
host: {
type: 'string' as const,
nullable: true as const, optional: false as const,
example: 'misskey.example.com',
},
avatarUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: false as const,
},
avatarBlurhash: {
type: 'any' as const,
nullable: true as const, optional: false as const,
},
avatarColor: {
type: 'any' as const,
nullable: true as const, optional: false as const,
default: null,
},
isAdmin: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
isModerator: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
isBot: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
isCat: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
emojis: {
type: 'array' as const,
nullable: false as const, optional: false as const,
items: {
type: 'object' as const,
nullable: false as const, optional: false as const,
properties: {
name: {
type: 'string' as const,
nullable: false as const, optional: false as const,
},
url: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'url',
},
},
},
},
url: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: true as const,
},
createdAt: {
type: 'string' as const,
nullable: false as const, optional: true as const,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
nullable: true as const, optional: true as const,
format: 'date-time',
},
bannerUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: true as const,
},
bannerBlurhash: {
type: 'any' as const,
nullable: true as const, optional: true as const,
},
bannerColor: {
type: 'any' as const,
nullable: true as const, optional: true as const,
default: null,
},
isLocked: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
isSuspended: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
example: false,
},
description: {
type: 'string' as const,
nullable: true as const, optional: true as const,
example: 'Hi masters, I am Ai!',
},
location: {
type: 'string' as const,
nullable: true as const, optional: true as const,
},
birthday: {
type: 'string' as const,
nullable: true as const, optional: true as const,
example: '2018-03-12',
},
fields: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'object' as const,
nullable: false as const, optional: false as const,
properties: {
name: {
type: 'string' as const,
nullable: false as const, optional: false as const,
},
value: {
type: 'string' as const,
nullable: false as const, optional: false as const,
},
},
maxLength: 4,
},
},
followersCount: {
type: 'number' as const,
nullable: false as const, optional: true as const,
},
followingCount: {
type: 'number' as const,
nullable: false as const, optional: true as const,
},
notesCount: {
type: 'number' as const,
nullable: false as const, optional: true as const,
},
pinnedNoteIds: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
pinnedNotes: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'object' as const,
nullable: false as const, optional: false as const,
ref: 'Note' as const,
},
},
pinnedPageId: {
type: 'string' as const,
nullable: true as const, optional: true as const,
},
pinnedPage: {
type: 'object' as const,
nullable: true as const, optional: true as const,
ref: 'Page' as const,
},
twoFactorEnabled: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
usePasswordLessLogin: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
securityKeys: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
avatarId: {
type: 'string' as const,
nullable: true as const, optional: true as const,
format: 'id',
},
bannerId: {
type: 'string' as const,
nullable: true as const, optional: true as const,
format: 'id',
},
autoWatch: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
injectFeaturedNote: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
alwaysMarkNsfw: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
carefulBot: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
autoAcceptFollowed: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadSpecifiedNotes: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadMentions: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadAnnouncement: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadAntenna: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadChannel: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadMessagingMessage: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadNotification: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasPendingReceivedFollowRequest: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
integrations: {
type: 'object' as const,
nullable: false as const, optional: true as const,
},
mutedWords: {
type: 'array' as const,
nullable: false as const, optional: true as const,
},
mutedInstances: {
type: 'array' as const,
nullable: false as const, optional: true as const,
},
mutingNotificationTypes: {
type: 'array' as const,
nullable: false as const, optional: true as const,
},
isFollowing: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
hasPendingFollowRequestFromYou: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
hasPendingFollowRequestToYou: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isFollowed: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isBlocking: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isBlocked: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isMuted: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
},
};

View File

@ -0,0 +1,89 @@
export const packedAntennaSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
keywords: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
excludeKeywords: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
src: {
type: 'string',
optional: false, nullable: false,
enum: ['home', 'all', 'users', 'list', 'group'],
},
userListId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
userGroupId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
users: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
caseSensitive: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
notify: {
type: 'boolean',
optional: false, nullable: false,
},
withReplies: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
withFile: {
type: 'boolean',
optional: false, nullable: false,
},
hasUnreadNote: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
},
} as const;

View File

@ -0,0 +1,33 @@
export const packedAppSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
callbackUrl: {
type: 'string',
optional: false, nullable: true,
},
permission: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
secret: {
type: 'string',
optional: true, nullable: false,
},
isAuthorized: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,26 @@
export const packedBlockingSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
blockeeId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
blockee: {
type: 'object',
optional: false, nullable: false,
ref: 'UserDetailed',
},
},
} as const;

View File

@ -0,0 +1,51 @@
export const packedChannelSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
lastNotedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
nullable: true, optional: false,
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
usersCount: {
type: 'number',
nullable: false, optional: false,
},
isFollowing: {
type: 'boolean',
optional: true, nullable: false,
},
userId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
},
} as const;

View File

@ -0,0 +1,38 @@
export const packedClipSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
isPublic: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,107 @@
export const packedDriveFileSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
example: 'lenna.jpg',
},
type: {
type: 'string',
optional: false, nullable: false,
example: 'image/jpeg',
},
md5: {
type: 'string',
optional: false, nullable: false,
format: 'md5',
example: '15eca7fba0480996e2245f5185bf39f2',
},
size: {
type: 'number',
optional: false, nullable: false,
example: 51469,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
blurhash: {
type: 'string',
optional: false, nullable: true,
},
properties: {
type: 'object',
optional: false, nullable: false,
properties: {
width: {
type: 'number',
optional: true, nullable: false,
example: 1280,
},
height: {
type: 'number',
optional: true, nullable: false,
example: 720,
},
orientation: {
type: 'number',
optional: true, nullable: false,
example: 8,
},
avgColor: {
type: 'string',
optional: true, nullable: false,
example: 'rgb(40,65,87)',
},
},
},
url: {
type: 'string',
optional: false, nullable: true,
format: 'url',
},
thumbnailUrl: {
type: 'string',
optional: false, nullable: true,
format: 'url',
},
comment: {
type: 'string',
optional: false, nullable: true,
},
folderId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
folder: {
type: 'object',
optional: true, nullable: true,
ref: 'DriveFolder',
},
userId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
user: {
type: 'object',
optional: true, nullable: true,
ref: 'UserLite',
},
},
} as const;

View File

@ -0,0 +1,39 @@
export const packedDriveFolderSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
foldersCount: {
type: 'number',
optional: true, nullable: false,
},
filesCount: {
type: 'number',
optional: true, nullable: false,
},
parentId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object',
optional: true, nullable: true,
ref: 'DriveFolder',
},
},
} as const;

View File

@ -0,0 +1,36 @@
export const packedEmojiSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
name: {
type: 'string',
optional: false, nullable: false,
},
category: {
type: 'string',
optional: false, nullable: true,
},
host: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,105 @@
import config from "@/config";
export const packedFederationInstanceSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
caughtAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
host: {
type: 'string',
optional: false, nullable: false,
example: 'misskey.example.com',
},
usersCount: {
type: 'number',
optional: false, nullable: false,
},
notesCount: {
type: 'number',
optional: false, nullable: false,
},
followingCount: {
type: 'number',
optional: false, nullable: false,
},
followersCount: {
type: 'number',
optional: false, nullable: false,
},
driveUsage: {
type: 'number',
optional: false, nullable: false,
},
driveFiles: {
type: 'number',
optional: false, nullable: false,
},
latestRequestSentAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
lastCommunicatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
isNotResponding: {
type: 'boolean',
optional: false, nullable: false,
},
isSuspended: {
type: 'boolean',
optional: false, nullable: false,
},
softwareName: {
type: 'string',
optional: false, nullable: true,
example: 'misskey',
},
softwareVersion: {
type: 'string',
optional: false, nullable: true,
example: config.version,
},
openRegistrations: {
type: 'boolean',
optional: false, nullable: true,
example: true,
},
name: {
type: 'string',
optional: false, nullable: true,
},
description: {
type: 'string',
optional: false, nullable: true,
},
maintainerName: {
type: 'string',
optional: false, nullable: true,
},
maintainerEmail: {
type: 'string',
optional: false, nullable: true,
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
format: 'url',
},
infoUpdatedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
},
} as const;

View File

@ -0,0 +1,36 @@
export const packedFollowingSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
followeeId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
followee: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
followerId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
follower: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
},
} as const;

View File

@ -0,0 +1,69 @@
export const packedGalleryPostSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
title: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
fileIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
files: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
tags: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,34 @@
export const packedHashtagSchema = {
type: 'object',
properties: {
tag: {
type: 'string',
optional: false, nullable: false,
example: 'misskey',
},
mentionedUsersCount: {
type: 'number',
optional: false, nullable: false,
},
mentionedLocalUsersCount: {
type: 'number',
optional: false, nullable: false,
},
mentionedRemoteUsersCount: {
type: 'number',
optional: false, nullable: false,
},
attachedUsersCount: {
type: 'number',
optional: false, nullable: false,
},
attachedLocalUsersCount: {
type: 'number',
optional: false, nullable: false,
},
attachedRemoteUsersCount: {
type: 'number',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,73 @@
export const packedMessagingMessageSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: true, nullable: false,
},
text: {
type: 'string',
optional: false, nullable: true,
},
fileId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
},
file: {
type: 'object',
optional: true, nullable: true,
ref: 'DriveFile',
},
recipientId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
recipient: {
type: 'object',
optional: true, nullable: true,
ref: 'UserLite',
},
groupId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
group: {
type: 'object',
optional: true, nullable: true,
ref: 'UserGroup',
},
isRead: {
type: 'boolean',
optional: true, nullable: false,
},
reads: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
},
} as const;

View File

@ -0,0 +1,26 @@
export const packedMutingSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
muteeId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
mutee: {
type: 'object',
optional: false, nullable: false,
ref: 'UserDetailed',
},
},
} as const;

View File

@ -0,0 +1,26 @@
export const packedNoteFavoriteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
note: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
noteId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
} as const;

View File

@ -0,0 +1,25 @@
export const packedNoteReactionSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
user: {
type: 'object',
optional: false, nullable: false,
ref: 'UserLite',
},
type: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,183 @@
export const packedNoteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
text: {
type: 'string',
optional: false, nullable: true,
},
cw: {
type: 'string',
optional: true, nullable: true,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
replyId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
renoteId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
reply: {
type: 'object',
optional: true, nullable: true,
ref: 'Note',
},
renote: {
type: 'object',
optional: true, nullable: true,
ref: 'Note',
},
isHidden: {
type: 'boolean',
optional: true, nullable: false,
},
visibility: {
type: 'string',
optional: false, nullable: false,
},
mentions: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
visibleUserIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
fileIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
files: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
tags: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
poll: {
type: 'object',
optional: true, nullable: true,
},
channelId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
channel: {
type: 'object',
optional: true, nullable: true,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: true,
},
},
},
},
localOnly: {
type: 'boolean',
optional: true, nullable: false,
},
emojis: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
name: {
type: 'string',
optional: false, nullable: false,
},
url: {
type: 'string',
optional: false, nullable: true,
},
},
},
},
reactions: {
type: 'object',
optional: false, nullable: false,
},
renoteCount: {
type: 'number',
optional: false, nullable: false,
},
repliesCount: {
type: 'number',
optional: false, nullable: false,
},
uri: {
type: 'string',
optional: true, nullable: false,
},
url: {
type: 'string',
optional: true, nullable: false,
},
myReaction: {
type: 'object',
optional: true, nullable: true,
},
},
} as const;

View File

@ -0,0 +1,66 @@
import { notificationTypes } from "@/types";
export const packedNotificationSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
isRead: {
type: 'boolean',
optional: false, nullable: false,
},
type: {
type: 'string',
optional: false, nullable: false,
enum: [...notificationTypes],
},
user: {
type: 'object',
ref: 'UserLite',
optional: true, nullable: true,
},
userId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: true, nullable: true,
},
reaction: {
type: 'string',
optional: true, nullable: true,
},
choice: {
type: 'number',
optional: true, nullable: true,
},
invitation: {
type: 'object',
optional: true, nullable: true,
},
body: {
type: 'string',
optional: true, nullable: true,
},
header: {
type: 'string',
optional: true, nullable: true,
},
icon: {
type: 'string',
optional: true, nullable: true,
},
},
} as const;

View File

@ -0,0 +1,51 @@
export const packedPageSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
title: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
summary: {
type: 'string',
optional: false, nullable: true,
},
content: {
type: 'array',
optional: false, nullable: false,
},
variables: {
type: 'array',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,25 @@
export const packedQueueCountSchema = {
type: 'object',
properties: {
waiting: {
type: 'number',
optional: false, nullable: false,
},
active: {
type: 'number',
optional: false, nullable: false,
},
completed: {
type: 'number',
optional: false, nullable: false,
},
failed: {
type: 'number',
optional: false, nullable: false,
},
delayed: {
type: 'number',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,34 @@
export const packedUserGroupSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
ownerId: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
userIds: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
},
},
} as const;

View File

@ -0,0 +1,29 @@
export const packedUserListSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
userIds: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
},
},
} as const;

View File

@ -0,0 +1,467 @@
export const packedUserLiteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
nullable: false, optional: false,
format: 'id',
example: 'xxxxxxxxxx',
},
name: {
type: 'string',
nullable: true, optional: false,
example: '藍',
},
username: {
type: 'string',
nullable: false, optional: false,
example: 'ai',
},
host: {
type: 'string',
nullable: true, optional: false,
example: 'misskey.example.com',
},
avatarUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
avatarBlurhash: {
type: 'any',
nullable: true, optional: false,
},
avatarColor: {
type: 'any',
nullable: true, optional: false,
default: null,
},
isAdmin: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isModerator: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isBot: {
type: 'boolean',
nullable: false, optional: true,
},
isCat: {
type: 'boolean',
nullable: false, optional: true,
},
emojis: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
url: {
type: 'string',
nullable: false, optional: false,
format: 'url',
},
},
},
},
onlineStatus: {
type: 'string',
format: 'url',
nullable: true, optional: false,
enum: ['unknown', 'online', 'active', 'offline'],
},
},
} as const;
export const packedUserDetailedNotMeOnlySchema = {
type: 'object',
properties: {
url: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
uri: {
type: 'string',
format: 'uri',
nullable: true, optional: false,
},
createdAt: {
type: 'string',
nullable: false, optional: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
nullable: true, optional: false,
format: 'date-time',
},
lastFetchedAt: {
type: 'string',
nullable: true, optional: false,
format: 'date-time',
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
bannerBlurhash: {
type: 'any',
nullable: true, optional: false,
},
bannerColor: {
type: 'any',
nullable: true, optional: false,
default: null,
},
isLocked: {
type: 'boolean',
nullable: false, optional: false,
},
isSilenced: {
type: 'boolean',
nullable: false, optional: false,
},
isSuspended: {
type: 'boolean',
nullable: false, optional: false,
example: false,
},
description: {
type: 'string',
nullable: true, optional: false,
example: 'Hi masters, I am Ai!',
},
location: {
type: 'string',
nullable: true, optional: false,
},
birthday: {
type: 'string',
nullable: true, optional: false,
example: '2018-03-12',
},
lang: {
type: 'string',
nullable: true, optional: false,
example: 'ja-JP',
},
fields: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
value: {
type: 'string',
nullable: false, optional: false,
},
},
maxLength: 4,
},
},
followersCount: {
type: 'number',
nullable: false, optional: false,
},
followingCount: {
type: 'number',
nullable: false, optional: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
pinnedNoteIds: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
},
pinnedNotes: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
ref: 'Note',
},
},
pinnedPageId: {
type: 'string',
nullable: true, optional: false,
},
pinnedPage: {
type: 'object',
nullable: true, optional: false,
ref: 'Page',
},
publicReactions: {
type: 'boolean',
nullable: false, optional: false,
},
twoFactorEnabled: {
type: 'boolean',
nullable: false, optional: false,
default: false,
},
usePasswordLessLogin: {
type: 'boolean',
nullable: false, optional: false,
default: false,
},
securityKeys: {
type: 'boolean',
nullable: false, optional: false,
default: false,
},
//#region relations
isFollowing: {
type: 'boolean',
nullable: false, optional: true,
},
isFollowed: {
type: 'boolean',
nullable: false, optional: true,
},
hasPendingFollowRequestFromYou: {
type: 'boolean',
nullable: false, optional: true,
},
hasPendingFollowRequestToYou: {
type: 'boolean',
nullable: false, optional: true,
},
isBlocking: {
type: 'boolean',
nullable: false, optional: true,
},
isBlocked: {
type: 'boolean',
nullable: false, optional: true,
},
isMuted: {
type: 'boolean',
nullable: false, optional: true,
},
//#endregion
},
} as const;
export const packedMeDetailedOnlySchema = {
type: 'object',
properties: {
avatarId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
bannerId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
injectFeaturedNote: {
type: 'boolean',
nullable: true, optional: false,
},
receiveAnnouncementEmail: {
type: 'boolean',
nullable: true, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: true, optional: false,
},
carefulBot: {
type: 'boolean',
nullable: true, optional: false,
},
autoAcceptFollowed: {
type: 'boolean',
nullable: true, optional: false,
},
noCrawle: {
type: 'boolean',
nullable: true, optional: false,
},
isExplorable: {
type: 'boolean',
nullable: false, optional: false,
},
isDeleted: {
type: 'boolean',
nullable: false, optional: false,
},
hideOnlineStatus: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadSpecifiedNotes: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadMentions: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadAnnouncement: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadAntenna: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadChannel: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadMessagingMessage: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadNotification: {
type: 'boolean',
nullable: false, optional: false,
},
hasPendingReceivedFollowRequest: {
type: 'boolean',
nullable: false, optional: false,
},
integrations: {
type: 'object',
nullable: true, optional: false,
},
mutedWords: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
},
mutedInstances: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
mutingNotificationTypes: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
emailNotificationTypes: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
//#region secrets
email: {
type: 'string',
nullable: true, optional: true,
},
emailVerified: {
type: 'boolean',
nullable: true, optional: true,
},
securityKeysList: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'object',
nullable: false, optional: false,
},
},
//#endregion
},
} as const;
export const packedUserDetailedNotMeSchema = {
type: 'object',
allOf: [
{
type: 'object',
ref: 'UserLite',
},
{
type: 'object',
ref: 'UserDetailedNotMeOnly',
},
],
} as const;
export const packedMeDetailedSchema = {
type: 'object',
allOf: [
{
type: 'object',
ref: 'UserLite',
},
{
type: 'object',
ref: 'UserDetailedNotMeOnly',
},
{
type: 'object',
ref: 'MeDetailedOnly',
},
],
} as const;
export const packedUserDetailedSchema = {
oneOf: [
{
type: 'object',
ref: 'UserDetailedNotMe',
},
{
type: 'object',
ref: 'MeDetailed',
},
],
} as const;
export const packedUserSchema = {
oneOf: [
{
type: 'object',
ref: 'UserLite',
},
{
type: 'object',
ref: 'UserDetailed',
},
],
} as const;

View File

@ -1,13 +1,11 @@
type Await<T> = T extends Promise<infer U> ? U : T;
type AwaitAll<T> = {
[P in keyof T]: Await<T[P]>;
export type Promiseable<T> = {
[K in keyof T]: Promise<T[K]> | T[K];
};
export async function awaitAll<T>(obj: T): Promise<AwaitAll<T>> {
const target = {} as any;
const keys = Object.keys(obj);
const values = Object.values(obj);
export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> {
const target = {} as T;
const keys = Object.keys(obj) as unknown as (keyof T)[];
const values = Object.values(obj) as any[];
const resolvedValues = await Promise.all(values.map(value =>
(!value || !value.constructor || value.constructor.name !== 'Object')

View File

@ -213,6 +213,16 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']
});
}
export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) {
return dbQueue.add('importCustomEmojis', {
user: user,
fileId: fileId,
}, {
removeOnComplete: true,
removeOnFail: true,
});
}
export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) {
return dbQueue.add('deleteAccount', {
user: user,

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs';
import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file';
import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host';
import { Users, Blockings } from '@/models/index';
@ -86,7 +86,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
logger.succ(`Exported to: ${path}`);
const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true);
const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`);
cleanup();

View File

@ -6,11 +6,12 @@ import { ulid } from 'ulid';
const mime = require('mime-types');
const archiver = require('archiver');
import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file';
import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat';
import { Users, Emojis } from '@/models/index';
import { } from '@/queue/types';
import { downloadUrl } from '@/misc/download-url';
import config from '@/config/index';
const logger = queueLogger.createSubLogger('export-custom-emojis');
@ -52,7 +53,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
});
};
await writeMeta(`{"metaVersion":1,"emojis":[`);
await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`);
const customEmojis = await Emojis.find({
where: {
@ -64,21 +65,25 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
});
for (const emoji of customEmojis) {
const exportId = ulid().toLowerCase();
const ext = mime.extension(emoji.type);
const emojiPath = path + '/' + exportId + (ext ? '.' + ext : '');
const fileName = emoji.name + (ext ? '.' + ext : '');
const emojiPath = path + '/' + fileName;
fs.writeFileSync(emojiPath, '', 'binary');
let downloaded = false;
try {
await downloadUrl(emoji.url, emojiPath);
await downloadUrl(emoji.originalUrl, emojiPath);
downloaded = true;
} catch (e) { // TODO: 何度か再試行
logger.error(e);
}
if (!downloaded) {
fs.unlinkSync(emojiPath);
}
const content = JSON.stringify({
id: exportId,
fileName: fileName,
downloaded: downloaded,
emoji: emoji,
});
@ -106,7 +111,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
logger.succ(`Exported to: ${archivePath}`);
const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.zip';
const driveFile = await addFile(user, archivePath, fileName, null, null, true);
const driveFile = await addFile({ user, path: archivePath, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`);
cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs';
import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file';
import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host';
import { Users, Followings, Mutings } from '@/models/index';
@ -87,7 +87,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
logger.succ(`Exported to: ${path}`);
const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true);
const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`);
cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs';
import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file';
import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host';
import { Users, Mutings } from '@/models/index';
@ -86,7 +86,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
logger.succ(`Exported to: ${path}`);
const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true);
const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`);
cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs';
import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file';
import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat';
import { Users, Notes, Polls } from '@/models/index';
import { MoreThan } from 'typeorm';
@ -95,7 +95,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
logger.succ(`Exported to: ${path}`);
const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json';
const driveFile = await addFile(user, path, fileName, null, null, true);
const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`);
cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs';
import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file';
import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host';
import { Users, UserLists, UserListJoinings } from '@/models/index';
@ -63,7 +63,7 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any):
logger.succ(`Exported to: ${path}`);
const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true);
const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`);
cleanup();

View File

@ -0,0 +1,85 @@
import * as Bull from 'bull';
import * as tmp from 'tmp';
import * as fs from 'fs';
const unzipper = require('unzipper');
import { getConnection } from 'typeorm';
import { queueLogger } from '../../logger';
import { downloadUrl } from '@/misc/download-url';
import { DriveFiles, Emojis } from '@/models/index';
import { DbUserImportJobData } from '@/queue/types';
import { addFile } from '@/services/drive/add-file';
import { genId } from '@/misc/gen-id';
const logger = queueLogger.createSubLogger('import-custom-emojis');
// TODO: 名前衝突時の動作を選べるようにする
export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing custom emojis ...`);
const file = await DriveFiles.findOne({
id: job.data.fileId,
});
if (file == null) {
done();
return;
}
// Create temp dir
const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => {
tmp.dir((e, path, cleanup) => {
if (e) return rej(e);
res([path, cleanup]);
});
});
logger.info(`Temp dir is ${path}`);
const destPath = path + '/emojis.zip';
try {
fs.writeFileSync(destPath, '', 'binary');
await downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行
logger.error(e);
throw e;
}
const outputPath = path + '/emojis';
const unzipStream = fs.createReadStream(destPath);
const extractor = unzipper.Extract({ path: outputPath });
extractor.on('close', async () => {
const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8');
const meta = JSON.parse(metaRaw);
for (const record of meta.emojis) {
if (!record.downloaded) continue;
const emojiInfo = record.emoji;
const emojiPath = outputPath + '/' + record.fileName;
await Emojis.delete({
name: emojiInfo.name,
});
const driveFile = await addFile({ user: null, path: emojiPath, name: record.fileName, force: true });
const emoji = await Emojis.insert({
id: genId(),
updatedAt: new Date(),
name: emojiInfo.name,
category: emojiInfo.category,
host: null,
aliases: emojiInfo.aliases,
originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type,
}).then(x => Emojis.findOneOrFail(x.identifiers[0]));
}
await getConnection().queryResultCache!.remove(['meta_emojis']);
cleanup();
logger.succ('Imported');
done();
});
unzipStream.pipe(extractor);
logger.succ(`Unzipping to ${outputPath}`);
}

View File

@ -46,13 +46,13 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done:
});
if (list == null) {
list = await UserLists.save({
list = await UserLists.insert({
id: genId(),
createdAt: new Date(),
userId: user.id,
name: listName,
userIds: [],
});
}).then(x => UserLists.findOneOrFail(x.identifiers[0]));
}
let target = isSelfHost(host!) ? await Users.findOne({

View File

@ -12,6 +12,7 @@ import { importUserLists } from './import-user-lists';
import { deleteAccount } from './delete-account';
import { importMuting } from './import-muting';
import { importBlocking } from './import-blocking';
import { importCustomEmojis } from './import-custom-emojis';
const jobs = {
deleteDriveFiles,
@ -25,6 +26,7 @@ const jobs = {
importMuting,
importBlocking,
importUserLists,
importCustomEmojis,
deleteAccount,
} as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>;

View File

@ -1,4 +1,4 @@
import uploadFromUrl from '@/services/drive/upload-from-url';
import { uploadFromUrl } from '@/services/drive/upload-from-url';
import { IRemoteUser } from '@/models/entities/user';
import Resolver from '../resolver';
import { fetchMeta } from '@/misc/fetch-meta';
@ -28,9 +28,15 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
logger.info(`Creating the Image: ${image.url}`);
const instance = await fetchMeta();
const cache = instance.cacheRemoteFiles;
let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH));
let file = await uploadFromUrl({
url: image.url,
user: actor,
uri: image.url,
sensitive: image.sensitive,
isLink: !instance.cacheRemoteFiles,
comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)
});
if (file.isLink) {
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、

View File

@ -320,14 +320,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
if ((tag.updated != null && exists.updatedAt == null)
|| (tag.id != null && exists.uri == null)
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|| (tag.icon!.url !== exists.url)
|| (tag.icon!.url !== exists.originalUrl)
) {
await Emojis.update({
host,
name,
}, {
uri: tag.id,
url: tag.icon!.url,
originalUrl: tag.icon!.url,
publicUrl: tag.icon!.url,
updatedAt: new Date(),
});
@ -342,14 +343,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
logger.info(`register emoji host=${host}, name=${name}`);
return await Emojis.save({
return await Emojis.insert({
id: genId(),
host,
name,
uri: tag.id,
url: tag.icon!.url,
originalUrl: tag.icon!.url,
publicUrl: tag.icon!.url,
updatedAt: new Date(),
aliases: [],
} as Partial<Emoji>);
} as Partial<Emoji>).then(x => Emojis.findOneOrFail(x.identifiers[0]));
}));
}

View File

@ -9,6 +9,6 @@ export default (emoji: Emoji) => ({
icon: {
type: 'Image',
mediaType: emoji.type || 'image/png',
url: emoji.url,
url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため
},
});

View File

@ -0,0 +1,15 @@
import config from '@/config/index';
import { IObject, IActivity } from '@/remote/activitypub/type';
import { ILocalUser, IRemoteUser } from '@/models/entities/user';
import { getInstanceActor } from '@/services/instance-actor';
// to anonymise reporters, the reporting actor must be a system user
// object has to be a uri or array of uris
export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => {
return {
type: 'Flag',
actor: `${config.url}/users/${user.id}`,
content,
object,
};
};

View File

@ -32,7 +32,7 @@ export const renderActivity = (x: any): IActivity | null => {
PropertyValue: 'schema:PropertyValue',
value: 'schema:value',
// Misskey
misskey: `${config.url}/ns#`,
misskey: 'https://misskey-hub.net/ns#',
'_misskey_content': 'misskey:_misskey_content',
'_misskey_quote': 'misskey:_misskey_quote',
'_misskey_reaction': 'misskey:_misskey_reaction',

View File

@ -37,7 +37,7 @@ export async function resolveUser(username: string, host: string | null, option?
});
}
const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser;
const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser | null;
const acctLower = `${usernameLower}@${host}`;

View File

@ -67,7 +67,7 @@ router.get('/notes/:note', async (ctx, next) => {
const note = await Notes.findOne({
id: ctx.params.note,
visibility: In(['public', 'home']),
visibility: In(['public' as const, 'home' as const]),
localOnly: false,
});
@ -96,7 +96,7 @@ router.get('/notes/:note/activity', async ctx => {
const note = await Notes.findOne({
id: ctx.params.note,
userHost: null,
visibility: In(['public', 'home']),
visibility: In(['public' as const, 'home' as const]),
localOnly: false,
});

View File

@ -10,7 +10,7 @@ export class AuthenticationError extends Error {
}
}
export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => {
export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => {
if (token == null) {
return [null, null];
}

View File

@ -1,5 +1,5 @@
import { performance } from 'perf_hooks';
import limiter from './limiter';
import { limiter } from './limiter';
import { User } from '@/models/entities/user';
import endpoints from './endpoints';
import { ApiError } from './error';

View File

@ -29,14 +29,14 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
(async () => {
// Append signin history
const record = await Signins.save({
const record = await Signins.insert({
id: genId(),
createdAt: new Date(),
userId: user.id,
ip: ctx.ip,
headers: ctx.headers,
success: true,
});
}).then(x => Signins.findOneOrFail(x.identifiers[0]));
// Publish signin event
publishMainStream(user.id, 'signin', await Signins.pack(record));

View File

@ -3,7 +3,7 @@ import { dirname } from 'path';
import { Context } from 'cafy';
import * as path from 'path';
import * as glob from 'glob';
import { SimpleSchema } from '@/misc/simple-schema';
import { Schema } from '@/misc/schema';
//const _filename = fileURLToPath(import.meta.url);
const _filename = __filename;
@ -18,87 +18,87 @@ export type Param = {
};
export interface IEndpointMeta {
stability?: string; //'deprecated' | 'experimental' | 'stable';
readonly stability?: 'deprecated' | 'experimental' | 'stable';
tags?: string[];
readonly tags?: ReadonlyArray<string>;
params?: {
[key: string]: Param;
readonly params?: {
readonly [key: string]: Param;
};
errors?: {
[key: string]: {
message: string;
code: string;
id: string;
readonly errors?: {
readonly [key: string]: {
readonly message: string;
readonly code: string;
readonly id: string;
};
};
res?: SimpleSchema;
readonly res?: Schema;
/**
* このエンドポイントにリクエストするのにユーザー情報が必須か否か
* 省略した場合は false として解釈されます。
*/
requireCredential?: boolean;
readonly requireCredential?: boolean;
/**
* 管理者のみ使えるエンドポイントか否か
*/
requireAdmin?: boolean;
readonly requireAdmin?: boolean;
/**
* 管理者またはモデレーターのみ使えるエンドポイントか否か
*/
requireModerator?: boolean;
readonly requireModerator?: boolean;
/**
* エンドポイントのリミテーションに関するやつ
* 省略した場合はリミテーションは無いものとして解釈されます。
* また、withCredential が false の場合はリミテーションを行うことはできません。
*/
limit?: {
readonly limit?: {
/**
* 複数のエンドポイントでリミットを共有したい場合に指定するキー
*/
key?: string;
readonly key?: string;
/**
* リミットを適用する期間(ms)
* このプロパティを設定する場合、max プロパティも設定する必要があります。
*/
duration?: number;
readonly duration?: number;
/**
* durationで指定した期間内にいくつまでリクエストできるのか
* このプロパティを設定する場合、duration プロパティも設定する必要があります。
*/
max?: number;
readonly max?: number;
/**
* 最低でもどれくらいの間隔を開けてリクエストしなければならないか(ms)
*/
minInterval?: number;
readonly minInterval?: number;
};
/**
* ファイルの添付を必要とするか否か
* 省略した場合は false として解釈されます。
*/
requireFile?: boolean;
readonly requireFile?: boolean;
/**
* サードパーティアプリからはリクエストすることができないか否か
* 省略した場合は false として解釈されます。
*/
secure?: boolean;
readonly secure?: boolean;
/**
* エンドポイントの種類
* パーミッションの実現に利用されます。
*/
kind?: string;
readonly kind?: string;
}
export interface IEndpoint {

View File

@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -46,70 +46,76 @@ export const meta = {
]),
default: 'combined',
},
forwarded: {
validator: $.optional.bool,
default: false,
},
},
res: {
type: 'array' as const,
optional: false as const, nullable: false as const,
type: 'array',
optional: false, nullable: false,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string' as const,
nullable: false as const, optional: false as const,
type: 'string',
nullable: false, optional: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
nullable: false as const, optional: false as const,
type: 'string',
nullable: false, optional: false,
format: 'date-time',
},
comment: {
type: 'string' as const,
nullable: false as const, optional: false as const,
type: 'string',
nullable: false, optional: false,
},
resolved: {
type: 'boolean' as const,
nullable: false as const, optional: false as const,
type: 'boolean',
nullable: false, optional: false,
example: false,
},
reporterId: {
type: 'string' as const,
nullable: false as const, optional: false as const,
type: 'string',
nullable: false, optional: false,
format: 'id',
},
targetUserId: {
type: 'string' as const,
nullable: false as const, optional: false as const,
type: 'string',
nullable: false, optional: false,
format: 'id',
},
assigneeId: {
type: 'string' as const,
nullable: true as const, optional: false as const,
type: 'string',
nullable: true, optional: false,
format: 'id',
},
reporter: {
type: 'object' as const,
nullable: false as const, optional: false as const,
type: 'object',
nullable: false, optional: false,
ref: 'User',
},
targetUser: {
type: 'object' as const,
nullable: false as const, optional: false as const,
type: 'object',
nullable: false, optional: false,
ref: 'User',
},
assignee: {
type: 'object' as const,
nullable: true as const, optional: true as const,
type: 'object',
nullable: true, optional: true,
ref: 'User',
},
},
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps) => {
const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId);

View File

@ -16,18 +16,19 @@ export const meta = {
},
res: {
type: 'object' as const,
optional: false as const, nullable: false as const,
type: 'object',
optional: false, nullable: false,
ref: 'User',
properties: {
token: {
type: 'string' as const,
optional: false as const, nullable: false as const,
type: 'string',
optional: false, nullable: false,
},
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps, _me) => {
const me = _me ? await Users.findOneOrFail(_me.id) : null;
const noUsers = (await Users.count({

View File

@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -17,8 +17,9 @@ export const meta = {
validator: $.type(ID),
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps, me) => {
const user = await Users.findOne(ps.userId);

View File

@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -32,8 +32,9 @@ export const meta = {
validator: $.str.min(1),
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps) => {
await Ads.insert({
id: genId(),

View File

@ -7,7 +7,7 @@ import { ApiError } from '../../../error';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -23,8 +23,9 @@ export const meta = {
id: 'ccac9863-3a03-416e-b899-8a64041118b1',
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps, me) => {
const ad = await Ads.findOne(ps.id);

View File

@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -24,8 +24,9 @@ export const meta = {
validator: $.optional.type(ID),
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps) => {
const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId)
.andWhere('ad.expiresAt > :now', { now: new Date() });

View File

@ -7,7 +7,7 @@ import { ApiError } from '../../../error';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -44,8 +44,9 @@ export const meta = {
id: 'b7aa1727-1354-47bc-a182-3a9c3973d300',
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps, me) => {
const ad = await Ads.findOne(ps.id);

View File

@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireCredential: true,
requireModerator: true,
params: {
@ -22,50 +22,51 @@ export const meta = {
},
res: {
type: 'object' as const,
optional: false as const, nullable: false as const,
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
title: {
type: 'string' as const,
optional: false as const, nullable: false as const,
type: 'string',
optional: false, nullable: false,
},
text: {
type: 'string' as const,
optional: false as const, nullable: false as const,
type: 'string',
optional: false, nullable: false,
},
imageUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
type: 'string',
optional: false, nullable: true,
},
},
},
};
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, async (ps) => {
const announcement = await Announcements.save({
const announcement = await Announcements.insert({
id: genId(),
createdAt: new Date(),
updatedAt: null,
title: ps.title,
text: ps.text,
imageUrl: ps.imageUrl,
});
}).then(x => Announcements.findOneOrFail(x.identifiers[0]));
return announcement;
});

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