Compare commits

..

278 Commits

Author SHA1 Message Date
ffe75d85ae feat: Emoji generator 2022-05-07 12:45:33 +09:00
8168b4edad add reload button 2022-05-01 21:51:08 +09:00
ab9b648c59 Merge tag '12.110.1' 2022-04-23 20:32:00 +09:00
3658f19d98 12.110.1 2022-04-23 19:54:09 +09:00
e213c2e844 remove unused locale 2022-04-23 19:53:17 +09:00
dd86397e85 fix: アンテナ、クリップ、リストの表示を速くする (#8518)
* アンテナノートを取得するクエリがタイムアウトしないように速くする

* テーブル名を直接指定しないようにする

* クリップの取得を速くする

* リストの取得を速くする
2022-04-23 19:44:25 +09:00
6d33b366f8 fix ogp rendering and refactor 2022-04-23 19:44:17 +09:00
710e633e33 Merge tag '12.110.0' 2022-04-13 22:47:42 +09:00
dfe6615d68 Update README.md 2022-04-13 08:40:03 +09:00
33c22b5f3e Merge branch 'develop' 2022-04-11 23:13:18 +09:00
16c7ef41fb 12.110.0 2022-04-11 23:13:09 +09:00
6e50579f9f New Crowdin updates (#8469)
* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

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

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

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Arabic)

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

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

* 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)
2022-04-11 23:12:27 +09:00
08ff4926c7 enhance(client): show loading icon on splash screen
Close #8481
2022-04-11 23:11:11 +09:00
a7015e6f09 refactor 2022-04-11 22:50:53 +09:00
fd4ec81bcb update deps 2022-04-11 22:48:04 +09:00
9c33e6eef7 Update CHANGELOG.md 2022-04-09 19:33:13 +09:00
9f7cdb4bc7 refactor 2022-04-08 19:01:38 +09:00
a9ec9df606 画像のリンクを変更 2022-04-07 01:33:31 +09:00
daa0ca72a7 fix(api): parameter validation of users/show was wrong 2022-04-06 00:04:25 +09:00
67fc39b8db chore(deps): bump axios from 0.21.1 to 0.21.4 (#8471)
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-05 21:34:33 +09:00
45c457b8b3 chore: fix lint on windows 2022-04-05 21:32:14 +09:00
2ff8ace195 12.109.2-nca10.net-v3 2022-04-04 23:45:02 +09:00
17081c4612 Merge remote-tracking branch 'origin/master' 2022-04-04 20:14:06 +09:00
deaea4a0d8 link切れだった画像のlinkを変更 2022-04-04 20:13:54 +09:00
8646f653d0 update apple-touch-icon.png
update apple-touch-icon.png
2022-04-04 17:51:13 +09:00
8252edfc1e update apple-touch-icon.png 2022-04-04 17:41:45 +09:00
cf77eaaba9 エラー画像の変更 2022-04-04 13:28:39 +09:00
d52b8ca8ab 12.109.2-nca10.net-v2 2022-04-04 12:29:12 +09:00
0109308f5a faviconとその他の画像を変更 2022-04-04 12:27:39 +09:00
fc06bb8c49 bibibi_nullcatのurlを変更 2022-04-04 11:14:59 +09:00
ec3be7e4d3 enhance(webhook): add userId to payload 2022-04-03 22:42:01 +09:00
f8e6f3cc73 improve webhook 2022-04-03 22:36:30 +09:00
fc39383d65 12.109.2-nca10.net-v1 2022-04-03 20:15:05 +09:00
7f5d189528 fix type 2022-04-03 16:40:47 +09:00
ff9a074ab6 fix type 2022-04-03 16:35:36 +09:00
91f4ec3747 fix types 2022-04-03 16:30:22 +09:00
41c2aed7dc chore: fix lint 2022-04-03 15:33:22 +09:00
b8360313e8 chore: fix paths 2022-04-03 15:24:29 +09:00
403b82277c refactor actions 2022-04-03 15:21:46 +09:00
433505df48 fix e2e test 2022-04-03 15:14:26 +09:00
090f8eff67 Merge branch 'develop' 2022-04-03 14:01:19 +09:00
395fe7eb4b 12.109.2 2022-04-03 14:01:12 +09:00
c8935b32f8 fix: validation (better #8456) (#8461)
* Revert "revert 484e023c0"

This reverts commit c03b70c949.

* also allow pure renote

* fix checks for pure renote
2022-04-03 13:57:26 +09:00
ebb687cde4 Update CHANGELOG.md 2022-04-03 13:56:44 +09:00
e47a8bf666 fix theme-color apply (#8464) 2022-04-03 13:56:00 +09:00
408d54f2eb fix(api): admin/update-meta was not working 2022-04-03 13:54:22 +09:00
f9b5d92176 Fix: Adjust ESLint calls to properly interpret globs (#8462)
* fix(backend): rename .eslintrc.js to .eslintrc.cjs

* fix(backend): wrap lint path glob in quotation marks

* fix(client): wrap lint path glob in quotation marks

* chore(workflow): make lint workflow use Node 16
2022-04-02 22:52:26 +09:00
61dfa6d598 Update CHANGELOG.md 2022-04-02 21:40:49 +09:00
2f621ceb7a 12.109.1-nca10.net-v1 2022-04-02 17:20:22 +09:00
5abe05d572 Merge branch 'develop' 2022-04-02 16:56:35 +09:00
7722fc4d3f 12.109.1 2022-04-02 16:56:24 +09:00
6a379b463f Update CHANGELOG.md 2022-04-02 16:49:54 +09:00
c03b70c949 revert 484e023c0 2022-04-02 16:47:53 +09:00
2375359d12 Merge branch 'develop' 2022-04-02 15:34:03 +09:00
68d462b301 12.109.0 2022-04-02 15:33:51 +09:00
6b6464a659 fix syntax error 2022-04-02 15:31:11 +09:00
4fdfbfd5c4 New Crowdin updates (#8409)
* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

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

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

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

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

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* 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 (Chinese Simplified)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Slovak)

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

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Indonesian)

* 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)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)

* New translations ja-JP.yml (Esperanto)
2022-04-02 15:29:24 +09:00
8e5f2690f2 feat: Webhook (#8457)
* feat: introduce webhook

* wip

* wip

* wip

* Update CHANGELOG.md
2022-04-02 15:28:49 +09:00
99e6ef5996 Update CHANGELOG.md 2022-04-02 15:26:48 +09:00
1033e8e57f fix(federation): avoid duplicate activity delivery (#8429)
* prefer shared inbox over individual inbox

* no new shared inbox for direct recipes

* fix type error
2022-04-02 15:16:35 +09:00
f7030d4a42 enhance: タッチパッド・タッチスクリーンでのデッキの操作性を向上 (#8450)
* enhance experience of deck with touchpad

* test: 単純にdeltaYを加算してみる

* clean up

* ios bug fix?

* ✌️

* use overflow-y

* Safari does not supports clip
2022-04-02 15:12:01 +09:00
484e023c0c enhance(doc): required input fields (#8456)
* remove empty file

If the endpoint is to be implemented later, the file can be added back,
but for now it is confusing to have an empty file.

* enhance(doc): document defaults

Default for `isPublic` is based on the database schema default value.
Defaults for `local` and `withFiles` are based on the behaviour of the endpoint.

* enhance(doc): explain nullable emoji category

* fix: make nullable if default is null

* enhance(doc): explain mute attribute expiresAt

* fix: define required fields

- `notes/create`: the default for `text` has been removed because ajv can not handle
  `default` inside of `anyOf`, see
  https://ajv.js.org/guide/modifying-data.html#assigning-defaults
  and the default value cannot be `null` if text is `nullable: false` in the `anyOf`
  first alternative.
- `notes/create`: The `mediaIds` property has been marked as deprecated because it
  has the same behaviour as using `fileIds`, but the implementation tries to handlè
  `fileIds` first.
- The result schema for `admin/emoji/list` has been altered because the `host`
  property will always be `null` as it is filtered this way in the database query.
  See packages/backend/src/server/api/endpoints/admin/emoji/list.ts line 67.

* enhance(doc): explain nullable hostname

* update changelog

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2022-04-02 15:04:36 +09:00
7c781179a1 Update CHANGELOG.md 2022-04-02 12:12:10 +09:00
39302bf0ea fix(server): admin/meta is not working
Fix #8455
2022-04-01 19:17:19 +09:00
11ccb98c93 update deps 2022-04-01 19:13:40 +09:00
4e63022a49 fix(server): add missing import 2022-03-29 18:46:59 +09:00
75da7ab484 Update ROADMAP.md 2022-03-28 13:38:05 +09:00
515ab0105a Update CONTRIBUTING.md 2022-03-27 20:11:43 +09:00
6241ed9621 Update ROADMAP.md 2022-03-27 19:37:50 +09:00
1786de8ce0 Update ROADMAP.md 2022-03-27 19:20:05 +09:00
ebe1b78579 Create ROADMAP.md 2022-03-27 19:13:20 +09:00
a8c3e0186a chore(deps): bump follow-redirects from 1.14.1 to 1.14.8 (#8313)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.1 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.1...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:42:22 +09:00
7ccb9226ef chore(deps): bump follow-redirects in /packages/backend (#8314)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:42:05 +09:00
48742dcee1 Bump ansi-regex from 5.0.0 to 5.0.1 in /packages/client (#8422)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:41:52 +09:00
4acfa7a191 Bump nanoid from 3.1.20 to 3.3.1 in /packages/client (#8425)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.20 to 3.3.1.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.20...3.3.1)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:41:39 +09:00
122d5ea529 Bump nanoid from 3.1.30 to 3.3.1 in /packages/backend (#8426)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.3.1.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.3.1)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:41:26 +09:00
47795cc683 chore(deps): bump minimist from 1.2.5 to 1.2.6 (#8445)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:33:16 +09:00
ba66ddd41e chore(deps): bump minimist from 1.2.5 to 1.2.6 in /packages/client (#8446)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-27 16:32:57 +09:00
525b4b6cf4 tweak client 2022-03-27 16:28:25 +09:00
30fe632d54 fix of client 2022-03-27 16:21:09 +09:00
4e139d2aae fix query 2022-03-27 16:16:13 +09:00
d113aae217 update deps 2022-03-27 15:34:34 +09:00
a6f6ddc34e limit federation of reactions on direct notes (#8448) 2022-03-27 13:57:04 +09:00
c7969ef418 refactor 2022-03-27 02:22:31 +09:00
a3cdb4cca1 fix: Handle decodeURIComponent error (#8411) 2022-03-27 02:21:56 +09:00
e6f455a9bb fix null in query 2022-03-26 19:48:22 +09:00
fa1a53270e refactor 2022-03-26 19:33:18 +09:00
17589843da perf(server): use cached user info in getUserFromApId 2022-03-26 19:09:57 +09:00
8cbfc047bb refactor 2022-03-26 18:42:37 +09:00
31f0d1d874 Update CHANGELOG.md 2022-03-26 18:28:30 +09:00
475cee9029 perf(server): reduce db query when get notifications 2022-03-26 18:22:55 +09:00
3cac8e0f6f perf(server): improve deliver performance 2022-03-26 17:43:08 +09:00
bc2c40a6ca refactor and performance improvements 2022-03-26 17:19:27 +09:00
7a4c3bab7e chore(deps): bump minimist from 1.2.5 to 1.2.6 in /packages/backend (#8447)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-26 15:35:10 +09:00
1c67c26bd8 refactor: migrate to typeorm 3.0 (#8443)
* wip

* wip

* wip

* Update following.ts

* wip

* wip

* wip

* Update resolve-user.ts

* maxQueryExecutionTime

* wip

* wip
2022-03-26 15:34:00 +09:00
41c87074e6 .js 2022-03-25 16:35:24 +09:00
889a890ac5 update deps 2022-03-25 16:32:10 +09:00
ac8c66f5ab perf(server): refactor and performance improvements 2022-03-25 16:27:41 +09:00
22b56ac65c refactor 2022-03-25 13:11:52 +09:00
6f5282058f Update account.ts 2022-03-25 02:43:48 +09:00
863b6c48f8 enhance(client): アカウント情報の取得に失敗したとき強制ログアウトではなく警告を表示するように 2022-03-25 01:53:20 +09:00
083b913dd2 refactor 2022-03-25 01:51:34 +09:00
d0a346ed8a refactor: separate meta api for admin or not 2022-03-25 01:50:28 +09:00
725b78349a recognize null in _misskey_content for notes (#8440) 2022-03-23 03:16:04 +09:00
08bace6c7d Resolve #7208 (#7226)
add decrement replies count on delete note and a test for that
2022-03-22 22:48:33 +09:00
ba9563b983 Use unique id for Undo (#8434) 2022-03-22 00:39:00 +09:00
9320c1699a perf(server): disable some antenna features to improve performance 2022-03-22 00:07:43 +09:00
ff19640171 perf(server): reduce db query 2022-03-21 20:43:43 +09:00
483c97e224 12.108.1-nca10.net-v2 2022-03-21 17:49:30 +09:00
b6a92f3680 Add: 絵文字作成ボタンを追加 2022-03-21 17:45:53 +09:00
a1a6e3560a Add: 絵文字作成ボタンを追加 2022-03-21 17:45:20 +09:00
c7f9a84970 imp: コントロールパネルの表示位置変更 2022-03-21 16:11:41 +09:00
81374e0eac imp: コントロールパネルの表示位置変更 2022-03-21 16:11:13 +09:00
81ee9025fb tweak log 2022-03-21 05:45:09 +09:00
0d05f05610 chore: add note 2022-03-21 05:43:52 +09:00
21de5c4a9c perf(server): reduce db query 2022-03-21 05:42:11 +09:00
131ff24e53 .js 2022-03-21 05:33:06 +09:00
b6da0e9b92 .js 2022-03-21 05:31:04 +09:00
836ae732f6 perf(server): cache nodeinfo to reduce db query 2022-03-21 05:26:42 +09:00
6f2e93c6a1 perf(server): reduce db query 2022-03-21 05:21:37 +09:00
78736c70f7 デッキまわりをCompositon API / Setup Sugarに (#8410)
* universal.widgets.vue

* column.vue, antenna-column.vue

* direct-column.vue, list-column.vue

* main-column.vue

* wip

* ✌️

* fix

* ✌️

* ✌️
2022-03-21 03:11:14 +09:00
eb9e6d230f perf(server): reduce db query 2022-03-21 01:22:00 +09:00
aebd77ad38 perf(server): reduce db query 2022-03-20 15:44:49 +09:00
f68b646878 Fix: warn about outdated NodeJS fixed (#8388)
* Fix #8387

* update changelog

Co-authored-by: Johann150 <johann.galle@protonmail.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2022-03-20 01:34:45 +09:00
c928941d29 fix(client): fix popup menu direction calclation 2022-03-19 22:03:53 +09:00
544b5ba838 perf(server): reduce memory usage of redis 2022-03-19 19:34:04 +09:00
815c8bf4c8 update deps 2022-03-19 19:22:58 +09:00
75191a942f Update CHANGELOG.md 2022-03-19 19:10:12 +09:00
3e9ac810ba Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2022-03-19 19:09:02 +09:00
caadc0978a feat: introduce bull dashboard 2022-03-19 19:08:55 +09:00
255dc6334f fix API console (#8416)
Adjusted the server to send the API description based on the new
API type declarations introduced previously.
2022-03-19 18:25:06 +09:00
c0bf7cd871 Fix: "Adding email to profile ends in error" (#8405) 2022-03-14 22:57:55 +09:00
7cf524c8f8 Update docker-image.yml 2022-03-13 22:11:38 +09:00
1c1013046e 12.108.1-nca10.net-v1 2022-03-13 22:05:00 +09:00
2689836ab1 fix: 顔文字機能の修正 2022-03-11 16:29:50 +09:00
cfc5578d14 fix: 顔文字機能の修正 2022-03-11 16:27:43 +09:00
c4a166db59 12.108.0-nca10.net-v2 2022-03-10 00:24:00 +09:00
22933ef7f1 重複していた個所の修正 2022-03-10 00:00:33 +09:00
692a1ba551 add docker-compose.yml 2022-03-09 23:36:59 +09:00
9507b44d22 12.108.0-nca10.net-v1 2022-03-09 23:15:42 +09:00
f7114628e0 docker-compose.ymlを省くように 2022-03-09 14:02:44 +09:00
9a53bb67fc インストールをインスタールに変更 2022-03-09 13:59:09 +09:00
18a2d430fc gitignoreにdocker-compose.ymlを追加 2022-03-09 13:57:35 +09:00
4a836121b4 Update ncatlang.md 2022-03-09 12:53:55 +09:00
30399dee8f Update README.md 2022-03-09 12:53:27 +09:00
f0f3442fb1 Update README.md 2022-03-09 12:52:15 +09:00
d31446d8aa 12.107.0-nca10.net-v5 2022-03-08 18:35:45 +09:00
c214fe17bf 新しいiconに変更 2022-03-08 18:34:43 +09:00
5f40394a8e Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-02-23 03:15:19 +09:00
97f725ae9c 色調整 2022-02-23 03:15:07 +09:00
0706c775ac Create docker-image.yml 2022-02-22 22:02:47 +09:00
be45be0be9 顔文字選択画面を改新 2022-02-22 02:52:13 +09:00
9abbabd64f Delete 利用規約.md 2022-02-21 20:20:19 +09:00
39221998bd Create 利用規約.md 2022-02-21 20:19:38 +09:00
de66f35798 Update README.md 2022-02-21 20:13:10 +09:00
2b402fe3a0 v4!! 2022-02-21 19:42:41 +09:00
03fe23d507 顔文字機能ฅ(=✧ω✧=)ฅ 2022-02-21 19:41:21 +09:00
b2ffcb77e0 住んでる匹数固定 2022-02-21 09:20:34 +09:00
f37bd9ea65 v3!!! 2022-02-20 18:14:41 +09:00
750d528db2 コンソトールパネル 2022-02-20 18:09:07 +09:00
d837bbf5f7 botアイコンいい感じ 2022-02-20 18:08:54 +09:00
38d3703484 削除の色変更 2022-02-19 17:09:57 +09:00
7667493d56 チャートの色変更 2022-02-19 16:48:21 +09:00
ff96126057 リアクションの色味変更 2022-02-19 16:40:06 +09:00
477163cb84 リアクション+ 2022-02-19 05:40:01 +09:00
a3dbde894a isBotのとこ変えた 2022-02-19 05:39:51 +09:00
59b2715058 オンライン状態のところの色変更 2022-02-17 20:06:02 +09:00
270b842c71 botのところいいかんじにした 2022-02-17 20:05:50 +09:00
8b197ed4d6 サーバーの通信量を監視するやつの色を変えた 2022-02-16 18:13:57 +09:00
3a38bbbe6d Update README.md 2022-02-16 10:39:10 +09:00
f70838a983 Update README.md 2022-02-16 10:37:38 +09:00
1fd2a7ec13 色を変えたよ 2022-02-15 22:25:09 +09:00
dac56bace9 Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-02-14 20:24:54 +09:00
bb655b1807 dayの色変更 2022-02-14 20:24:48 +09:00
b68e98d521 Update README.md 2022-02-12 23:21:50 +09:00
c36dee8808 日付表示の色変更 2022-02-12 18:53:58 +09:00
86847f1866 12.107.0にあっぷだて 2022-02-12 17:48:24 +09:00
203454848a 色なおし 2022-02-12 17:47:02 +09:00
2f640ccdf1 12.106.3あっぷだて 2022-02-12 14:10:06 +09:00
aaf0f9aab1 アップデート 2022-02-11 23:55:01 +09:00
377f84e26e Merge branches 'Ncat' and 'Ncat' of https://github.com/nullnyat/nca10.net into Ncat 2022-02-11 22:20:22 +09:00
7384c55b69 ホームにのにを消した 2022-02-11 22:17:46 +09:00
83726b9c96 Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-02-11 19:28:46 +09:00
3295696a20 v2 2022-02-11 19:27:20 +09:00
054db12d02 Merge tag '12.106.0-nca10.net-v2' into Ncat 2022-02-11 19:21:49 +09:00
3de91eee2c ログインページの絵文字変更 2022-02-11 18:47:21 +09:00
3ef1748e6d おかえりのとことnca10に変えるやつ 2022-02-11 18:40:37 +09:00
8402a9b665 12.106.0あっぷだて 2022-02-11 18:10:12 +09:00
dd47d50679 イロカエtあ close #5 2022-02-10 02:16:16 +09:00
595f01ed28 Merge pull request #6 from NullCatSlave/ncat-sim-dev-renote-home
homeRNのオプションも追加する
2022-02-10 00:35:17 +09:00
3a83aabf42 Merge remote-tracking branch 'sim1222_misskey/ncat-sim-dev-renote-home' into ncat-sim-dev-renote-home 2022-02-09 23:24:32 +09:00
87ba0ec566 feat: Add renote home only button typo 2022-02-09 22:49:11 +09:00
75482bb4b3 feat: Add renote home only button 2022-02-09 22:47:55 +09:00
c57f03b092 12.105.0あっぷだて 2022-02-09 22:06:29 +09:00
e53e40bf62 12.104.0あっぷだて 2022-02-09 15:19:44 +09:00
be4d3a2b84 エラー画面をブルスク風にした
Co-authored-by: sousuke0422 <sousuke0422@users.noreply.github.com>
Co-authored-by: あずき⪥™ <hijiki02@users.noreply.github.com>
Co-authored-by: こけっち <sim1222@users.noreply.github.com>
Co-authored-by: ふるふる <frfs@users.noreply.github.com>
2022-02-08 22:38:09 +09:00
08d0b0f5ef ウィジェットの色 2022-02-08 17:19:08 +09:00
828121217d ボタンキラキラ+ 2022-02-08 17:18:41 +09:00
49fad60683 ログインページ関連 2022-02-08 17:18:08 +09:00
adc85b8af4 くうはく消した 2022-02-08 14:27:55 +09:00
7532e44d37 ボタンきらきら 2022-02-08 00:33:05 +09:00
081a312132 色変わってないとこ 2022-02-08 00:32:58 +09:00
f5ba0db663 オンラインユーザーのウィジェット 2022-02-07 22:50:02 +09:00
ed727aac78 リアクション戻した 2022-02-07 22:49:30 +09:00
8f85675275 僕が使うウィジェットだけの色変えた 2022-02-07 19:43:08 +09:00
5d1946465b リアクションのとこのUI変えてみた 2022-02-07 16:56:34 +09:00
839dbd98da 鍵Renoteに変えた 2022-02-07 13:17:18 +09:00
866f3954a9 Update README.md 2022-02-07 11:03:31 +09:00
dd2f324050 Update README.md 2022-02-07 11:02:47 +09:00
ee6209afb2 Update README.md 2022-02-07 10:59:50 +09:00
3b68630e61 移動 2022-02-07 10:51:12 +09:00
8f539adfdb Create ncatlang.md 2022-02-07 10:44:55 +09:00
56f552cc89 Update README.md 2022-02-07 10:41:08 +09:00
bb7fc84fab Update README.md 2022-02-07 10:40:39 +09:00
e7ae677b71 Update README.md 2022-02-07 10:40:29 +09:00
64f80f122f Update README.md 2022-02-07 10:39:37 +09:00
43a59c9b80 Update Dockerfile 2022-02-07 09:43:00 +09:00
53f548bb08 Update Dockerfile 2022-02-07 09:40:54 +09:00
291743087d フォロワーのみに鍵リノートボタンが英語環境だとバグが発生する可能性があるバグを修正 (#14) 2022-02-07 09:28:25 +09:00
432210e1b5 Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-02-07 09:07:28 +09:00
cb115d31cc 色変えた 2022-02-07 09:07:19 +09:00
088ecac89d feat: リノートメニューに鍵リノートボタンを追加 (#13) 2022-02-07 03:18:33 +09:00
7257336dc5 藍ちゃん要素を消した 2022-02-04 21:06:51 +09:00
feb4373735 test 2022-02-04 20:46:37 +09:00
9861135546 test 2022-02-04 20:44:24 +09:00
22c0d5cb4e Merge tag '12.103.1' into Ncat 2022-02-03 14:12:24 +09:00
9d1e4b9014 色変えたりした 2022-02-03 11:03:52 +09:00
99971cb91c Merge tag 'HEAD' into Ncat 2022-02-03 09:49:19 +09:00
c40da925a3 Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-02-03 09:07:38 +09:00
7c75e6666e ねこのurl変えた 2022-02-03 09:06:54 +09:00
922b7b0ad3 Update README.md 2022-02-01 19:52:39 +09:00
05c2db1dc0 Theme変えた 2022-01-31 08:41:00 +09:00
282c2be5af 画像周りアップデート 2022-01-31 08:17:54 +09:00
6fd089d31a にゃんにゃん復活 2022-01-29 07:19:40 +09:00
4850919a5b ボタンもっかい消したのと言語ファイル復活させた 2022-01-29 06:37:13 +09:00
ed0eafbfd0 Merge remote-tracking branch 'upstream/master' into Ncat 2022-01-29 06:03:44 +09:00
89120d45c0 Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-01-29 05:58:01 +09:00
1eedeb5842 Update README.md 2022-01-28 04:38:03 +09:00
c363154e07 治らんかったから戻してen-US追加した 2022-01-28 04:34:51 +09:00
87627eebaa ぬるきゃ語しかないからeu端末だとエラー吐くの多分治った! 2022-01-28 03:43:25 +09:00
a5956fc2f4 a 2022-01-27 02:25:52 +09:00
2924b84deb 投稿の時に表示されるアイコン気になりすぎるから消した 2022-01-27 02:04:29 +09:00
3b303a20a4 Merge branch 'master' into ncat2 2022-01-27 01:12:49 +09:00
0fb9a438f9 update (#8)
* 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>
2022-01-27 00:50:28 +09:00
139f4ae26b Update README.md 2022-01-26 20:23:39 +09:00
7e25be2513 Merge remote-tracking branch 'origin/Ncat' into Ncat 2022-01-26 20:22:26 +09:00
c32edde497 ぬるきゃご以外を消した 2022-01-26 20:13:58 +09:00
b6e66d1d39 Update README.md 2022-01-26 20:08:29 +09:00
1d3ec1f31c Update README.md 2022-01-26 19:47:11 +09:00
a8abf35a91 AdminとModeratorのタグを猫に!!!
にゃんにゃん
2022-01-26 18:43:41 +09:00
90462ea1de 転々 2022-01-23 20:11:16 +09:00
bb1d02bde2 転々 2022-01-23 20:10:10 +09:00
884f6a2fc9 ログイン画面の右上のGitHubのURLを自分のに変更 2022-01-23 03:12:28 +09:00
d5ae48a754 Update README.md 2022-01-20 23:53:00 +09:00
0e04fd26d7 Update README.md 2022-01-20 23:52:50 +09:00
14440a95af Update README.md 2022-01-15 05:48:31 +09:00
645d945a33 Update README.md 2022-01-15 05:46:54 +09:00
1024fa4706 Update README.md 2022-01-15 05:46:38 +09:00
f231ad84e9 Update README.md 2022-01-15 04:33:38 +09:00
5c79e72774 Update README.md 2022-01-15 04:30:51 +09:00
639024897a Update README.md 2022-01-15 04:29:59 +09:00
0103cf8cd5 Update README.md 2022-01-15 04:25:29 +09:00
37129c2aa2 Update README.md 2022-01-15 04:23:56 +09:00
30f7e9e104 Update README.md 2022-01-15 04:20:15 +09:00
edad7fdbfe Update README.md 2022-01-10 02:58:04 +09:00
ec6facfe7d Update instancecolor.md 2022-01-09 23:36:46 +09:00
42184984a2 Update README.md 2022-01-09 23:35:41 +09:00
cc82bc182a Update README.md 2022-01-09 23:34:29 +09:00
6e52dc622d Create instancecolor.md 2022-01-09 23:33:21 +09:00
55ee63a57e Update README.md 2022-01-09 14:24:26 +09:00
a03f330c49 Themeを色々変えてみた 2022-01-06 23:07:20 +09:00
01185a830b 新規登録を殺した 2022-01-06 23:06:58 +09:00
502441c0e8 日本語なんてなかったをした 2022-01-06 23:06:03 +09:00
e1985690d2 Merge branch 'Ncat' of https://github.com/nullnyat/nca10.net into Ncat 2022-01-04 20:35:18 +09:00
eedd182187 色々消した 2022-01-04 20:35:07 +09:00
e9e803db18 Update README.md 2022-01-04 20:22:21 +09:00
22c91811ad Update README.md 2022-01-04 20:22:06 +09:00
ded13933f2 色々やった 2022-01-04 20:16:59 +09:00
555 changed files with 6422 additions and 10036 deletions

18
.github/workflows/docker-image.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Docker Image CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Docker image
run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)

View File

@ -14,12 +14,12 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- uses: actions/setup-node@v1 - uses: actions/setup-node@v3
with: with:
node-version: 12.x node-version: 16.x
- uses: actions/cache@v2 cache: 'yarn'
with: cache-dependency-path: |
path: '**/node_modules' packages/backend/yarn.lock
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} packages/client/yarn.lock
- run: yarn install - run: yarn install
- run: yarn lint - run: yarn lint

View File

@ -33,9 +33,13 @@ jobs:
with: with:
submodules: true submodules: true
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: |
packages/backend/yarn.lock
packages/client/yarn.lock
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Check yarn.lock - name: Check yarn.lock
@ -80,13 +84,13 @@ jobs:
#- uses: browser-actions/setup-firefox@latest #- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }} # if: ${{ matrix.browser == 'firefox' }}
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- uses: actions/cache@v2 cache: 'yarn'
with: cache-dependency-path: |
path: '**/node_modules' packages/backend/yarn.lock
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} packages/client/yarn.lock
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Check yarn.lock - name: Check yarn.lock

View File

@ -10,6 +10,54 @@
You should also include the user name that made the change. You should also include the user name that made the change.
--> -->
## 12.110.1 (2022/04/23)
### Bugfixes
- Fix GOP rendering @syuilo
- Improve performance of antenna, clip, and list @xianon
## 12.110.0 (2022/04/11)
### Improvements
- Improve webhook @syuilo
- Client: Show loading icon on splash screen @syuilo
### Bugfixes
- API: parameter validation of users/show was wrong
- Federation: リモートインスタンスへのダイレクト投稿が届かない問題を修正 @syuilo
## 12.109.2 (2022/04/03)
### Bugfixes
- API: admin/update-meta was not working @syuilo
- Client: テーマを切り替えたり読み込んだりするとmeta[name="theme-color"]のcontentがundefinedになる問題を修正 @tamaina
## 12.109.1 (2022/04/02)
### Bugfixes
- API: Renoteが行えない問題を修正
## 12.109.0 (2022/04/02)
### Improvements
- Webhooks @syuilo
- Bull Dashboardを組み込み、ジョブキューの確認や操作を行えるように @syuilo
- Bull Dashboardを開くには、最初だけ一旦ログアウトしてから再度管理者権限を持つアカウントでログインする必要があります
- Check that installed Node.js version fulfills version requirement @ThatOneCalculator
- Server: overall performance improvements @syuilo
- Federation: avoid duplicate activity delivery @Johann150
- Federation: limit federation of reactions on direct notes @Johann150
- Client: タッチパッド・タッチスクリーンでのデッキの操作性を向上 @tamaina
### Bugfixes
- email address validation was not working @ybw2016v
- API: fix endpoint endpoint @Johann150
- API: fix admin/meta endpoint @syuilo
- API: improved validation and documentation for endpoints that accept different variants of input @Johann150
- API: `notes/create`: The `mediaIds` property is now deprecated. @Johann150
- Use `fileIds` instead, it has the same behaviour.
- Client: URIエンコーディングが異常でdecodeURIComponentが失敗するとURLが表示できなくなる問題を修正 @tamaina
## 12.108.1 (2022/03/12) ## 12.108.1 (2022/03/12)
### Bugfixes ### Bugfixes

View File

@ -6,6 +6,9 @@ Also, you might receive comments on your Issue/PR in Japanese, but you do not ne
The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
It will also allow the reader to use the translation tool of their preference if necessary. It will also allow the reader to use the translation tool of their preference if necessary.
## Roadmap
See [ROADMAP.md](./ROADMAP.md)
## Issues ## Issues
Before creating an issue, please check the following: Before creating an issue, please check the following:
- To avoid duplication, please search for similar issues before creating a new issue. - To avoid duplication, please search for similar issues before creating a new issue.
@ -198,11 +201,13 @@ MongoDBの時とは違い、findOneでレコードを取得する時に対象レ
MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください
### Migration作成方法 ### Migration作成方法
``` packages/backendで:
npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前 -o ```sh
npx typeorm migration:generate -d ormconfig.js -o <migration name>
``` ```
作成されたスクリプトは不必要な変更を含むため除去してください - 生成後、ファイルをmigration下に移してください
- 作成されたスクリプトは不必要な変更を含むため除去してください
### コネクションには`markRaw`せよ ### コネクションには`markRaw`せよ
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。

View File

@ -32,4 +32,3 @@ COPY --from=builder /misskey/packages/client/node_modules ./packages/client/node
COPY . ./ COPY . ./
CMD ["npm", "run", "migrateandstart"] CMD ["npm", "run", "migrateandstart"]

173
README.md
View File

@ -1,168 +1,7 @@
[![Misskey](https://github.com/misskey-dev/assets/blob/main/banner.png?raw=true)](https://join.misskey.page/) ## これってなに?
[Misskey](https://github.com/misskey-dev/misskey)ベースのSNS
<div align="center"> ## 本家との違い
[ここ](https://nullc.at/nca10.net/)にかいてあるよ
**🌎 A forever evolving, interplanetary microblogging platform. 🚀** ## 導入方法
[MisskeyHub](https://misskey-hub.net/docs/install.html)をみてね
**Misskey** is a distributed microblogging platform with advanced features such as Reactions and a highly customizable UI.
[Learn more](https://misskey-hub.net/)
---
[✨ Find an instance](https://misskey-hub.net/instances.html)
[📦 Create your own instance](https://misskey-hub.net/docs/install.html)
[🛠️ Contribute](./CONTRIBUTING.md)
[🚀 Join the community](https://discord.gg/Wp8gVStHW3)
---
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
</div>
<div>
<a href="https://xn--931a.moe/"><img src="https://github.com/misskey-dev/misskey/blob/develop/assets/ai.png?raw=true" align="right" height="320px"/></a>
## ✨ Features
- **ActivityPub support**\
It is possible to interact with other software.
- **Reactions**\
You can add "reactions" to each post, making it easy for you to express your feelings.
- **Drive**\
An interface to manage uploaded files such as images, videos, sounds, etc.
You can also organize your favorite content into folders, making it easy to share again.
- **Rich Web UI**\
Misskey has a rich WebUI by default.
It is highly customizable by flexibly changing the layout and installing various widgets and themes.
Furthermore, plug-ins can be created using AiScript, a original programming language.
- and more...
</div>
<div style="clear: both;"></div>
## Sponsors
<div align="center">
<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>
</div>
## Backers
<!-- PATREON_START -->
<table><tr>
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td>
<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan " width="100"></td>
<td><img src="https://c8.patreon.com/2/200/27648259" alt="みなしま " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td>
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp </a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td>
<td><a href="https://www.patreon.com/user?u=27648259">みなしま </a></td>
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/4.jpg?token-time=2145916800&token-hash=BslMqDjTjz8KYANLvxL87agHTugHa0dMPUzT-hwR6Vk%3D" alt="Nesakko" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Demogrognard" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon " width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/36813045/29876ea679d443bcbba3c3f16edab8c2/2.jpeg?token-time=2145916800&token-hash=YCKWnIhrV9rjUCV9KqtJnEqjy_uGYF3WMXftjUdpi7o%3D" alt="Wataru Manji (manji0)" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
<td><a href="https://www.patreon.com/user?u=776209">Demogrognard</a></td>
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
<td><a href="https://www.patreon.com/user?u=557245">mkatze </a></td>
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y </a></td>
<td><a href="https://www.patreon.com/AureoleArk">AureoleArk </a></td>
<td><a href="https://www.patreon.com/osapon">osapon </a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ </a></td>
<td><a href="https://www.patreon.com/user?u=36813045">Wataru Manji (manji0)</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61 " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/38837364/9421361c54c645ac8f5fc442a40c32e9/1.png?token-time=2145916800&token-hash=TUZB48Nem3BeUPLBH6s3P6WyKBnQOy0xKaDSTBBUNzA%3D" alt="xianon" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td>
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin </a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=38837364">xianon</a></td>
<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26144593/9514b10a5c1b42a3af58621aee213d1d/1.png?token-time=2145916800&token-hash=v1PYRsjzu4c_mndN4Hvi_dlispZJsuGRCQeNS82pUSM%3D" alt="EBISUME" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td>
<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </a></td>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura </a></td>
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
<td><a href="https://www.patreon.com/user?u=9109588">nafuchoco </a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
<td><a href="https://www.patreon.com/user?u=26144593">EBISUME</a></td>
<td><a href="https://www.patreon.com/noellabo">noellabo </a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/14661394" alt="Chandler " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/Corset">CG </a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td>
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
<td><a href="https://www.patreon.com/user?u=14661394">Chandler </a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=23932002">nenohi </a></td>
<td><a href="https://www.patreon.com/efertone">Efertone </a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Sun, 26 Jul 2020 07:00:10 UTC
<!-- PATREON_END -->
[backer-url]: #backers
[backer-badge]: https://opencollective.com/misskey/backers/badge.svg
[backers-image]: https://opencollective.com/misskey/backers.svg
[sponsor-url]: #sponsors
[sponsor-badge]: https://opencollective.com/misskey/sponsors/badge.svg
[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
[support-url]: https://opencollective.com/misskey#support
[syuilo-link]: https://syuilo.com
[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70

36
ROADMAP.md Normal file
View File

@ -0,0 +1,36 @@
# Roadmap
The order of individual tasks is a guide only and is subject to change depending on the situation.
Also, the later tasks are more indefinite and are subject to change as development progresses.
## (1) Improve maintainability \<current phase\>
This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
- Make the number of type errors zero (backend)
- Probably need to switch some libraries to others that make it difficult to reduce type errors
- e.g. koa to fastify https://github.com/misskey-dev/misskey/issues/7537
- Improve CI
- Fix tests
- mocha, jest, etc. do not support the combination of `TypeScript + ESM + Path alias`, and the tests currently do not work.
- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986
- Add more tests
- May need to implement a mechanism that allows for DI
- Improve documentation
## (2) Improve functionality
Once Phase 1 is complete and an environment conducive to the development of a stable system is in place, the implementation of new functions can begin gradually.
- OAuth2 support https://github.com/misskey-dev/misskey/issues/8262
- GraphQL support?
## (3) Improve scalability
Once the development of the feature has settled down, this may be an opportunity to make larger modifications.
- Rewriting in Rust?
## (4) Change the world
It is time to promote Misskey and change the world.
- Become more major than services such as Twitter and become critical infrastructure for the world
- MiOS will be developed and integrated into various systems - What is MiOS?
- Letting Ai-chan interfere with the real world
- Make Misskey a member of GAFA; Misskey's office must be a reinforced concrete brutalist building with a courtyard.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,52 +1,31 @@
version: "3" version: "3"
services: services:
web: db:
build: . restart: always
restart: always image: postgres:12.2-alpine
links: ports:
- db - "5432:5432"
- redis networks:
# - es - internal_network
ports: - external_network
- "127.0.0.1:3000:3000" env_file:
networks: - .config/docker.env
- internal_network volumes:
- external_network - ./db:/var/lib/postgresql/data
volumes:
- ./files:/misskey/files redis:
- ./.config:/misskey/.config:ro restart: always
image: redis:4.0-alpine
redis: ports:
restart: always - "6379:6379"
image: redis:4.0-alpine networks:
networks: - internal_network
- internal_network - external_network
volumes: volumes:
- ./redis:/data - ./redis:/data
db: networks:
restart: always internal_network:
image: postgres:12.2-alpine internal: true
networks: external_network:
- internal_network
env_file:
- .config/docker.env
volumes:
- ./db:/var/lib/postgresql/data
# es:
# restart: always
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
# environment:
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
# - "TAKE_FILE_OWNERSHIP=111"
# networks:
# - internal_network
# volumes:
# - ./elasticsearch:/usr/share/elasticsearch/data
networks:
internal_network:
internal: true
external_network:

View File

@ -0,0 +1,6 @@
`packages/backend/src/server/web/views/base.pug` の中の
```
meta(name='theme-color' content='#好きな色')
meta(name='theme-color-orig' content='#好きな色')
```
の好きな色の部分を変更する(Hex値)

15
explanation/ncatlang.md Normal file
View File

@ -0,0 +1,15 @@
## 主な変更点
- なとナがにゃ
- コピーがコビー
- あなたが僕
- パスワードが鍵
- doneがドネ
- ユーザー名が名前
- バナーがバニャーニャ
- ユーザーネームがid
- コントロールパネルがコンソトールパネル
## ぬるきゃごの導入方法
1. `./locales/ja-JP.yml``ja-NCAT.yml` に変更
2. `ja-NCAT.yml``./locales` のなかに入れる
3. `./locales/inde.js``const languages``ja-NCAT` を追加する

View File

@ -189,7 +189,7 @@ clearCachedFiles: "امسح التخزين المؤقت"
clearCachedFilesConfirm: "أتريد حذف التخزين المؤقت للملفات البعيدة؟" clearCachedFilesConfirm: "أتريد حذف التخزين المؤقت للملفات البعيدة؟"
blockedInstances: "المثلاء المحجوبون" blockedInstances: "المثلاء المحجوبون"
blockedInstancesDescription: "قائمة بالمثلاء التي تريد حظرها بحيث كل نطاق في سطر لوحده. بعد إدراجهم لن يتمكنوا من التفاعل مع هذا المثيل." blockedInstancesDescription: "قائمة بالمثلاء التي تريد حظرها بحيث كل نطاق في سطر لوحده. بعد إدراجهم لن يتمكنوا من التفاعل مع هذا المثيل."
muteAndBlock: "تم كتمها / تم حجبها" muteAndBlock: "المكتومون والمحجوبون"
mutedUsers: "الحسابات المكتومة" mutedUsers: "الحسابات المكتومة"
blockedUsers: "الحسابات المحجوبة" blockedUsers: "الحسابات المحجوبة"
noUsers: "ليس هناك مستخدمون" noUsers: "ليس هناك مستخدمون"
@ -490,7 +490,7 @@ none: "لا شيء"
showInPage: "اعرض في الصفحة" showInPage: "اعرض في الصفحة"
popout: "منبثقة" popout: "منبثقة"
volume: "مستوى الصوت" volume: "مستوى الصوت"
masterVolume: "القرص الرئيسي" masterVolume: "حجم الصوت الرئيس"
details: "التفاصيل" details: "التفاصيل"
chooseEmoji: "اختر إيموجي" chooseEmoji: "اختر إيموجي"
unableToProcess: "يتعذر إكمال العملية" unableToProcess: "يتعذر إكمال العملية"
@ -521,6 +521,7 @@ divider: "فاصل"
addItem: "إضافة عنصر" addItem: "إضافة عنصر"
relays: "المُرَحلات" relays: "المُرَحلات"
addRelay: "إضافة مُرحّل" addRelay: "إضافة مُرحّل"
inboxUrl: "رابط صندوق الوارد"
addedRelays: "المرحلات المضافة" addedRelays: "المرحلات المضافة"
serviceworkerInfo: "يجب أن يفعل لإرسال الإشعارات." serviceworkerInfo: "يجب أن يفعل لإرسال الإشعارات."
deletedNote: "ملاحظة محذوفة" deletedNote: "ملاحظة محذوفة"
@ -533,6 +534,8 @@ enablePlayer: "افتح مشغل الفيديو"
disablePlayer: "أغلق مشغل الفيديو" disablePlayer: "أغلق مشغل الفيديو"
themeEditor: "مصمم القوالب" themeEditor: "مصمم القوالب"
description: "الوصف" description: "الوصف"
describeFile: "أضف تعليقًا توضيحيًا"
enterFileDescription: "أدخل تعليقًا توضيحيًا"
author: "الكاتب" author: "الكاتب"
leaveConfirm: "لديك تغييرات غير محفوظة. أتريد المتابعة دون حفظها؟" leaveConfirm: "لديك تغييرات غير محفوظة. أتريد المتابعة دون حفظها؟"
manage: "إدارة " manage: "إدارة "
@ -564,6 +567,9 @@ smtpPass: "الكلمة السرية"
emptyToDisableSmtpAuth: "اترك اسم المستخدم وكلمة المرور فارغين لتعطيل التحقق من SMTP" emptyToDisableSmtpAuth: "اترك اسم المستخدم وكلمة المرور فارغين لتعطيل التحقق من SMTP"
smtpSecureInfo: "عطل هذا الخيار عند استخدام STARTTLS" smtpSecureInfo: "عطل هذا الخيار عند استخدام STARTTLS"
wordMute: "حظر الكلمات" wordMute: "حظر الكلمات"
regexpError: "خطأ في التعبير النمطي"
instanceMute: "المثلاء المكتومون"
userSaysSomething: "كتب {name} شيءً"
makeActive: "تفعيل" makeActive: "تفعيل"
display: "المظهر" display: "المظهر"
copy: "نسخ" copy: "نسخ"
@ -590,10 +596,16 @@ reportAbuse: "أبلغ"
reportAbuseOf: "أبلغ عن {name}" reportAbuseOf: "أبلغ عن {name}"
fillAbuseReportDescription: "أكتب بالتفصيل سبب البلاغ، إذا كنت تبلغ عن ملاحظة أرفق رابط لها." fillAbuseReportDescription: "أكتب بالتفصيل سبب البلاغ، إذا كنت تبلغ عن ملاحظة أرفق رابط لها."
abuseReported: "أُرسل البلاغ، شكرًا لك" abuseReported: "أُرسل البلاغ، شكرًا لك"
reporter: "المُبلّغ"
reporteeOrigin: "أصل البلاغ"
reporterOrigin: "أصل المُبلّغ"
forwardReport: "وجّه البلاغ إلى المثيل البعيد"
forwardReportIsAnonymous: "في المثيل البعيد سيظهر المبلّغ كحساب مجهول."
send: "أرسل" send: "أرسل"
abuseMarkAsResolved: "علّم البلاغ كمحلول" abuseMarkAsResolved: "علّم البلاغ كمحلول"
openInNewTab: "افتح في لسان جديد" openInNewTab: "افتح في لسان جديد"
defaultNavigationBehaviour: "سلوك الملاحة الافتراضي" defaultNavigationBehaviour: "سلوك الملاحة الافتراضي"
editTheseSettingsMayBreakAccount: "تعديل هذه الإعدادات قد يسبب عطبًا لحسابك"
instanceTicker: "معلومات المثيل الأصلي للملاحظات" instanceTicker: "معلومات المثيل الأصلي للملاحظات"
waitingFor: "في انتظار {x}" waitingFor: "في انتظار {x}"
random: "عشوائي" random: "عشوائي"
@ -624,10 +636,15 @@ no: "لا"
driveFilesCount: "عدد الملفات في قرص التخزين" driveFilesCount: "عدد الملفات في قرص التخزين"
driveUsage: "المستغل من قرص التخزين" driveUsage: "المستغل من قرص التخزين"
noCrawleDescription: "يطلب من محركات البحث ألّا يُفهرسوا ملفك الشخصي وملاحظات وصفحاتك وما شابه." noCrawleDescription: "يطلب من محركات البحث ألّا يُفهرسوا ملفك الشخصي وملاحظات وصفحاتك وما شابه."
alwaysMarkSensitive: "علّم افتراضيًا جميع ملاحظاتي كذات محتوى حساس"
loadRawImages: "حمّل الصور الأصلية بدلًا من المصغرات"
disableShowingAnimatedImages: "لا تشغّل الصور المتحركة" disableShowingAnimatedImages: "لا تشغّل الصور المتحركة"
verificationEmailSent: "أُرسل بريد التحقق. أنقر على الرابط المضمن لإكمال التحقق."
notSet: "لم يعيّن" notSet: "لم يعيّن"
emailVerified: "تُحقّق من بريدك الإلكتروني" emailVerified: "تُحقّق من بريدك الإلكتروني"
noteFavoritesCount: "عدد الملاحظات المفضلة" noteFavoritesCount: "عدد الملاحظات المفضلة"
pageLikesCount: "عدد الصفحات التي أعجبت بها"
pageLikedCount: "عدد صفحاتك المُعجب بها"
contact: "التواصل" contact: "التواصل"
useSystemFont: "استخدم الخط الافتراضية للنظام" useSystemFont: "استخدم الخط الافتراضية للنظام"
clips: "مشابك" clips: "مشابك"
@ -635,6 +652,7 @@ experimentalFeatures: "ميّزات اختبارية"
developer: "المطور" developer: "المطور"
makeExplorable: "أظهر الحساب في صفحة \"استكشاف\"" makeExplorable: "أظهر الحساب في صفحة \"استكشاف\""
makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\"" makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\""
showGapBetweenNotesInTimeline: "أظهر فجوات بين المشاركات في الخيط الزمني"
wide: "عريض" wide: "عريض"
narrow: "رفيع" narrow: "رفيع"
reloadToApplySetting: "سيُطبق هذا الإعداد بعد إعادة تحميل الصفحة، أتريد إعادة تحميلها الآن؟" reloadToApplySetting: "سيُطبق هذا الإعداد بعد إعادة تحميل الصفحة، أتريد إعادة تحميلها الآن؟"
@ -782,6 +800,7 @@ tenMinutes: "10 دقائق"
oneHour: "ساعة" oneHour: "ساعة"
oneDay: "يوم" oneDay: "يوم"
oneWeek: "أسبوع" oneWeek: "أسبوع"
failedToFetchAccountInformation: "تعذر جلب معلومات الحساب"
_emailUnavailable: _emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم" used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة" format: "صيغة البريد الإلكتروني غير صالحة"
@ -860,6 +879,7 @@ _mfm:
centerDescription: "يمركز المحتوى في الوَسَط." centerDescription: "يمركز المحتوى في الوَسَط."
quote: "اقتبس" quote: "اقتبس"
emoji: "إيموجي مخصص" emoji: "إيموجي مخصص"
emojiDescription: "إحاطة اسم الإيموجي بنقطتي تفسير سيستبدله بصورة الإيموجي."
search: "البحث" search: "البحث"
flip: "اقلب" flip: "اقلب"
flipDescription: "يقلب المحتوى عموديًا أو أفقيًا" flipDescription: "يقلب المحتوى عموديًا أو أفقيًا"
@ -871,15 +891,28 @@ _mfm:
jumpDescription: "يمنح للمحتوى حركة قفز." jumpDescription: "يمنح للمحتوى حركة قفز."
bounce: "تأثير (ارتداد)" bounce: "تأثير (ارتداد)"
bounceDescription: "يمنح للمحتوى حركة ارتدادية" bounceDescription: "يمنح للمحتوى حركة ارتدادية"
shake: "تأثير (اهتزاز)"
shakeDescription: "يمنح المحتوى حركة اهتزازية."
spin: "تأثير (دوران)"
spinDescription: "يمنح المحتوى حركة دورانية."
x2: "كبير" x2: "كبير"
x2Description: "يُكبر المحتوى"
x3: "كبير جداً" x3: "كبير جداً"
x3Description: "يُضخم المحتوى"
x4: "هائل"
x4Description: "يُضخم المحتوى أكثر مما سبق."
blur: "طمس" blur: "طمس"
blurDescription: "يطمس المحتوى، لكن بالتمرير فوقه سيظهر بوضوح."
font: "الخط" font: "الخط"
fontDescription: "الخط المستخدم لعرض المحتوى."
rainbow: "قوس قزح" rainbow: "قوس قزح"
rainbowDescription: "اجعل المحتوى يظهر بألوان الطيف" rainbowDescription: "اجعل المحتوى يظهر بألوان الطيف"
rotate: "تدوير" rotate: "تدوير"
rotateDescription: "يُدير المحتوى بزاوية معيّنة."
_instanceTicker: _instanceTicker:
none: "لا تظهره بتاتًا"
remote: "أظهر للمستخدمين البِعاد" remote: "أظهر للمستخدمين البِعاد"
always: "أظهره دائمًا"
_serverDisconnectedBehavior: _serverDisconnectedBehavior:
reload: "إعادة تحميل تلقائية" reload: "إعادة تحميل تلقائية"
dialog: "أظهر مربع حوار التحذيرات" dialog: "أظهر مربع حوار التحذيرات"
@ -899,12 +932,18 @@ _menuDisplay:
hide: "إخفاء" hide: "إخفاء"
_wordMute: _wordMute:
muteWords: "الكلمات المحظورة" muteWords: "الكلمات المحظورة"
muteWordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\"."
muteWordsDescription2: "احصر الكلمات المفتاحية بين بين شرطتين مائلتين لاستخدامها كتعابير نمطية" muteWordsDescription2: "احصر الكلمات المفتاحية بين بين شرطتين مائلتين لاستخدامها كتعابير نمطية"
softDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني." softDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني."
hardDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني.بالإضافة إلى أن هذه الملاحظات ستبقى مخفية حتى وإن تغيرت الشروط." hardDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني.بالإضافة إلى أن هذه الملاحظات ستبقى مخفية حتى وإن تغيرت الشروط."
soft: "لينة" soft: "لينة"
hard: "قاسية" hard: "قاسية"
mutedNotes: "الملاحظات المكتومة" mutedNotes: "الملاحظات المكتومة"
_instanceMute:
instanceMuteDescription: "هذه سيحجب كل ملاحظات الخوادم المحجوبة ومشاركاتها والردود على تلك الملاحظات حتى وإن كانت من خادم غير محجوب."
instanceMuteDescription2: "مدخلة لكل سطر"
title: "يخفي ملاحظات الخوادم المسرودة."
heading: "قائمة الخوادم المحجوبة"
_theme: _theme:
explore: "استكشف قوالب المظهر" explore: "استكشف قوالب المظهر"
install: "تنصيب قالب" install: "تنصيب قالب"
@ -1198,6 +1237,8 @@ _pages:
font: "الخط" font: "الخط"
fontSerif: "Serif" fontSerif: "Serif"
fontSansSerif: "Sans Serif" fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "عيّن صورة مصغّرة"
eyeCatchingImageRemove: "احذف صورة مصغّرة"
chooseBlock: "إضافة كتلة" chooseBlock: "إضافة كتلة"
selectType: "اختر النوع" selectType: "اختر النوع"
enterVariableName: "أدخل اسم المتغيّر" enterVariableName: "أدخل اسم المتغيّر"
@ -1458,6 +1499,7 @@ _notification:
pollVote: "مصوِت شارك في الاستطلاع" pollVote: "مصوِت شارك في الاستطلاع"
receiveFollowRequest: "طلبات المتابعة المتلقاة" receiveFollowRequest: "طلبات المتابعة المتلقاة"
followRequestAccepted: "طلبات المتابعة المقبولة" followRequestAccepted: "طلبات المتابعة المقبولة"
groupInvited: "دعوات الفريق"
app: "إشعارات التطبيقات المرتبطة" app: "إشعارات التطبيقات المرتبطة"
_deck: _deck:
alwaysShowMainColumn: "أظهر العمود الرئيسي دائمًا" alwaysShowMainColumn: "أظهر العمود الرئيسي دائمًا"

View File

@ -832,6 +832,10 @@ size: "আকার"
numberOfColumn: "কলামের সংখ্যা" numberOfColumn: "কলামের সংখ্যা"
searchByGoogle: "গুগল" searchByGoogle: "গুগল"
indefinitely: "অনির্দিষ্ট" indefinitely: "অনির্দিষ্ট"
tenMinutes: "১০ মিনিট"
oneHour: "১ ঘণ্টা"
oneDay: "একদিন"
oneWeek: "এক সপ্তাহ"
_emailUnavailable: _emailUnavailable:
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"

View File

@ -478,8 +478,8 @@ promote: "Werbung schalten"
numberOfDays: "Anzahl der Tage" numberOfDays: "Anzahl der Tage"
hideThisNote: "Diese Notiz verstecken" hideThisNote: "Diese Notiz verstecken"
showFeaturedNotesInTimeline: "Beliebte Notizen in der Chronik anzeigen" showFeaturedNotesInTimeline: "Beliebte Notizen in der Chronik anzeigen"
objectStorage: "Objektspeicher" objectStorage: "Object Storage"
useObjectStorage: "Objektspeicher verwenden" useObjectStorage: "Object Storage verwenden"
objectStorageBaseUrl: "Basis-URL" objectStorageBaseUrl: "Basis-URL"
objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN oder Proxy, gib dessen URL an. Für S3 verwende 'https://<bucket>.s3.amazonaws.com'. Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'." objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN oder Proxy, gib dessen URL an. Für S3 verwende 'https://<bucket>.s3.amazonaws.com'. Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
@ -827,7 +827,7 @@ overridedDeviceKind: "Gerätetyp"
smartphone: "Smartphone" smartphone: "Smartphone"
tablet: "Tablet" tablet: "Tablet"
auto: "Automatisch" auto: "Automatisch"
themeColor: "Instanzfarbe" themeColor: "Farbe der Instanz-Information"
size: "Größe" size: "Größe"
numberOfColumn: "Spaltenanzahl" numberOfColumn: "Spaltenanzahl"
searchByGoogle: "Googlen" searchByGoogle: "Googlen"
@ -840,6 +840,8 @@ tenMinutes: "10 Minuten"
oneHour: "Eine Stunde" oneHour: "Eine Stunde"
oneDay: "Einen Tag" oneDay: "Einen Tag"
oneWeek: "Eine Woche" oneWeek: "Eine Woche"
reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt."
failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden"
_emailUnavailable: _emailUnavailable:
used: "Diese Email-Adresse wird bereits verwendet" used: "Diese Email-Adresse wird bereits verwendet"
format: "Das Format dieser Email-Adresse ist ungültig" format: "Das Format dieser Email-Adresse ist ungültig"

View File

@ -94,6 +94,8 @@ unfollow: "Unfollow"
followRequestPending: "Follow request pending" followRequestPending: "Follow request pending"
enterEmoji: "Enter an emoji" enterEmoji: "Enter an emoji"
renote: "Renote" renote: "Renote"
renoteHomeOnly: "Renote to home only"
renoteFollowersOnly: "Renote to followers only"
unrenote: "Take back renote" unrenote: "Take back renote"
renoted: "Renoted." renoted: "Renoted."
cantRenote: "This post can't be renoted." cantRenote: "This post can't be renoted."
@ -827,7 +829,7 @@ overridedDeviceKind: "Device type"
smartphone: "Smartphone" smartphone: "Smartphone"
tablet: "Tablet" tablet: "Tablet"
auto: "Auto" auto: "Auto"
themeColor: "Theme Color" themeColor: "Instance Ticker Color"
size: "Size" size: "Size"
numberOfColumn: "Number of columns" numberOfColumn: "Number of columns"
searchByGoogle: "Google" searchByGoogle: "Google"
@ -840,6 +842,8 @@ tenMinutes: "10 minutes"
oneHour: "One hour" oneHour: "One hour"
oneDay: "One day" oneDay: "One day"
oneWeek: "One week" oneWeek: "One week"
reflectMayTakeTime: "It may take some time for this to be reflected."
failedToFetchAccountInformation: "Could not fetch account information"
_emailUnavailable: _emailUnavailable:
used: "This email address is already being used" used: "This email address is already being used"
format: "The format of this email address is invalid" format: "The format of this email address is invalid"

File diff suppressed because it is too large Load Diff

View File

@ -1216,7 +1216,7 @@ _poll:
votesCount: "{n} votes" votesCount: "{n} votes"
totalVotes: "{n} votes au total" totalVotes: "{n} votes au total"
vote: "Voter" vote: "Voter"
showResult: "Voir les résultats" showResult: "Voir résultats"
voted: "Déjà voté" voted: "Déjà voté"
closed: "Terminé" closed: "Terminé"
remainingDays: "{d} jours, {h} heures restantes" remainingDays: "{d} jours, {h} heures restantes"

View File

@ -592,6 +592,7 @@ smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP"
smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS" smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
testEmail: "Tes pengiriman surel" testEmail: "Tes pengiriman surel"
wordMute: "Bisukan kata" wordMute: "Bisukan kata"
regexpError: "Kesalahan ekspresi reguler"
instanceMute: "Bisuka instansi" instanceMute: "Bisuka instansi"
userSaysSomething: "{name} mengatakan sesuatu" userSaysSomething: "{name} mengatakan sesuatu"
makeActive: "Aktifkan" makeActive: "Aktifkan"
@ -825,8 +826,20 @@ overridedDeviceKind: "Tipe perangkat"
smartphone: "Ponsel" smartphone: "Ponsel"
tablet: "Tablet" tablet: "Tablet"
auto: "Otomatis" auto: "Otomatis"
themeColor: "Warna Tema"
size: "Ukuran"
numberOfColumn: "Jumlah per kolom"
searchByGoogle: "Penelusuran" searchByGoogle: "Penelusuran"
instanceDefaultLightTheme: "Bawaan instan tema terang"
instanceDefaultDarkTheme: "Bawaan instan tema gelap"
instanceDefaultThemeDescription: "Masukkan kode tema di format obyek."
mutePeriod: "Batas waktu bisu"
indefinitely: "Selamanya" indefinitely: "Selamanya"
tenMinutes: "10 Menit"
oneHour: "1 Jam"
oneDay: "1 Hari"
oneWeek: "1 Bulan"
failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
_emailUnavailable: _emailUnavailable:
used: "Alamat surel ini telah digunakan" used: "Alamat surel ini telah digunakan"
format: "Format tidak valid." format: "Format tidak valid."
@ -1599,6 +1612,7 @@ _notification:
youReceivedFollowRequest: "Kamu menerima permintaan mengikuti" youReceivedFollowRequest: "Kamu menerima permintaan mengikuti"
yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima" yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima"
youWereInvitedToGroup: "Telah diundang ke grup" youWereInvitedToGroup: "Telah diundang ke grup"
pollEnded: "Hasil Kuesioner telah keluar"
_types: _types:
all: "Semua" all: "Semua"
follow: "Ikuti" follow: "Ikuti"
@ -1608,6 +1622,7 @@ _notification:
quote: "Kutip" quote: "Kutip"
reaction: "Reaksi" reaction: "Reaksi"
pollVote: "Memilih di angket" pollVote: "Memilih di angket"
pollEnded: "Jajak pendapat berakhir"
receiveFollowRequest: "Permintaan mengikuti diterima" receiveFollowRequest: "Permintaan mengikuti diterima"
followRequestAccepted: "Permintaan mengikuti disetujui" followRequestAccepted: "Permintaan mengikuti disetujui"
groupInvited: "Diundang ke grup" groupInvited: "Diundang ke grup"

View File

@ -19,7 +19,6 @@ const languages = [
'da-DK', 'da-DK',
'de-DE', 'de-DE',
'en-US', 'en-US',
'eo-UY',
'es-ES', 'es-ES',
'fr-FR', 'fr-FR',
'id-ID', 'id-ID',

File diff suppressed because it is too large Load Diff

View File

@ -119,6 +119,23 @@ unblock: "Deblokkeren"
suspend: "Opschorten" suspend: "Opschorten"
unsuspend: "Heractiveren" unsuspend: "Heractiveren"
blockConfirm: "Weet je zeker dat je dit account wil blokkeren?" blockConfirm: "Weet je zeker dat je dit account wil blokkeren?"
unblockConfirm: "Ben je zeker dat je deze account wil blokkeren?"
suspendConfirm: "Ben je zeker dat je deze account wil suspenderen?"
unsuspendConfirm: "Ben je zeker dat je deze account wil opnieuw aanstellen?"
flagAsBot: "Markeer dit account als een robot."
flagAsBotDescription: "Als dit account van een programma wordt beheerd, zet deze vlag aan. Het aanzetten helpt andere ontwikkelaars om bijvoorbeeld onbedoelde feedback loops te doorbreken of om Misskey meer geschikt te maken."
flagAsCat: "Markeer dit account als een kat."
flagAsCatDescription: "Zet deze vlag aan als je wilt aangeven dat dit account een kat is."
flagShowTimelineReplies: "Toon antwoorden op de tijdlijn."
flagShowTimelineRepliesDescription: "Als je dit vlag aanzet, toont de tijdlijn ook antwoorden op andere en niet alleen jouw eigen notities."
autoAcceptFollowed: "Accepteer verzoeken om jezelf te volgen vanzelf als je de verzoeker al volgt."
addAccount: "Account toevoegen"
loginFailed: "Aanmelding mislukt."
showOnRemote: "Toon op de externe instantie."
general: "Algemeen"
wallpaper: "Achtergrond"
setWallpaper: "Achtergrond instellen"
removeWallpaper: "Achtergrond verwijderen"
searchWith: "Zoeken: {q}" searchWith: "Zoeken: {q}"
youHaveNoLists: "Je hebt geen lijsten" youHaveNoLists: "Je hebt geen lijsten"
followConfirm: "Weet je zeker dat je {name} wilt volgen?" followConfirm: "Weet je zeker dat je {name} wilt volgen?"
@ -205,6 +222,8 @@ resetAreYouSure: "Resetten?"
saved: "Opgeslagen" saved: "Opgeslagen"
messaging: "Chat" messaging: "Chat"
upload: "Uploaden" upload: "Uploaden"
keepOriginalUploading: "Origineel beeld behouden."
keepOriginalUploadingDescription: "Bewaar de originele versie bij het uploaden van afbeeldingen. Indien uitgeschakeld, wordt bij het uploaden een alternatieve versie voor webpublicatie genereert."
fromDrive: "Van schijf" fromDrive: "Van schijf"
fromUrl: "Van URL" fromUrl: "Van URL"
uploadFromUrl: "Uploaden vanaf een URL" uploadFromUrl: "Uploaden vanaf een URL"
@ -245,9 +264,36 @@ renameFile: "Wijzig bestandsnaam"
folderName: "Mapnaam" folderName: "Mapnaam"
createFolder: "Map aanmaken" createFolder: "Map aanmaken"
renameFolder: "Map hernoemen" renameFolder: "Map hernoemen"
deleteFolder: "Map verwijderen"
addFile: "Bestand toevoegen"
emptyDrive: "Jouw Drive is leeg."
emptyFolder: "Deze map is leeg"
unableToDelete: "Kan niet worden verwijderd"
inputNewFileName: "Voer een nieuwe naam in"
copyUrl: "URL kopiëren"
rename: "Hernoemen"
avatar: "Avatar"
banner: "Banner"
nsfw: "NSFW" nsfw: "NSFW"
whenServerDisconnected: "Wanneer de verbinding met de server wordt onderbroken"
disconnectedFromServer: "Verbinding met de server onderbroken."
inMb: "in megabytes"
pinnedNotes: "Vastgemaakte notitie" pinnedNotes: "Vastgemaakte notitie"
userList: "Lijsten" userList: "Lijsten"
aboutMisskey: "Over Misskey"
administrator: "Beheerder"
token: "Token"
securityKeyName: "Sleutelnaam"
registerSecurityKey: "Zekerheids-Sleutel registreren"
lastUsed: "Laatst gebruikt"
unregister: "Uitschrijven"
passwordLessLogin: "Inloggen zonder wachtwoord"
resetPassword: "Wachtwoord terugzetten"
newPasswordIs: "Het nieuwe wachtwoord is „{password}”."
reduceUiAnimation: "Verminder beweging in de UI"
share: "Delen"
notFound: "Niet gevonden"
cacheClear: "Cache verwijderen"
smtpHost: "Server" smtpHost: "Server"
smtpUser: "Gebruikersnaam" smtpUser: "Gebruikersnaam"
smtpPass: "Wachtwoord" smtpPass: "Wachtwoord"

View File

@ -1,6 +1,7 @@
--- ---
_lang_: "Português" _lang_: "Português"
headlineMisskey: "Rede conectada por notas" headlineMisskey: "Rede conectada por notas"
introMisskey: "Bem-vindo! Misskey é um serviço de microblogue descentralizado de código aberto.\nCria \"notas\" e partilha o que te ocorre com todos à tua volta. 📡\nCom \"reações\" podes também expressar logo o que sentes às notas de todos. 👍\nExploremos um novo mundo! 🚀"
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Pesquisar" search: "Pesquisar"
notifications: "Notificações" notifications: "Notificações"
@ -22,6 +23,7 @@ otherSettings: "Outras configurações"
openInWindow: "Abrir numa janela" openInWindow: "Abrir numa janela"
profile: "Perfil" profile: "Perfil"
timeline: "Timeline" timeline: "Timeline"
noAccountDescription: "Este usuário não tem uma descrição."
login: "Iniciar sessão" login: "Iniciar sessão"
loggingIn: "Iniciando sessão…" loggingIn: "Iniciando sessão…"
logout: "Sair" logout: "Sair"
@ -29,8 +31,12 @@ signup: "Registrar-se"
uploading: "Enviando…" uploading: "Enviando…"
save: "Guardar" save: "Guardar"
users: "Usuários" users: "Usuários"
addUser: "Adicionar usuário"
favorite: "Favoritar" favorite: "Favoritar"
favorites: "Favoritar" favorites: "Favoritar"
unfavorite: "Remover dos favoritos"
favorited: "Adicionado aos favoritos."
alreadyFavorited: "Já adicionado aos favoritos."
showMore: "Ver mais" showMore: "Ver mais"
youGotNewFollower: "Você tem um novo seguidor" youGotNewFollower: "Você tem um novo seguidor"
followRequestAccepted: "Pedido de seguir aceito" followRequestAccepted: "Pedido de seguir aceito"

View File

@ -449,6 +449,56 @@ groupInvited: "Ai fost invitat într-un grup"
aboutX: "Despre {x}" aboutX: "Despre {x}"
useOsNativeEmojis: "Folosește emojiuri native OS-ului" useOsNativeEmojis: "Folosește emojiuri native OS-ului"
disableDrawer: "Nu folosi meniuri în stil sertar" disableDrawer: "Nu folosi meniuri în stil sertar"
youHaveNoGroups: "Nu ai niciun grup"
joinOrCreateGroup: "Primește o invitație într-un grup sau creează unul nou."
noHistory: "Nu există istoric"
signinHistory: "Istoric autentificări"
disableAnimatedMfm: "Dezactivează MFM cu animații"
doing: "Se procesează..."
category: "Categorie"
tags: "Etichete"
docSource: "Sursa acestui document"
createAccount: "Creează un cont"
existingAccount: "Cont existent"
regenerate: "Regenerează"
fontSize: "Mărimea fontului"
noFollowRequests: "Nu ai nicio cerere de urmărire în așteptare"
openImageInNewTab: "Deschide imaginile în taburi noi"
dashboard: "Panou de control"
local: "Local"
remote: "Extern"
total: "Total"
weekOverWeekChanges: "Schimbări până săptămâna trecută"
dayOverDayChanges: "Schimbări până ieri"
appearance: "Aspect"
clientSettings: "Setări client"
accountSettings: "Setări cont"
promotion: "Promovat"
promote: "Promovează"
numberOfDays: "Numărul zilelor"
hideThisNote: "Ascunde această notă"
showFeaturedNotesInTimeline: "Arată notele recomandate în cronologii"
objectStorage: "Object Storage"
useObjectStorage: "Folosește Object Storage"
objectStorageBaseUrl: "URL de bază"
objectStorageBaseUrlDesc: "URL-ul este folosit pentru referință. Specifică URL-ul CDN-ului sau Proxy-ului tău dacă folosești unul. Pentru S3 folosește 'https://<bucket>.s3.amazonaws.com' și pentru GCS sau servicii echivalente folosește 'https://storage.googleapis.com/<bucket>', etc."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Te rog specifică numele bucket-ului furnizorului tău."
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Fișierele vor fi stocate sub directoare cu acest prefix."
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Lasă acest câmp gol dacă folosești AWS S3, dacă nu specifică endpoint-ul ca '<host>' sau '<host>:<port>', depinzând de ce serviciu folosești."
objectStorageRegion: "Regiune"
objectStorageRegionDesc: "Specifică o regiune precum 'xx-east-1'. Dacă serviciul tău nu face distincția între regiuni lasă acest câmp gol sau introdu 'us-east-1'."
objectStorageUseSSL: "Folosește SSl"
objectStorageUseSSLDesc: "Oprește această opțiune dacă nu vei folosi HTTPS pentru conexiunile API-ului"
objectStorageUseProxy: "Conectează-te prin Proxy"
objectStorageUseProxyDesc: "Oprește această opțiune dacă vei nu folosi un Proxy pentru conexiunile API-ului"
objectStorageSetPublicRead: "Setează \"public-read\" pentru încărcare"
serverLogs: "Loguri server"
deleteAll: "Șterge tot"
showFixedPostForm: "Arată caseta de postare în vârful cronologie"
newNoteRecived: "Sunt note noi"
sounds: "Sunete" sounds: "Sunete"
listen: "Ascultă" listen: "Ascultă"
none: "Nimic" none: "Nimic"
@ -471,12 +521,54 @@ sort: "Sortează"
ascendingOrder: "Crescător" ascendingOrder: "Crescător"
descendingOrder: "Descrescător" descendingOrder: "Descrescător"
scratchpad: "Scratchpad" scratchpad: "Scratchpad"
scratchpadDescription: "Scratchpad-ul oferă un mediu de experimentare în AiScript. Poți scrie, executa și verifica rezultatele acestuia interacționând cu Misskey în el."
output: "Ieșire"
script: "Script"
disablePagesScript: "Dezactivează AiScript în Pagini"
updateRemoteUser: "Actualizează informațiile utilizatorului extern"
deleteAllFiles: "Șterge toate fișierele"
deleteAllFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele?"
removeAllFollowing: "Dezurmărește toți utilizatorii urmăriți"
removeAllFollowingDescription: "Asta va dez-urmări toate conturile din {host}. Te rog execută asta numai dacă instanța, de ex., nu mai există."
userSuspended: "Acest utilizator a fost suspendat."
userSilenced: "Acest utilizator a fost setat silențios."
yourAccountSuspendedTitle: "Acest cont a fost suspendat"
yourAccountSuspendedDescription: "Acest cont a fost suspendat din cauza încălcării termenilor de serviciu al serverului sau ceva similar. Contactează administratorul dacă ai dori să afli un motiv mai detaliat. Te rog nu crea un cont nou."
menu: "Meniu"
divider: "Separator"
addItem: "Adaugă element"
relays: "Relee"
addRelay: "Adaugă Releu"
inboxUrl: "URL-ul inbox-ului"
addedRelays: "Relee adăugate"
serviceworkerInfo: "Trebuie să fie activat pentru notificări push."
deletedNote: "Notă ștearsă"
invisibleNote: "Note ascunse"
enableInfiniteScroll: "Încarcă mai mult automat"
visibility: "Vizibilitate"
poll: "Sondaj"
useCw: "Ascunde conținutul"
enablePlayer: "Deschide player-ul video"
disablePlayer: "Închide player-ul video"
expandTweet: "Expandează tweet"
themeEditor: "Editor de teme"
description: "Descriere"
describeFile: "Adaugă titrări"
enterFileDescription: "Introdu titrările"
author: "Autor"
leaveConfirm: "Ai schimbări nesalvate. Vrei să renunți la ele?"
manage: "Gestionare"
plugins: "Pluginuri"
deck: "Deck"
undeck: "Părăsește Deck"
useBlurEffectForModal: "Folosește efect de blur pentru modale"
smtpHost: "Gazdă" smtpHost: "Gazdă"
smtpUser: "Nume de utilizator" smtpUser: "Nume de utilizator"
smtpPass: "Parolă" smtpPass: "Parolă"
clearCache: "Golește cache-ul" clearCache: "Golește cache-ul"
info: "Despre" info: "Despre"
user: "Utilizatori" user: "Utilizatori"
administration: "Gestionare"
searchByGoogle: "Caută" searchByGoogle: "Caută"
_email: _email:
_follow: _follow:
@ -487,9 +579,11 @@ _mfm:
emoji: "Emoji personalizat" emoji: "Emoji personalizat"
search: "Caută" search: "Caută"
_theme: _theme:
description: "Descriere"
keys: keys:
mention: "Mențiune" mention: "Mențiune"
renote: "Re-notează" renote: "Re-notează"
divider: "Separator"
_sfx: _sfx:
note: "Note" note: "Note"
notification: "Notificări" notification: "Notificări"

View File

@ -839,6 +839,8 @@ tenMinutes: "10 minút"
oneHour: "1 hodina" oneHour: "1 hodina"
oneDay: "1 deň" oneDay: "1 deň"
oneWeek: "1 týždeň" oneWeek: "1 týždeň"
reflectMayTakeTime: "Zmeny môžu chvíľu trvať kým sa prejavia."
failedToFetchAccountInformation: "Nepodarilo sa načítať informácie o účte."
_emailUnavailable: _emailUnavailable:
used: "Táto emailová adresa sa už používa" used: "Táto emailová adresa sa už používa"
format: "Formát emailovej adresy je nesprávny" format: "Formát emailovej adresy je nesprávny"

View File

@ -8,12 +8,12 @@ notifications: "通知"
username: "用户名" username: "用户名"
password: "密码" password: "密码"
forgotPassword: "忘记密码" forgotPassword: "忘记密码"
fetchingAsApObject: "联合查询" fetchingAsApObject: "在联邦宇宙查询中..."
ok: "OK" ok: "OK"
gotIt: "我明白了" gotIt: "我明白了"
cancel: "取消" cancel: "取消"
enterUsername: "输入用户名" enterUsername: "输入用户名"
renotedBy: "由 {user} 转" renotedBy: "由 {user} 转"
noNotes: "没有帖文" noNotes: "没有帖文"
noNotifications: "无通知" noNotifications: "无通知"
instance: "实例" instance: "实例"
@ -69,7 +69,7 @@ exportRequested: "导出请求已提交,这可能需要花一些时间,导
importRequested: "导入请求已提交,这可能需要花一点时间。" importRequested: "导入请求已提交,这可能需要花一点时间。"
lists: "列表" lists: "列表"
noLists: "列表为空" noLists: "列表为空"
note: "帖" note: "帖"
notes: "帖子" notes: "帖子"
following: "关注中" following: "关注中"
followers: "关注者" followers: "关注者"
@ -85,7 +85,7 @@ serverIsDead: "服务器没有响应。 请稍等片刻,然后重试。"
youShouldUpgradeClient: "请重新加载并使用新版本的客户端查看此页面。" youShouldUpgradeClient: "请重新加载并使用新版本的客户端查看此页面。"
enterListName: "输入列表名称" enterListName: "输入列表名称"
privacy: "隐私" privacy: "隐私"
makeFollowManuallyApprove: "关注者的关注请求需要批准" makeFollowManuallyApprove: "关注请求需要批准"
defaultNoteVisibility: "默认可见性" defaultNoteVisibility: "默认可见性"
follow: "关注" follow: "关注"
followRequest: "关注申请" followRequest: "关注申请"
@ -143,7 +143,7 @@ flagAsCat: "将这个账户设定为一只猫"
flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。" flagAsCatDescription: "如果您想表明此帐户是一只猫,请打开此标志。"
flagShowTimelineReplies: "在时间线上显示帖子的回复" flagShowTimelineReplies: "在时间线上显示帖子的回复"
flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。" flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。"
autoAcceptFollowed: "自动允许关注" autoAcceptFollowed: "自动允许关注者的关注"
addAccount: "添加账户" addAccount: "添加账户"
loginFailed: "登录失败" loginFailed: "登录失败"
showOnRemote: "转到所在实例显示" showOnRemote: "转到所在实例显示"
@ -162,7 +162,7 @@ recipient: "收件人"
annotation: "注解" annotation: "注解"
federation: "联合" federation: "联合"
instances: "实例" instances: "实例"
registeredAt: "初次观" registeredAt: "初次观"
latestRequestSentAt: "上次发送的请求" latestRequestSentAt: "上次发送的请求"
latestRequestReceivedAt: "上次收到的请求" latestRequestReceivedAt: "上次收到的请求"
latestStatus: "最后状态" latestStatus: "最后状态"
@ -254,7 +254,7 @@ agreeTo: "{0}人同意"
tos: "服务条款" tos: "服务条款"
start: "开始" start: "开始"
home: "首页" home: "首页"
remoteUserCaution: "由于是远程用户,信息不完整。" remoteUserCaution: "由于此用户来自其它实例,显示的信息可能不完整。"
activity: "活动" activity: "活动"
images: "图片" images: "图片"
birthday: "生日" birthday: "生日"
@ -372,7 +372,7 @@ recentlyUpdatedUsers: "最近投稿的用户"
recentlyRegisteredUsers: "最近登录的用户" recentlyRegisteredUsers: "最近登录的用户"
recentlyDiscoveredUsers: "最近发现的用户" recentlyDiscoveredUsers: "最近发现的用户"
exploreUsersCount: "有{count}个用户" exploreUsersCount: "有{count}个用户"
exploreFediverse: "探索Fediverse" exploreFediverse: "探索联邦宇宙"
popularTags: "热门标签" popularTags: "热门标签"
userList: "列表" userList: "列表"
about: "关于" about: "关于"
@ -561,7 +561,7 @@ manage: "管理"
plugins: "插件" plugins: "插件"
deck: "Deck" deck: "Deck"
undeck: "取消Deck" undeck: "取消Deck"
useBlurEffectForModal: "模态框使用模糊效果" useBlurEffectForModal: "对话框使用模糊效果"
useFullReactionPicker: "使用全功能的回应工具栏" useFullReactionPicker: "使用全功能的回应工具栏"
width: "宽度" width: "宽度"
height: "高度" height: "高度"
@ -840,6 +840,8 @@ tenMinutes: "10分钟"
oneHour: "1小时" oneHour: "1小时"
oneDay: "1天" oneDay: "1天"
oneWeek: "1周" oneWeek: "1周"
reflectMayTakeTime: "可能需要一些时间才能体现出效果。"
failedToFetchAccountInformation: "获取账户信息失败"
_emailUnavailable: _emailUnavailable:
used: "已经被使用过" used: "已经被使用过"
format: "无效的格式" format: "无效的格式"
@ -904,7 +906,7 @@ _nsfw:
_mfm: _mfm:
cheatSheet: "MFM代码速查表" cheatSheet: "MFM代码速查表"
intro: "MFM是一种在Misskey中的各个位置使用的专用标记语言。在这里您可以看到MFM中可用的语法列表。" intro: "MFM是一种在Misskey中的各个位置使用的专用标记语言。在这里您可以看到MFM中可用的语法列表。"
dummy: "通过Misskey扩展Fediverse的世界" dummy: "通过Misskey扩展联邦宇宙的世界"
mention: "提及" mention: "提及"
mentionDescription: "可以使用 @+用户名 来指示特定用户" mentionDescription: "可以使用 @+用户名 来指示特定用户"
hashtag: "话题标签" hashtag: "话题标签"
@ -967,7 +969,7 @@ _mfm:
rotateDescription: "旋转指定的角度。" rotateDescription: "旋转指定的角度。"
_instanceTicker: _instanceTicker:
none: "不显示" none: "不显示"
remote: "仅显示远程用户" remote: "仅远程用户"
always: "始终显示" always: "始终显示"
_serverDisconnectedBehavior: _serverDisconnectedBehavior:
reload: "自动重载" reload: "自动重载"
@ -1051,7 +1053,7 @@ _theme:
mention: "提及" mention: "提及"
mentionMe: "提及" mentionMe: "提及"
renote: "转发" renote: "转发"
modalBg: "模态框背景" modalBg: "对话框背景"
divider: "分割线" divider: "分割线"
scrollbarHandle: "滚动条" scrollbarHandle: "滚动条"
scrollbarHandleHover: "滚动条(悬停)" scrollbarHandleHover: "滚动条(悬停)"
@ -1238,7 +1240,7 @@ _visibility:
publicDescription: "您的帖子将出现在全局时间线上" publicDescription: "您的帖子将出现在全局时间线上"
home: "首页" home: "首页"
homeDescription: "仅发送至首页的时间线" homeDescription: "仅发送至首页的时间线"
followers: "关注者" followers: "关注者"
followersDescription: "仅发送至关注者" followersDescription: "仅发送至关注者"
specified: "指定用户" specified: "指定用户"
specifiedDescription: "仅发送至指定用户" specifiedDescription: "仅发送至指定用户"

View File

@ -81,6 +81,8 @@ somethingHappened: "發生錯誤"
retry: "重試" retry: "重試"
pageLoadError: "載入頁面失敗" pageLoadError: "載入頁面失敗"
pageLoadErrorDescription: "這通常是因為網路錯誤或是瀏覽器快取殘留的原因。請先清除瀏覽器快取,稍後再重試" pageLoadErrorDescription: "這通常是因為網路錯誤或是瀏覽器快取殘留的原因。請先清除瀏覽器快取,稍後再重試"
serverIsDead: "伺服器沒有回應。請稍等片刻,然後重試。"
youShouldUpgradeClient: "請重新載入以使用新版本的客戶端顯示此頁面"
enterListName: "輸入清單名稱" enterListName: "輸入清單名稱"
privacy: "隱私" privacy: "隱私"
makeFollowManuallyApprove: "手動審核追隨請求" makeFollowManuallyApprove: "手動審核追隨請求"
@ -104,6 +106,7 @@ clickToShow: "按一下以顯示"
sensitive: "敏感內容" sensitive: "敏感內容"
add: "新增" add: "新增"
reaction: "情感" reaction: "情感"
reactionSetting: "在選擇器中顯示反應"
reactionSettingDescription2: "拖動以重新列序,點擊以刪除,按下 + 添加。" reactionSettingDescription2: "拖動以重新列序,點擊以刪除,按下 + 添加。"
rememberNoteVisibility: "記住貼文可見性" rememberNoteVisibility: "記住貼文可見性"
attachCancel: "移除附件" attachCancel: "移除附件"
@ -138,6 +141,7 @@ flagAsBot: "此使用者是機器人"
flagAsBotDescription: "如果本帳戶是由程式控制請啟用此選項。啟用後會作為標示幫助其他開發者防止機器人之間產生無限互動的行為並會調整Misskey內部系統將本帳戶識別為機器人" flagAsBotDescription: "如果本帳戶是由程式控制請啟用此選項。啟用後會作為標示幫助其他開發者防止機器人之間產生無限互動的行為並會調整Misskey內部系統將本帳戶識別為機器人"
flagAsCat: "此使用者是貓" flagAsCat: "此使用者是貓"
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示" flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
autoAcceptFollowed: "自動追隨中使用者的追隨請求" autoAcceptFollowed: "自動追隨中使用者的追隨請求"
addAccount: "添加帳戶" addAccount: "添加帳戶"
loginFailed: "登入失敗" loginFailed: "登入失敗"
@ -599,6 +603,9 @@ reportAbuse: "檢舉"
reportAbuseOf: "檢舉{name}" reportAbuseOf: "檢舉{name}"
fillAbuseReportDescription: "請填寫檢舉的詳細理由。可以的話請附上針對的URL網址。" fillAbuseReportDescription: "請填寫檢舉的詳細理由。可以的話請附上針對的URL網址。"
abuseReported: "回報已送出。感謝您的報告。" abuseReported: "回報已送出。感謝您的報告。"
reporter: "檢舉者"
reporteeOrigin: "檢舉來源"
reporterOrigin: "檢舉者來源"
send: "發送" send: "發送"
abuseMarkAsResolved: "處理完畢" abuseMarkAsResolved: "處理完畢"
openInNewTab: "在新分頁中開啟" openInNewTab: "在新分頁中開啟"
@ -734,6 +741,7 @@ postToGallery: "發佈到相簿"
gallery: "相簿" gallery: "相簿"
recentPosts: "最新貼文" recentPosts: "最新貼文"
popularPosts: "熱門的貼文" popularPosts: "熱門的貼文"
shareWithNote: "在貼文中分享"
ads: "廣告" ads: "廣告"
expiration: "期限" expiration: "期限"
memo: "備忘錄" memo: "備忘錄"
@ -743,14 +751,35 @@ middle: "中"
low: "低" low: "低"
emailNotConfiguredWarning: "沒有設定電子郵件地址" emailNotConfiguredWarning: "沒有設定電子郵件地址"
ratio: "%" ratio: "%"
previewNoteText: "預覽文本"
customCss: "自定義 CSS"
global: "公開" global: "公開"
sent: "發送" sent: "發送"
received: "收取"
searchResult: "搜尋結果"
hashtags: "#tag" hashtags: "#tag"
troubleshooting: "故障排除"
useBlurEffect: "在 UI 上使用模糊效果"
misskeyUpdated: "Misskey 更新完成!"
translate: "翻譯"
translatedFrom: "從 {x} 翻譯"
accountDeletionInProgress: "正在刪除帳戶"
pubSub: "Pub/Sub 帳戶"
resolved: "已解決"
unresolved: "未解決"
breakFollow: "移除追蹤者"
hide: "隱藏" hide: "隱藏"
leaveGroupConfirm: "確定離開「{name}」?"
auto: "自動"
searchByGoogle: "搜尋" searchByGoogle: "搜尋"
indefinitely: "無期限" indefinitely: "無期限"
_ffVisibility: _ffVisibility:
public: "發佈" public: "發佈"
private: "私密"
_signup:
almostThere: "即將完成"
_accountDelete:
inProgress: "正在刪除"
_ad: _ad:
back: "返回" back: "返回"
reduceFrequencyOfThisAd: "降低此廣告的頻率 " reduceFrequencyOfThisAd: "降低此廣告的頻率 "

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "12.108.1", "version": "12.110.1-nca10.net-v1",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -13,8 +13,7 @@
"start": "cd packages/backend && node --experimental-json-modules ./built/index.js", "start": "cd packages/backend && node --experimental-json-modules ./built/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js",
"init": "npm run migrate", "init": "npm run migrate",
"ormconfig": "node ./packages/backend/ormconfig.js", "migrate": "cd packages/backend && npx typeorm migration:run -d ormconfig.js",
"migrate": "cd packages/backend && npx typeorm migration:run",
"migrateandstart": "npm run migrate && npm run start", "migrateandstart": "npm run migrate && npm run start",
"gulp": "gulp build", "gulp": "gulp build",
"watch": "npm run dev", "watch": "npm run dev",
@ -42,10 +41,10 @@
"js-yaml": "4.1.0" "js-yaml": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "5.14.0", "@typescript-eslint/parser": "5.18.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "9.5.0", "cypress": "9.5.3",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"typescript": "4.6.2" "typescript": "4.6.3"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -0,0 +1,19 @@
export class webhook1648548247382 {
name = 'webhook1648548247382'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "webhook" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "on" character varying(128) array NOT NULL DEFAULT '{}', "url" character varying(1024) NOT NULL, "secret" character varying(1024) NOT NULL, "active" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_e6765510c2d078db49632b59020" PRIMARY KEY ("id")); COMMENT ON COLUMN "webhook"."createdAt" IS 'The created date of the Antenna.'; COMMENT ON COLUMN "webhook"."userId" IS 'The owner ID.'; COMMENT ON COLUMN "webhook"."name" IS 'The name of the Antenna.'`);
await queryRunner.query(`CREATE INDEX "IDX_f272c8c8805969e6a6449c77b3" ON "webhook" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_8063a0586ed1dfbe86e982d961" ON "webhook" ("on") `);
await queryRunner.query(`CREATE INDEX "IDX_5a056076f76b2efe08216ba655" ON "webhook" ("active") `);
await queryRunner.query(`ALTER TABLE "webhook" ADD CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "webhook" DROP CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_5a056076f76b2efe08216ba655"`);
await queryRunner.query(`DROP INDEX "public"."IDX_8063a0586ed1dfbe86e982d961"`);
await queryRunner.query(`DROP INDEX "public"."IDX_f272c8c8805969e6a6449c77b3"`);
await queryRunner.query(`DROP TABLE "webhook"`);
}
}

View File

@ -0,0 +1,14 @@
export class webhook21648816172177 {
name = 'webhook21648816172177'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "webhook" ADD "latestSentAt" TIMESTAMP WITH TIME ZONE`);
await queryRunner.query(`ALTER TABLE "webhook" ADD "latestStatus" integer`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestStatus"`);
await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestSentAt"`);
}
}

View File

@ -1,7 +1,8 @@
import { DataSource } from 'typeorm';
import config from './built/config/index.js'; import config from './built/config/index.js';
import { entities } from './built/db/postgre.js'; import { entities } from './built/db/postgre.js';
export default { export default new DataSource({
type: 'postgres', type: 'postgres',
host: config.db.host, host: config.db.host,
port: config.db.port, port: config.db.port,
@ -11,7 +12,4 @@ export default {
extra: config.db.extra, extra: config.db.extra,
entities: entities, entities: entities,
migrations: ['migration/*.js'], migrations: ['migration/*.js'],
cli: { });
migrationsDir: 'migration'
}
};

View File

@ -3,10 +3,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"init": "npm run migrate",
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs", "watch": "node watch.mjs",
"lint": "eslint --quiet src/**/*.ts", "lint": "eslint --quiet \"src/**/*.ts\"",
"mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"test": "npm run mocha" "test": "npm run mocha"
}, },
@ -15,7 +14,7 @@
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"dependencies": { "dependencies": {
"@discordapp/twemoji": "13.1.0", "@discordapp/twemoji": "13.1.1",
"@elastic/elasticsearch": "7.11.0", "@elastic/elasticsearch": "7.11.0",
"@koa/cors": "3.1.0", "@koa/cors": "3.1.0",
"@koa/multer": "3.0.0", "@koa/multer": "3.0.0",
@ -31,7 +30,7 @@
"@types/jsdom": "16.2.14", "@types/jsdom": "16.2.14",
"@types/jsonld": "1.5.6", "@types/jsonld": "1.5.6",
"@types/koa": "2.13.4", "@types/koa": "2.13.4",
"@types/koa-bodyparser": "4.3.6", "@types/koa-bodyparser": "4.3.7",
"@types/koa-cors": "0.0.2", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.2", "@types/koa-logger": "3.1.2",
@ -42,7 +41,7 @@
"@types/koa__multer": "2.0.4", "@types/koa__multer": "2.0.4",
"@types/koa__router": "8.0.11", "@types/koa__router": "8.0.11",
"@types/mocha": "9.1.0", "@types/mocha": "9.1.0",
"@types/node": "17.0.21", "@types/node": "17.0.23",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@ -56,28 +55,28 @@
"@types/redis": "4.0.11", "@types/redis": "4.0.11",
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/sanitize-html": "2.6.2", "@types/sanitize-html": "2.6.2",
"@types/sharp": "0.29.5", "@types/sharp": "0.30.1",
"@types/sinonjs__fake-timers": "8.1.1", "@types/sinonjs__fake-timers": "8.1.2",
"@types/speakeasy": "2.0.7", "@types/speakeasy": "2.0.7",
"@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"@types/uuid": "8.3.4", "@types/uuid": "8.3.4",
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.2", "@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.14.0", "@typescript-eslint/eslint-plugin": "5.18.0",
"@typescript-eslint/parser": "5.14.0", "@typescript-eslint/parser": "5.18.0",
"@bull-board/koa": "3.10.3",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"ajv": "8.10.0", "ajv": "8.11.0",
"archiver": "5.3.0", "archiver": "5.3.0",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1079.0", "aws-sdk": "2.1111.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.5", "blurhash": "1.1.5",
"broadcast-channel": "4.10.0", "broadcast-channel": "4.10.0",
"bull": "4.7.0", "bull": "4.8.1",
"cacheable-lookup": "6.0.4", "cacheable-lookup": "6.0.4",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "8.1.0", "cbor": "8.1.0",
@ -89,19 +88,19 @@
"date-fns": "2.28.0", "date-fns": "2.28.0",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "8.10.0", "eslint": "8.13.0",
"eslint-plugin-import": "2.25.4", "eslint-plugin-import": "2.26.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "17.1.1", "file-type": "17.1.1",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"got": "12.0.1", "got": "12.0.3",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"http-signature": "1.3.6", "http-signature": "1.3.6",
"ip-cidr": "3.0.4", "ip-cidr": "3.0.4",
"is-svg": "4.3.2", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "19.0.0", "jsdom": "19.0.0",
"json5": "2.2.0", "json5": "2.2.1",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"jsonld": "5.2.0", "jsonld": "5.2.0",
"jsrsasign": "8.0.20", "jsrsasign": "8.0.20",
@ -115,14 +114,14 @@
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.2", "koa-views": "7.0.2",
"mfm-js": "0.21.0", "mfm-js": "0.21.0",
"mime-types": "2.1.34", "mime-types": "2.1.35",
"misskey-js": "0.0.14", "misskey-js": "0.0.14",
"mocha": "9.2.1", "mocha": "9.2.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"multer": "1.4.4", "multer": "1.4.4",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.2.2", "node-fetch": "3.2.3",
"nodemailer": "6.7.2", "nodemailer": "6.7.3",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.7.3", "pg": "8.7.3",
@ -145,24 +144,24 @@
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sanitize-html": "2.7.0", "sanitize-html": "2.7.0",
"sharp": "0.30.2", "semver": "7.3.6",
"sharp": "0.30.3",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"style-loader": "3.3.1", "style-loader": "3.3.1",
"summaly": "2.5.0", "summaly": "2.5.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.11.6", "systeminformation": "5.11.9",
"throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "9.2.7", "ts-loader": "9.2.8",
"ts-node": "10.7.0", "ts-node": "10.7.0",
"tsc-alias": "1.4.1", "tsc-alias": "1.4.1",
"tsconfig-paths": "3.13.0", "tsconfig-paths": "3.14.1",
"twemoji-parser": "13.1.0", "twemoji-parser": "14.0.0",
"typeorm": "0.2.45", "typeorm": "0.3.5",
"typescript": "4.6.2", "typescript": "4.6.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11", "unzipper": "0.10.11",
"uuid": "8.3.2", "uuid": "8.3.2",
@ -172,7 +171,7 @@
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.83", "@redocly/openapi-core": "1.0.0-beta.93",
"@types/fluent-ffmpeg": "2.1.20", "@types/fluent-ffmpeg": "2.1.20",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"execa": "6.1.0" "execa": "6.1.0"

View File

@ -6,7 +6,7 @@ import cluster from 'node:cluster';
import chalk from 'chalk'; import chalk from 'chalk';
import chalkTemplate from 'chalk-template'; import chalkTemplate from 'chalk-template';
import * as portscanner from 'portscanner'; import * as portscanner from 'portscanner';
import { getConnection } from 'typeorm'; import semver from 'semver';
import Logger from '@/services/logger.js'; import Logger from '@/services/logger.js';
import loadConfig from '@/config/load.js'; import loadConfig from '@/config/load.js';
@ -14,7 +14,7 @@ import { Config } from '@/config/types.js';
import { lessThan } from '@/prelude/array.js'; import { lessThan } from '@/prelude/array.js';
import { envOption } from '../env.js'; import { envOption } from '../env.js';
import { showMachineInfo } from '@/misc/show-machine-info.js'; import { showMachineInfo } from '@/misc/show-machine-info.js';
import { initDb } from '../db/postgre.js'; import { db, initDb } from '../db/postgre.js';
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename); const _dirname = dirname(_filename);
@ -88,10 +88,6 @@ export async function masterMain() {
} }
} }
const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10));
const requiredNodejsVersion = [11, 7, 0];
const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion);
function showEnvironment(): void { function showEnvironment(): void {
const env = process.env.NODE_ENV; const env = process.env.NODE_ENV;
const logger = bootLogger.createSubLogger('env'); const logger = bootLogger.createSubLogger('env');
@ -108,10 +104,11 @@ function showEnvironment(): void {
function showNodejsVersion(): void { function showNodejsVersion(): void {
const nodejsLogger = bootLogger.createSubLogger('nodejs'); const nodejsLogger = bootLogger.createSubLogger('nodejs');
nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`); nodejsLogger.info(`Version ${process.version} detected.`);
if (!satisfyNodejsVersion) { const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, 'utf-8').trim();
nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true); if (semver.lt(process.version, minVersion)) {
nodejsLogger.error(`At least Node.js ${minVersion} required!`);
process.exit(1); process.exit(1);
} }
} }
@ -146,7 +143,7 @@ async function connectDb(): Promise<void> {
try { try {
dbLogger.info('Connecting...'); dbLogger.info('Connecting...');
await initDb(); await initDb();
const v = await getConnection().query('SHOW server_version').then(x => x[0].server_version); const v = await db.query('SHOW server_version').then(x => x[0].server_version);
dbLogger.succ(`Connected: v${v}`); dbLogger.succ(`Connected: v${v}`);
} catch (e) { } catch (e) {
dbLogger.error('Cannot connect', null, true); dbLogger.error('Cannot connect', null, true);

View File

@ -2,9 +2,10 @@
import pg from 'pg'; import pg from 'pg';
pg.types.setTypeParser(20, Number); pg.types.setTypeParser(20, Number);
import { createConnection, Logger, getConnection } from 'typeorm'; import { Logger, DataSource } from 'typeorm';
import * as highlight from 'cli-highlight'; import * as highlight from 'cli-highlight';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { envOption } from '../env.js';
import { dbLogger } from './logger.js'; import { dbLogger } from './logger.js';
@ -61,7 +62,6 @@ import { Antenna } from '@/models/entities/antenna.js';
import { AntennaNote } from '@/models/entities/antenna-note.js'; import { AntennaNote } from '@/models/entities/antenna-note.js';
import { PromoNote } from '@/models/entities/promo-note.js'; import { PromoNote } from '@/models/entities/promo-note.js';
import { PromoRead } from '@/models/entities/promo-read.js'; import { PromoRead } from '@/models/entities/promo-read.js';
import { envOption } from '../env.js';
import { Relay } from '@/models/entities/relay.js'; import { Relay } from '@/models/entities/relay.js';
import { MutedNote } from '@/models/entities/muted-note.js'; import { MutedNote } from '@/models/entities/muted-note.js';
import { Channel } from '@/models/entities/channel.js'; import { Channel } from '@/models/entities/channel.js';
@ -73,8 +73,9 @@ import { PasswordResetRequest } from '@/models/entities/password-reset-request.j
import { UserPending } from '@/models/entities/user-pending.js'; import { UserPending } from '@/models/entities/user-pending.js';
import { entities as charts } from '@/services/chart/entities.js'; import { entities as charts } from '@/services/chart/entities.js';
import { Webhook } from '@/models/entities/webhook.js';
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
class MyCustomLogger implements Logger { class MyCustomLogger implements Logger {
private highlight(sql: string) { private highlight(sql: string) {
@ -84,9 +85,7 @@ class MyCustomLogger implements Logger {
} }
public logQuery(query: string, parameters?: any[]) { public logQuery(query: string, parameters?: any[]) {
if (envOption.verbose) { sqlLogger.info(this.highlight(query).substring(0, 100));
sqlLogger.info(this.highlight(query));
}
} }
public logQueryError(error: string, query: string, parameters?: any[]) { public logQueryError(error: string, query: string, parameters?: any[]) {
@ -173,58 +172,59 @@ export const entities = [
Ad, Ad,
PasswordResetRequest, PasswordResetRequest,
UserPending, UserPending,
Webhook,
...charts, ...charts,
]; ];
export function initDb(justBorrow = false, sync = false, forceRecreate = false) { const log = process.env.NODE_ENV !== 'production';
if (!forceRecreate) {
try {
const conn = getConnection();
return Promise.resolve(conn);
} catch (e) {}
}
const log = process.env.NODE_ENV !== 'production'; export const db = new DataSource({
type: 'postgres',
return createConnection({ host: config.db.host,
type: 'postgres', port: config.db.port,
host: config.db.host, username: config.db.user,
port: config.db.port, password: config.db.pass,
username: config.db.user, database: config.db.db,
password: config.db.pass, extra: {
database: config.db.db, statement_timeout: 1000 * 10,
extra: { ...config.db.extra,
statement_timeout: 1000 * 10, },
...config.db.extra, synchronize: process.env.NODE_ENV === 'test',
dropSchema: process.env.NODE_ENV === 'test',
cache: !config.db.disableCache ? {
type: 'redis',
options: {
host: config.redis.host,
port: config.redis.port,
password: config.redis.pass,
prefix: `${config.redis.prefix}:query:`,
db: config.redis.db || 0,
}, },
synchronize: process.env.NODE_ENV === 'test' || sync, } : false,
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, logging: log,
cache: !config.db.disableCache ? { logger: log ? new MyCustomLogger() : undefined,
type: 'redis', maxQueryExecutionTime: 300,
options: { entities: entities,
host: config.redis.host, migrations: ['../../migration/*.js'],
port: config.redis.port, });
password: config.redis.pass,
prefix: `${config.redis.prefix}:query:`, export async function initDb() {
db: config.redis.db || 0, if (db.isInitialized) {
}, // nop
} : false, } else {
logging: log, await db.connect();
logger: log ? new MyCustomLogger() : undefined, }
entities: entities,
});
} }
export async function resetDb() { export async function resetDb() {
const reset = async () => { const reset = async () => {
const conn = await getConnection(); const tables = await db.query(`SELECT relname AS "table"
const tables = await conn.query(`SELECT relname AS "table"
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema') WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind = 'r' AND C.relkind = 'r'
AND nspname !~ '^pg_toast';`); AND nspname !~ '^pg_toast';`);
for (const table of tables) { for (const table of tables) {
await conn.query(`DELETE FROM "${table.table}" CASCADE`); await db.query(`DELETE FROM "${table.table}" CASCADE`);
} }
}; };

View File

@ -1,5 +1,5 @@
export class Cache<T> { export class Cache<T> {
private cache: Map<string | null, { date: number; value: T; }>; public cache: Map<string | null, { date: number; value: T; }>;
private lifetime: number; private lifetime: number;
constructor(lifetime: Cache<never>['lifetime']) { constructor(lifetime: Cache<never>['lifetime']) {
@ -28,16 +28,52 @@ export class Cache<T> {
this.cache.delete(key); this.cache.delete(key);
} }
public async fetch(key: string | null, fetcher: () => Promise<T>): Promise<T> { /**
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
const cachedValue = this.get(key); const cachedValue = this.get(key);
if (cachedValue !== undefined) { if (cachedValue !== undefined) {
// Cache HIT if (validator) {
return cachedValue; if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
} }
// Cache MISS // Cache MISS
const value = await fetcher(); const value = await fetcher();
this.set(key, value); return value;
}
/**
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
if (value !== undefined) {
this.set(key, value);
}
return value; return value;
} }
} }

View File

@ -42,7 +42,8 @@ async function getCaptchaResponse(url: string, secret: string, response: string)
headers: { headers: {
'User-Agent': config.userAgent, 'User-Agent': config.userAgent,
}, },
timeout: 10 * 1000, // TODO
//timeout: 10 * 1000,
agent: getAgentByUrl, agent: getAgentByUrl,
}).catch(e => { }).catch(e => {
throw `${e.message || e}`; throw `${e.message || e}`;

View File

@ -1,17 +1,26 @@
import { Antenna } from '@/models/entities/antenna.js'; import { Antenna } from '@/models/entities/antenna.js';
import { Note } from '@/models/entities/note.js'; import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
import { UserListJoinings, UserGroupJoinings } from '@/models/index.js'; import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js';
import { getFullApAccount } from './convert-host.js'; import { getFullApAccount } from './convert-host.js';
import * as Acct from '@/misc/acct.js'; import * as Acct from '@/misc/acct.js';
import { Packed } from './schema.js'; import { Packed } from './schema.js';
import { Cache } from './cache.js';
const blockingCache = new Cache<User['id'][]>(1000 * 60 * 5);
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
/** /**
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
*/ */
export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> { export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
if (note.visibility === 'specified') return false; if (note.visibility === 'specified') return false;
// アンテナ作成者がノート作成者にブロックされていたらスキップ
const blockings = await blockingCache.fetch(noteUser.id, () => Blockings.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
if (blockings.some(blocking => blocking === antenna.userId)) return false;
if (note.visibility === 'followers') { if (note.visibility === 'followers') {
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
@ -23,15 +32,15 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
} else if (antenna.src === 'list') { } else if (antenna.src === 'list') {
const listUsers = (await UserListJoinings.find({ const listUsers = (await UserListJoinings.findBy({
userListId: antenna.userListId!, userListId: antenna.userListId!,
})).map(x => x.userId); })).map(x => x.userId);
if (!listUsers.includes(note.userId)) return false; if (!listUsers.includes(note.userId)) return false;
} else if (antenna.src === 'group') { } else if (antenna.src === 'group') {
const joining = await UserGroupJoinings.findOneOrFail(antenna.userGroupJoiningId!); const joining = await UserGroupJoinings.findOneByOrFail({ id: antenna.userGroupJoiningId! });
const groupUsers = (await UserGroupJoinings.find({ const groupUsers = (await UserGroupJoinings.findBy({
userGroupId: joining.userGroupId, userGroupId: joining.userGroupId,
})).map(x => x.userId); })).map(x => x.userId);

View File

@ -1,19 +1,21 @@
import { db } from '@/db/postgre.js';
import { Meta } from '@/models/entities/meta.js'; import { Meta } from '@/models/entities/meta.js';
import { getConnection } from 'typeorm';
let cache: Meta; let cache: Meta;
export async function fetchMeta(noCache = false): Promise<Meta> { export async function fetchMeta(noCache = false): Promise<Meta> {
if (!noCache && cache) return cache; if (!noCache && cache) return cache;
return await getConnection().transaction(async transactionalEntityManager => { return await db.transaction(async transactionalEntityManager => {
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
const meta = await transactionalEntityManager.findOne(Meta, { const metas = await transactionalEntityManager.find(Meta, {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
}); });
const meta = metas[0];
if (meta) { if (meta) {
cache = meta; cache = meta;
return meta; return meta;

View File

@ -5,5 +5,5 @@ import { Users } from '@/models/index.js';
export async function fetchProxyAccount(): Promise<ILocalUser | null> { export async function fetchProxyAccount(): Promise<ILocalUser | null> {
const meta = await fetchMeta(); const meta = await fetchMeta();
if (meta.proxyAccountId == null) return null; if (meta.proxyAccountId == null) return null;
return await Users.findOneOrFail(meta.proxyAccountId) as ILocalUser; return await Users.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser;
} }

View File

@ -120,9 +120,9 @@ export const httpsAgent = config.proxy
*/ */
export function getAgentByUrl(url: URL, bypassProxy = false) { export function getAgentByUrl(url: URL, bypassProxy = false) {
if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) { if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
return url.protocol == 'http:' ? _http : _https; return url.protocol === 'http:' ? _http : _https;
} else { } else {
return url.protocol == 'http:' ? httpAgent : httpsAgent; return url.protocol === 'http:' ? httpAgent : httpsAgent;
} }
} }

View File

@ -23,7 +23,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
} }
// ファイルが添付されているとき // ファイルが添付されているとき
if ((note.files || []).length != 0) { if ((note.files || []).length !== 0) {
summary += ` (📎${note.files!.length})`; summary += ` (📎${note.files!.length})`;
} }

View File

@ -6,5 +6,5 @@ import { Cache } from './cache.js';
const cache = new Cache<UserKeypair>(Infinity); const cache = new Cache<UserKeypair>(Infinity);
export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> { export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> {
return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId)); return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId: userId }));
} }

View File

@ -1,4 +1,4 @@
import { In } from 'typeorm'; import { In, IsNull } from 'typeorm';
import { Emojis } from '@/models/index.js'; import { Emojis } from '@/models/index.js';
import { Emoji } from '@/models/entities/emoji.js'; import { Emoji } from '@/models/entities/emoji.js';
import { Note } from '@/models/entities/note.js'; import { Note } from '@/models/entities/note.js';
@ -52,9 +52,9 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
const { name, host } = parseEmojiStr(emojiName, noteUserHost); const { name, host } = parseEmojiStr(emojiName, noteUserHost);
if (name == null) return null; if (name == null) return null;
const queryOrNull = async () => (await Emojis.findOne({ const queryOrNull = async () => (await Emojis.findOneBy({
name, name,
host, host: host ?? IsNull(),
})) || null; })) || null;
const emoji = await cache.fetch(`${name} ${host}`, queryOrNull); const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
@ -112,7 +112,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
for (const host of hosts) { for (const host of hosts) {
emojisQuery.push({ emojisQuery.push({
name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)),
host: host, host: host ?? IsNull(),
}); });
} }
const _emojis = emojisQuery.length > 0 ? await Emojis.find({ const _emojis = emojisQuery.length > 0 ? await Emojis.find({

View File

@ -3,6 +3,7 @@ import { emojiRegex } from './emoji-regex.js';
import { fetchMeta } from './fetch-meta.js'; import { fetchMeta } from './fetch-meta.js';
import { Emojis } from '@/models/index.js'; import { Emojis } from '@/models/index.js';
import { toPunyNullable } from './convert-host.js'; import { toPunyNullable } from './convert-host.js';
import { IsNull } from 'typeorm';
const legacies: Record<string, string> = { const legacies: Record<string, string> = {
'like': '👍', 'like': '👍',
@ -74,8 +75,8 @@ export async function toDbReaction(reaction?: string | null, reacterHost?: strin
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/); const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
if (custom) { if (custom) {
const name = custom[1]; const name = custom[1];
const emoji = await Emojis.findOne({ const emoji = await Emojis.findOneBy({
host: reacterHost || null, host: reacterHost ?? IsNull(),
name, name,
}); });

View File

@ -0,0 +1,49 @@
import { Webhooks } from '@/models/index.js';
import { Webhook } from '@/models/entities/webhook.js';
import { subsdcriber } from '../db/redis.js';
let webhooksFetched = false;
let webhooks: Webhook[] = [];
export async function getActiveWebhooks() {
if (!webhooksFetched) {
webhooks = await Webhooks.findBy({
active: true,
});
webhooksFetched = true;
}
return webhooks;
}
subsdcriber.on('message', async (_, data) => {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message;
switch (type) {
case 'webhookCreated':
if (body.active) {
webhooks.push(body);
}
break;
case 'webhookUpdated':
if (body.active) {
const i = webhooks.findIndex(a => a.id === body.id);
if (i > -1) {
webhooks[i] = body;
} else {
webhooks.push(body);
}
} else {
webhooks = webhooks.filter(a => a.id !== body.id);
}
break;
case 'webhookDeleted':
webhooks = webhooks.filter(a => a.id !== body.id);
break;
default:
break;
}
}
});

View File

@ -1,6 +1,6 @@
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { DriveFile } from './drive-file.js';
import { id } from '../id.js'; import { id } from '../id.js';
import { DriveFile } from './drive-file.js';
@Entity() @Entity()
@Index(['usernameLower', 'host'], { unique: true }) @Index(['usernameLower', 'host'], { unique: true })
@ -207,7 +207,7 @@ export class User {
@Column('boolean', { @Column('boolean', {
default: false, default: false,
comment: 'Whether to show users replying to other users in the timeline' comment: 'Whether to show users replying to other users in the timeline',
}) })
public showTimelineReplies: boolean; public showTimelineReplies: boolean;
@ -234,3 +234,9 @@ export interface ILocalUser extends User {
export interface IRemoteUser extends User { export interface IRemoteUser extends User {
host: string; host: string;
} }
export type CacheableLocalUser = ILocalUser;
export type CacheableRemoteUser = IRemoteUser;
export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;

View File

@ -0,0 +1,73 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user.js';
import { id } from '../id.js';
export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
@Entity()
export class Webhook {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
comment: 'The created date of the Antenna.',
})
public createdAt: Date;
@Index()
@Column({
...id(),
comment: 'The owner ID.',
})
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Column('varchar', {
length: 128,
comment: 'The name of the Antenna.',
})
public name: string;
@Index()
@Column('varchar', {
length: 128, array: true, default: '{}',
})
public on: (typeof webhookEventTypes)[number][];
@Column('varchar', {
length: 1024,
})
public url: string;
@Column('varchar', {
length: 1024,
})
public secret: string;
@Index()
@Column('boolean', {
default: true,
})
public active: boolean;
/**
* 直近のリクエスト送信日時
*/
@Column('timestamp with time zone', {
nullable: true,
})
public latestSentAt: Date | null;
/**
* 直近のリクエスト送信時のHTTPステータスコード
*/
@Column('integer', {
nullable: true,
})
public latestStatus: number | null;
}

View File

@ -1,4 +1,6 @@
import { getRepository, getCustomRepository } from 'typeorm'; import { } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Announcement } from './entities/announcement.js'; import { Announcement } from './entities/announcement.js';
import { AnnouncementRead } from './entities/announcement-read.js'; import { AnnouncementRead } from './entities/announcement-read.js';
import { Instance } from './entities/instance.js'; import { Instance } from './entities/instance.js';
@ -62,66 +64,68 @@ import { Ad } from './entities/ad.js';
import { PasswordResetRequest } from './entities/password-reset-request.js'; import { PasswordResetRequest } from './entities/password-reset-request.js';
import { UserPending } from './entities/user-pending.js'; import { UserPending } from './entities/user-pending.js';
import { InstanceRepository } from './repositories/instance.js'; import { InstanceRepository } from './repositories/instance.js';
import { Webhook } from './entities/webhook.js';
export const Announcements = getRepository(Announcement); export const Announcements = db.getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead); export const AnnouncementReads = db.getRepository(AnnouncementRead);
export const Apps = getCustomRepository(AppRepository); export const Apps = (AppRepository);
export const Notes = getCustomRepository(NoteRepository); export const Notes = (NoteRepository);
export const NoteFavorites = getCustomRepository(NoteFavoriteRepository); export const NoteFavorites = (NoteFavoriteRepository);
export const NoteWatchings = getRepository(NoteWatching); export const NoteWatchings = db.getRepository(NoteWatching);
export const NoteThreadMutings = getRepository(NoteThreadMuting); export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
export const NoteReactions = getCustomRepository(NoteReactionRepository); export const NoteReactions = (NoteReactionRepository);
export const NoteUnreads = getRepository(NoteUnread); export const NoteUnreads = db.getRepository(NoteUnread);
export const Polls = getRepository(Poll); export const Polls = db.getRepository(Poll);
export const PollVotes = getRepository(PollVote); export const PollVotes = db.getRepository(PollVote);
export const Users = getCustomRepository(UserRepository); export const Users = (UserRepository);
export const UserProfiles = getRepository(UserProfile); export const UserProfiles = db.getRepository(UserProfile);
export const UserKeypairs = getRepository(UserKeypair); export const UserKeypairs = db.getRepository(UserKeypair);
export const UserPendings = getRepository(UserPending); export const UserPendings = db.getRepository(UserPending);
export const AttestationChallenges = getRepository(AttestationChallenge); export const AttestationChallenges = db.getRepository(AttestationChallenge);
export const UserSecurityKeys = getRepository(UserSecurityKey); export const UserSecurityKeys = db.getRepository(UserSecurityKey);
export const UserPublickeys = getRepository(UserPublickey); export const UserPublickeys = db.getRepository(UserPublickey);
export const UserLists = getCustomRepository(UserListRepository); export const UserLists = (UserListRepository);
export const UserListJoinings = getRepository(UserListJoining); export const UserListJoinings = db.getRepository(UserListJoining);
export const UserGroups = getCustomRepository(UserGroupRepository); export const UserGroups = (UserGroupRepository);
export const UserGroupJoinings = getRepository(UserGroupJoining); export const UserGroupJoinings = db.getRepository(UserGroupJoining);
export const UserGroupInvitations = getCustomRepository(UserGroupInvitationRepository); export const UserGroupInvitations = (UserGroupInvitationRepository);
export const UserNotePinings = getRepository(UserNotePining); export const UserNotePinings = db.getRepository(UserNotePining);
export const UsedUsernames = getRepository(UsedUsername); export const UsedUsernames = db.getRepository(UsedUsername);
export const Followings = getCustomRepository(FollowingRepository); export const Followings = (FollowingRepository);
export const FollowRequests = getCustomRepository(FollowRequestRepository); export const FollowRequests = (FollowRequestRepository);
export const Instances = getCustomRepository(InstanceRepository); export const Instances = (InstanceRepository);
export const Emojis = getCustomRepository(EmojiRepository); export const Emojis = (EmojiRepository);
export const DriveFiles = getCustomRepository(DriveFileRepository); export const DriveFiles = (DriveFileRepository);
export const DriveFolders = getCustomRepository(DriveFolderRepository); export const DriveFolders = (DriveFolderRepository);
export const Notifications = getCustomRepository(NotificationRepository); export const Notifications = (NotificationRepository);
export const Metas = getRepository(Meta); export const Metas = db.getRepository(Meta);
export const Mutings = getCustomRepository(MutingRepository); export const Mutings = (MutingRepository);
export const Blockings = getCustomRepository(BlockingRepository); export const Blockings = (BlockingRepository);
export const SwSubscriptions = getRepository(SwSubscription); export const SwSubscriptions = db.getRepository(SwSubscription);
export const Hashtags = getCustomRepository(HashtagRepository); export const Hashtags = (HashtagRepository);
export const AbuseUserReports = getCustomRepository(AbuseUserReportRepository); export const AbuseUserReports = (AbuseUserReportRepository);
export const RegistrationTickets = getRepository(RegistrationTicket); export const RegistrationTickets = db.getRepository(RegistrationTicket);
export const AuthSessions = getCustomRepository(AuthSessionRepository); export const AuthSessions = (AuthSessionRepository);
export const AccessTokens = getRepository(AccessToken); export const AccessTokens = db.getRepository(AccessToken);
export const Signins = getCustomRepository(SigninRepository); export const Signins = (SigninRepository);
export const MessagingMessages = getCustomRepository(MessagingMessageRepository); export const MessagingMessages = (MessagingMessageRepository);
export const Pages = getCustomRepository(PageRepository); export const Pages = (PageRepository);
export const PageLikes = getCustomRepository(PageLikeRepository); export const PageLikes = (PageLikeRepository);
export const GalleryPosts = getCustomRepository(GalleryPostRepository); export const GalleryPosts = (GalleryPostRepository);
export const GalleryLikes = getCustomRepository(GalleryLikeRepository); export const GalleryLikes = (GalleryLikeRepository);
export const ModerationLogs = getCustomRepository(ModerationLogRepository); export const ModerationLogs = (ModerationLogRepository);
export const Clips = getCustomRepository(ClipRepository); export const Clips = (ClipRepository);
export const ClipNotes = getRepository(ClipNote); export const ClipNotes = db.getRepository(ClipNote);
export const Antennas = getCustomRepository(AntennaRepository); export const Antennas = (AntennaRepository);
export const AntennaNotes = getRepository(AntennaNote); export const AntennaNotes = db.getRepository(AntennaNote);
export const PromoNotes = getRepository(PromoNote); export const PromoNotes = db.getRepository(PromoNote);
export const PromoReads = getRepository(PromoRead); export const PromoReads = db.getRepository(PromoRead);
export const Relays = getCustomRepository(RelayRepository); export const Relays = (RelayRepository);
export const MutedNotes = getRepository(MutedNote); export const MutedNotes = db.getRepository(MutedNote);
export const Channels = getCustomRepository(ChannelRepository); export const Channels = (ChannelRepository);
export const ChannelFollowings = getRepository(ChannelFollowing); export const ChannelFollowings = db.getRepository(ChannelFollowing);
export const ChannelNotePinings = getRepository(ChannelNotePining); export const ChannelNotePinings = db.getRepository(ChannelNotePining);
export const RegistryItems = getRepository(RegistryItem); export const RegistryItems = db.getRepository(RegistryItem);
export const Ads = getRepository(Ad); export const Webhooks = db.getRepository(Webhook);
export const PasswordResetRequests = getRepository(PasswordResetRequest); export const Ads = db.getRepository(Ad);
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);

View File

@ -1,14 +1,13 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; import { AbuseUserReport } from '@/models/entities/abuse-user-report.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(AbuseUserReport) export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({
export class AbuseUserReportRepository extends Repository<AbuseUserReport> { async pack(
public async pack(
src: AbuseUserReport['id'] | AbuseUserReport, src: AbuseUserReport['id'] | AbuseUserReport,
) { ) {
const report = typeof src === 'object' ? src : await this.findOneOrFail(src); const report = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: report.id, id: report.id,
@ -29,11 +28,11 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
}) : null, }) : null,
forwarded: report.forwarded, forwarded: report.forwarded,
}); });
} },
public packMany( packMany(
reports: any[], reports: any[],
) { ) {
return Promise.all(reports.map(x => this.pack(x))); return Promise.all(reports.map(x => this.pack(x)));
} },
} });

View File

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Antenna } from '@/models/entities/antenna.js'; import { Antenna } from '@/models/entities/antenna.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { AntennaNotes, UserGroupJoinings } from '../index.js'; import { AntennaNotes, UserGroupJoinings } from '../index.js';
@EntityRepository(Antenna) export const AntennaRepository = db.getRepository(Antenna).extend({
export class AntennaRepository extends Repository<Antenna> { async pack(
public async pack(
src: Antenna['id'] | Antenna, src: Antenna['id'] | Antenna,
): Promise<Packed<'Antenna'>> { ): Promise<Packed<'Antenna'>> {
const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src); const antenna = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null; const hasUnreadNote = (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != null;
const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOne(antenna.userGroupJoiningId) : null; const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) : null;
return { return {
id: antenna.id, id: antenna.id,
@ -29,5 +28,5 @@ export class AntennaRepository extends Repository<Antenna> {
withFile: antenna.withFile, withFile: antenna.withFile,
hasUnreadNote, hasUnreadNote,
}; };
} },
} });

View File

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { App } from '@/models/entities/app.js'; import { App } from '@/models/entities/app.js';
import { AccessTokens } from '../index.js'; import { AccessTokens } from '../index.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { User } from '../entities/user.js'; import { User } from '../entities/user.js';
@EntityRepository(App) export const AppRepository = db.getRepository(App).extend({
export class AppRepository extends Repository<App> { async pack(
public async pack(
src: App['id'] | App, src: App['id'] | App,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
@ -21,7 +20,7 @@ export class AppRepository extends Repository<App> {
includeProfileImageIds: false, includeProfileImageIds: false,
}, options); }, options);
const app = typeof src === 'object' ? src : await this.findOneOrFail(src); const app = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: app.id, id: app.id,
@ -30,11 +29,11 @@ export class AppRepository extends Repository<App> {
permission: app.permission, permission: app.permission,
...(opts.includeSecret ? { secret: app.secret } : {}), ...(opts.includeSecret ? { secret: app.secret } : {}),
...(me ? { ...(me ? {
isAuthorized: await AccessTokens.count({ isAuthorized: await AccessTokens.countBy({
appId: app.id, appId: app.id,
userId: me.id, userId: me.id,
}).then(count => count > 0), }).then(count => count > 0),
} : {}), } : {}),
}; };
} },
} });

View File

@ -1,21 +1,20 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Apps } from '../index.js'; import { Apps } from '../index.js';
import { AuthSession } from '@/models/entities/auth-session.js'; import { AuthSession } from '@/models/entities/auth-session.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(AuthSession) export const AuthSessionRepository = db.getRepository(AuthSession).extend({
export class AuthSessionRepository extends Repository<AuthSession> { async pack(
public async pack(
src: AuthSession['id'] | AuthSession, src: AuthSession['id'] | AuthSession,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
) { ) {
const session = typeof src === 'object' ? src : await this.findOneOrFail(src); const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: session.id, id: session.id,
app: Apps.pack(session.appId, me), app: Apps.pack(session.appId, me),
token: session.token, token: session.token,
}); });
} },
} });

View File

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { Blocking } from '@/models/entities/blocking.js'; import { Blocking } from '@/models/entities/blocking.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(Blocking) export const BlockingRepository = db.getRepository(Blocking).extend({
export class BlockingRepository extends Repository<Blocking> { async pack(
public async pack(
src: Blocking['id'] | Blocking, src: Blocking['id'] | Blocking,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
): Promise<Packed<'Blocking'>> { ): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src); const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: blocking.id, id: blocking.id,
@ -21,12 +20,12 @@ export class BlockingRepository extends Repository<Blocking> {
detail: true, detail: true,
}), }),
}); });
} },
public packMany( packMany(
blockings: any[], blockings: any[],
me: { id: User['id'] } me: { id: User['id'] }
) { ) {
return Promise.all(blockings.map(x => this.pack(x, me))); return Promise.all(blockings.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,23 +1,22 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Channel } from '@/models/entities/channel.js'; import { Channel } from '@/models/entities/channel.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js'; import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(Channel) export const ChannelRepository = db.getRepository(Channel).extend({
export class ChannelRepository extends Repository<Channel> { async pack(
public async pack(
src: Channel['id'] | Channel, src: Channel['id'] | Channel,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Channel'>> { ): Promise<Packed<'Channel'>> {
const channel = typeof src === 'object' ? src : await this.findOneOrFail(src); const channel = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const meId = me ? me.id : null; const meId = me ? me.id : null;
const banner = channel.bannerId ? await DriveFiles.findOne(channel.bannerId) : null; const banner = channel.bannerId ? await DriveFiles.findOneBy({ id: channel.bannerId }) : null;
const hasUnreadNote = meId ? (await NoteUnreads.findOne({ noteChannelId: channel.id, userId: meId })) != null : undefined; const hasUnreadNote = meId ? (await NoteUnreads.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined;
const following = meId ? await ChannelFollowings.findOne({ const following = meId ? await ChannelFollowings.findOneBy({
followerId: meId, followerId: meId,
followeeId: channel.id, followeeId: channel.id,
}) : null; }) : null;
@ -38,5 +37,5 @@ export class ChannelRepository extends Repository<Channel> {
hasUnreadNote, hasUnreadNote,
} : {}), } : {}),
}; };
} },
} });

View File

@ -1,15 +1,14 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Clip } from '@/models/entities/clip.js'; import { Clip } from '@/models/entities/clip.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(Clip) export const ClipRepository = db.getRepository(Clip).extend({
export class ClipRepository extends Repository<Clip> { async pack(
public async pack(
src: Clip['id'] | Clip, src: Clip['id'] | Clip,
): Promise<Packed<'Clip'>> { ): Promise<Packed<'Clip'>> {
const clip = typeof src === 'object' ? src : await this.findOneOrFail(src); const clip = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: clip.id, id: clip.id,
@ -20,12 +19,12 @@ export class ClipRepository extends Repository<Clip> {
description: clip.description, description: clip.description,
isPublic: clip.isPublic, isPublic: clip.isPublic,
}); });
} },
public packMany( packMany(
clips: Clip[], clips: Clip[],
) { ) {
return Promise.all(clips.map(x => this.pack(x))); return Promise.all(clips.map(x => this.pack(x)));
} },
} });

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFile } from '@/models/entities/drive-file.js';
import { Users, DriveFolders } from '../index.js'; import { Users, DriveFolders } from '../index.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@ -16,9 +16,8 @@ type PackOptions = {
withUser?: boolean, withUser?: boolean,
}; };
@EntityRepository(DriveFile) export const DriveFileRepository = db.getRepository(DriveFile).extend({
export class DriveFileRepository extends Repository<DriveFile> { validateFileName(name: string): boolean {
public validateFileName(name: string): boolean {
return ( return (
(name.trim().length > 0) && (name.trim().length > 0) &&
(name.length <= 200) && (name.length <= 200) &&
@ -26,9 +25,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
(name.indexOf('/') === -1) && (name.indexOf('/') === -1) &&
(name.indexOf('..') === -1) (name.indexOf('..') === -1)
); );
} },
public getPublicProperties(file: DriveFile): DriveFile['properties'] { getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) { if (file.properties.orientation != null) {
const properties = JSON.parse(JSON.stringify(file.properties)); const properties = JSON.parse(JSON.stringify(file.properties));
if (file.properties.orientation >= 5) { if (file.properties.orientation >= 5) {
@ -39,9 +38,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
} }
return file.properties; return file.properties;
} },
public getPublicUrl(file: DriveFile, thumbnail = false): string | null { getPublicUrl(file: DriveFile, thumbnail = false): string | null {
// リモートかつメディアプロキシ // リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && config.mediaProxy != null) { if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
return appendQuery(config.mediaProxy, query({ return appendQuery(config.mediaProxy, query({
@ -62,9 +61,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type); const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type);
return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url); return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url);
} },
public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> { async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> {
const id = typeof user === 'object' ? user.id : user; const id = typeof user === 'object' ? user.id : user;
const { sum } = await this const { sum } = await this
@ -75,9 +74,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne(); .getRawOne();
return parseInt(sum, 10) || 0; return parseInt(sum, 10) || 0;
} },
public async calcDriveUsageOfHost(host: string): Promise<number> { async calcDriveUsageOfHost(host: string): Promise<number> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userHost = :host', { host: toPuny(host) }) .where('file.userHost = :host', { host: toPuny(host) })
@ -86,9 +85,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne(); .getRawOne();
return parseInt(sum, 10) || 0; return parseInt(sum, 10) || 0;
} },
public async calcDriveUsageOfLocal(): Promise<number> { async calcDriveUsageOfLocal(): Promise<number> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userHost IS NULL') .where('file.userHost IS NULL')
@ -97,9 +96,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne(); .getRawOne();
return parseInt(sum, 10) || 0; return parseInt(sum, 10) || 0;
} },
public async calcDriveUsageOfRemote(): Promise<number> { async calcDriveUsageOfRemote(): Promise<number> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userHost IS NOT NULL') .where('file.userHost IS NOT NULL')
@ -108,11 +107,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne(); .getRawOne();
return parseInt(sum, 10) || 0; return parseInt(sum, 10) || 0;
} },
public async pack(src: DriveFile['id'], options?: PackOptions): Promise<Packed<'DriveFile'> | null>; async pack(
public async pack(src: DriveFile, options?: PackOptions): Promise<Packed<'DriveFile'>>;
public async pack(
src: DriveFile['id'] | DriveFile, src: DriveFile['id'] | DriveFile,
options?: PackOptions options?: PackOptions
): Promise<Packed<'DriveFile'> | null> { ): Promise<Packed<'DriveFile'> | null> {
@ -121,11 +118,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
self: false, self: false,
}, options); }, options);
const file = typeof src === 'object' ? src : await this.findOne(src); const file = typeof src === 'object' ? src : await this.findOneBy({ id: src });
if (file == null) return null; if (file == null) return null;
const meta = await fetchMeta();
return await awaitAll<Packed<'DriveFile'>>({ return await awaitAll<Packed<'DriveFile'>>({
id: file.id, id: file.id,
createdAt: file.createdAt.toISOString(), createdAt: file.createdAt.toISOString(),
@ -146,13 +141,13 @@ export class DriveFileRepository extends Repository<DriveFile> {
userId: opts.withUser ? file.userId : null, userId: opts.withUser ? file.userId : null,
user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
}); });
} },
public async packMany( async packMany(
files: (DriveFile['id'] | DriveFile)[], files: (DriveFile['id'] | DriveFile)[],
options?: PackOptions options?: PackOptions
) { ) {
const items = await Promise.all(files.map(f => this.pack(f, options))); const items = await Promise.all(files.map(f => this.pack(f, options)));
return items.filter(x => x != null); return items.filter(x => x != null);
} },
} });

View File

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { DriveFolders, DriveFiles } from '../index.js'; import { DriveFolders, DriveFiles } from '../index.js';
import { DriveFolder } from '@/models/entities/drive-folder.js'; import { DriveFolder } from '@/models/entities/drive-folder.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
@EntityRepository(DriveFolder) export const DriveFolderRepository = db.getRepository(DriveFolder).extend({
export class DriveFolderRepository extends Repository<DriveFolder> { async pack(
public async pack(
src: DriveFolder['id'] | DriveFolder, src: DriveFolder['id'] | DriveFolder,
options?: { options?: {
detail: boolean detail: boolean
@ -16,7 +15,7 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
detail: false, detail: false,
}, options); }, options);
const folder = typeof src === 'object' ? src : await this.findOneOrFail(src); const folder = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: folder.id, id: folder.id,
@ -25,10 +24,10 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
parentId: folder.parentId, parentId: folder.parentId,
...(opts.detail ? { ...(opts.detail ? {
foldersCount: DriveFolders.count({ foldersCount: DriveFolders.countBy({
parentId: folder.id, parentId: folder.id,
}), }),
filesCount: DriveFiles.count({ filesCount: DriveFiles.countBy({
folderId: folder.id, folderId: folder.id,
}), }),
@ -39,5 +38,5 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
} : {}), } : {}),
} : {}), } : {}),
}); });
} },
} });

View File

@ -1,13 +1,12 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Emoji } from '@/models/entities/emoji.js'; import { Emoji } from '@/models/entities/emoji.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
@EntityRepository(Emoji) export const EmojiRepository = db.getRepository(Emoji).extend({
export class EmojiRepository extends Repository<Emoji> { async pack(
public async pack(
src: Emoji['id'] | Emoji, src: Emoji['id'] | Emoji,
): Promise<Packed<'Emoji'>> { ): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src); const emoji = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: emoji.id, id: emoji.id,
@ -18,11 +17,11 @@ export class EmojiRepository extends Repository<Emoji> {
// || emoji.originalUrl してるのは後方互換性のため // || emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl || emoji.originalUrl, url: emoji.publicUrl || emoji.originalUrl,
}; };
} },
public packMany( packMany(
emojis: any[], emojis: any[],
) { ) {
return Promise.all(emojis.map(x => this.pack(x))); return Promise.all(emojis.map(x => this.pack(x)));
} },
} });

View File

@ -1,20 +1,19 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { FollowRequest } from '@/models/entities/follow-request.js'; import { FollowRequest } from '@/models/entities/follow-request.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(FollowRequest) export const FollowRequestRepository = db.getRepository(FollowRequest).extend({
export class FollowRequestRepository extends Repository<FollowRequest> { async pack(
public async pack(
src: FollowRequest['id'] | FollowRequest, src: FollowRequest['id'] | FollowRequest,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
) { ) {
const request = typeof src === 'object' ? src : await this.findOneOrFail(src); const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: request.id, id: request.id,
follower: await Users.pack(request.followerId, me), follower: await Users.pack(request.followerId, me),
followee: await Users.pack(request.followeeId, me), followee: await Users.pack(request.followeeId, me),
}; };
} },
} });

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { Following } from '@/models/entities/following.js'; import { Following } from '@/models/entities/following.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
@ -29,25 +29,24 @@ type RemoteFolloweeFollowing = Following & {
followeeSharedInbox: string; followeeSharedInbox: string;
}; };
@EntityRepository(Following) export const FollowingRepository = db.getRepository(Following).extend({
export class FollowingRepository extends Repository<Following> { isLocalFollower(following: Following): following is LocalFollowerFollowing {
public isLocalFollower(following: Following): following is LocalFollowerFollowing {
return following.followerHost == null; return following.followerHost == null;
} },
public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
return following.followerHost != null; return following.followerHost != null;
} },
public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
return following.followeeHost == null; return following.followeeHost == null;
} },
public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
return following.followeeHost != null; return following.followeeHost != null;
} },
public async pack( async pack(
src: Following['id'] | Following, src: Following['id'] | Following,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
opts?: { opts?: {
@ -55,7 +54,7 @@ export class FollowingRepository extends Repository<Following> {
populateFollower?: boolean; populateFollower?: boolean;
} }
): Promise<Packed<'Following'>> { ): Promise<Packed<'Following'>> {
const following = typeof src === 'object' ? src : await this.findOneOrFail(src); const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
if (opts == null) opts = {}; if (opts == null) opts = {};
@ -71,9 +70,9 @@ export class FollowingRepository extends Repository<Following> {
detail: true, detail: true,
}) : undefined, }) : undefined,
}); });
} },
public packMany( packMany(
followings: any[], followings: any[],
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
opts?: { opts?: {
@ -82,5 +81,5 @@ export class FollowingRepository extends Repository<Following> {
} }
) { ) {
return Promise.all(followings.map(x => this.pack(x, me, opts))); return Promise.all(followings.map(x => this.pack(x, me, opts)));
} },
} });

View File

@ -1,25 +1,24 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { GalleryLike } from '@/models/entities/gallery-like.js'; import { GalleryLike } from '@/models/entities/gallery-like.js';
import { GalleryPosts } from '../index.js'; import { GalleryPosts } from '../index.js';
@EntityRepository(GalleryLike) export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({
export class GalleryLikeRepository extends Repository<GalleryLike> { async pack(
public async pack(
src: GalleryLike['id'] | GalleryLike, src: GalleryLike['id'] | GalleryLike,
me?: any me?: any
) { ) {
const like = typeof src === 'object' ? src : await this.findOneOrFail(src); const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: like.id, id: like.id,
post: await GalleryPosts.pack(like.post || like.postId, me), post: await GalleryPosts.pack(like.post || like.postId, me),
}; };
} },
public packMany( packMany(
likes: any[], likes: any[],
me: any me: any
) { ) {
return Promise.all(likes.map(x => this.pack(x, me))); return Promise.all(likes.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,18 +1,17 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { GalleryPost } from '@/models/entities/gallery-post.js'; import { GalleryPost } from '@/models/entities/gallery-post.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { Users, DriveFiles, GalleryLikes } from '../index.js'; import { Users, DriveFiles, GalleryLikes } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(GalleryPost) export const GalleryPostRepository = db.getRepository(GalleryPost).extend({
export class GalleryPostRepository extends Repository<GalleryPost> { async pack(
public async pack(
src: GalleryPost['id'] | GalleryPost, src: GalleryPost['id'] | GalleryPost,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'GalleryPost'>> { ): Promise<Packed<'GalleryPost'>> {
const meId = me ? me.id : null; const meId = me ? me.id : null;
const post = typeof src === 'object' ? src : await this.findOneOrFail(src); const post = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: post.id, id: post.id,
@ -27,14 +26,14 @@ export class GalleryPostRepository extends Repository<GalleryPost> {
tags: post.tags.length > 0 ? post.tags : undefined, tags: post.tags.length > 0 ? post.tags : undefined,
isSensitive: post.isSensitive, isSensitive: post.isSensitive,
likedCount: post.likedCount, likedCount: post.likedCount,
isLiked: meId ? await GalleryLikes.findOne({ postId: post.id, userId: meId }).then(x => x != null) : undefined, isLiked: meId ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined,
}); });
} },
public packMany( packMany(
posts: GalleryPost[], posts: GalleryPost[],
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
) { ) {
return Promise.all(posts.map(x => this.pack(x, me))); return Promise.all(posts.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,10 +1,9 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Hashtag } from '@/models/entities/hashtag.js'; import { Hashtag } from '@/models/entities/hashtag.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
@EntityRepository(Hashtag) export const HashtagRepository = db.getRepository(Hashtag).extend({
export class HashtagRepository extends Repository<Hashtag> { async pack(
public async pack(
src: Hashtag, src: Hashtag,
): Promise<Packed<'Hashtag'>> { ): Promise<Packed<'Hashtag'>> {
return { return {
@ -16,11 +15,11 @@ export class HashtagRepository extends Repository<Hashtag> {
attachedLocalUsersCount: src.attachedLocalUsersCount, attachedLocalUsersCount: src.attachedLocalUsersCount,
attachedRemoteUsersCount: src.attachedRemoteUsersCount, attachedRemoteUsersCount: src.attachedRemoteUsersCount,
}; };
} },
public packMany( packMany(
hashtags: Hashtag[], hashtags: Hashtag[],
) { ) {
return Promise.all(hashtags.map(x => this.pack(x))); return Promise.all(hashtags.map(x => this.pack(x)));
} },
} });

View File

@ -1,10 +1,9 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Instance } from '@/models/entities/instance.js'; import { Instance } from '@/models/entities/instance.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
@EntityRepository(Instance) export const InstanceRepository = db.getRepository(Instance).extend({
export class InstanceRepository extends Repository<Instance> { async pack(
public async pack(
instance: Instance, instance: Instance,
): Promise<Packed<'FederationInstance'>> { ): Promise<Packed<'FederationInstance'>> {
return { return {
@ -29,11 +28,11 @@ export class InstanceRepository extends Repository<Instance> {
iconUrl: instance.iconUrl, iconUrl: instance.iconUrl,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null, infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
}; };
} },
public packMany( packMany(
instances: Instance[], instances: Instance[],
) { ) {
return Promise.all(instances.map(x => this.pack(x))); return Promise.all(instances.map(x => this.pack(x)));
} },
} });

View File

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Users, DriveFiles, UserGroups } from '../index.js'; import { Users, DriveFiles, UserGroups } from '../index.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(MessagingMessage) export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({
export class MessagingMessageRepository extends Repository<MessagingMessage> { async pack(
public async pack(
src: MessagingMessage['id'] | MessagingMessage, src: MessagingMessage['id'] | MessagingMessage,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
@ -19,7 +18,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
populateGroup: true, populateGroup: true,
}; };
const message = typeof src === 'object' ? src : await this.findOneOrFail(src); const message = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: message.id, id: message.id,
@ -36,5 +35,5 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
isRead: message.isRead, isRead: message.isRead,
reads: message.reads, reads: message.reads,
}; };
} },
} });

View File

@ -1,14 +1,13 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { ModerationLog } from '@/models/entities/moderation-log.js'; import { ModerationLog } from '@/models/entities/moderation-log.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(ModerationLog) export const ModerationLogRepository = db.getRepository(ModerationLog).extend({
export class ModerationLogRepository extends Repository<ModerationLog> { async pack(
public async pack(
src: ModerationLog['id'] | ModerationLog, src: ModerationLog['id'] | ModerationLog,
) { ) {
const log = typeof src === 'object' ? src : await this.findOneOrFail(src); const log = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: log.id, id: log.id,
@ -20,11 +19,11 @@ export class ModerationLogRepository extends Repository<ModerationLog> {
detail: true, detail: true,
}), }),
}); });
} },
public packMany( packMany(
reports: any[], reports: any[],
) { ) {
return Promise.all(reports.map(x => this.pack(x))); return Promise.all(reports.map(x => this.pack(x)));
} },
} });

View File

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Users } from '../index.js'; import { Users } from '../index.js';
import { Muting } from '@/models/entities/muting.js'; import { Muting } from '@/models/entities/muting.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(Muting) export const MutingRepository = db.getRepository(Muting).extend({
export class MutingRepository extends Repository<Muting> { async pack(
public async pack(
src: Muting['id'] | Muting, src: Muting['id'] | Muting,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
): Promise<Packed<'Muting'>> { ): Promise<Packed<'Muting'>> {
const muting = typeof src === 'object' ? src : await this.findOneOrFail(src); const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({ return await awaitAll({
id: muting.id, id: muting.id,
@ -22,12 +21,12 @@ export class MutingRepository extends Repository<Muting> {
detail: true, detail: true,
}), }),
}); });
} },
public packMany( packMany(
mutings: any[], mutings: any[],
me: { id: User['id'] } me: { id: User['id'] }
) { ) {
return Promise.all(mutings.map(x => this.pack(x, me))); return Promise.all(mutings.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,15 +1,14 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { NoteFavorite } from '@/models/entities/note-favorite.js'; import { NoteFavorite } from '@/models/entities/note-favorite.js';
import { Notes } from '../index.js'; import { Notes } from '../index.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(NoteFavorite) export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
export class NoteFavoriteRepository extends Repository<NoteFavorite> { async pack(
public async pack(
src: NoteFavorite['id'] | NoteFavorite, src: NoteFavorite['id'] | NoteFavorite,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
) { ) {
const favorite = typeof src === 'object' ? src : await this.findOneOrFail(src); const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: favorite.id, id: favorite.id,
@ -17,12 +16,12 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
noteId: favorite.noteId, noteId: favorite.noteId,
note: await Notes.pack(favorite.note || favorite.noteId, me), note: await Notes.pack(favorite.note || favorite.noteId, me),
}; };
} },
public packMany( packMany(
favorites: any[], favorites: any[],
me: { id: User['id'] } me: { id: User['id'] }
) { ) {
return Promise.all(favorites.map(x => this.pack(x, me))); return Promise.all(favorites.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,13 +1,12 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { NoteReaction } from '@/models/entities/note-reaction.js'; import { NoteReaction } from '@/models/entities/note-reaction.js';
import { Notes, Users } from '../index.js'; import { Notes, Users } from '../index.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { convertLegacyReaction } from '@/misc/reaction-lib.js'; import { convertLegacyReaction } from '@/misc/reaction-lib.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(NoteReaction) export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
export class NoteReactionRepository extends Repository<NoteReaction> { async pack(
public async pack(
src: NoteReaction['id'] | NoteReaction, src: NoteReaction['id'] | NoteReaction,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
@ -18,7 +17,7 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
withNote: false, withNote: false,
}, options); }, options);
const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src); const reaction = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: reaction.id, id: reaction.id,
@ -29,5 +28,5 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
note: await Notes.pack(reaction.note ?? reaction.noteId, me), note: await Notes.pack(reaction.note ?? reaction.noteId, me),
} : {}), } : {}),
}; };
} },
} });

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository, In } from 'typeorm'; import { In } from 'typeorm';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { Note } from '@/models/entities/note.js'; import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@ -9,10 +9,133 @@ import { awaitAll } from '@/prelude/await-all.js';
import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js'; import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js';
import { NoteReaction } from '@/models/entities/note-reaction.js'; import { NoteReaction } from '@/models/entities/note-reaction.js';
import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { db } from '@/db/postgre.js';
@EntityRepository(Note) async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
export class NoteRepository extends Repository<Note> { // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> { let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOneBy({
followeeId: packedNote.userId,
followerId: meId,
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
}
async function populatePoll(note: Note, meId: User['id'] | null) {
const poll = await Polls.findOneByOrFail({ noteId: note.id });
const choices = poll.choices.map(c => ({
text: c,
votes: poll.votes[poll.choices.indexOf(c)],
isVoted: false,
}));
if (meId) {
if (poll.multiple) {
const votes = await PollVotes.findBy({
userId: meId,
noteId: note.id,
});
const myChoices = votes.map(v => v.choice);
for (const myChoice of myChoices) {
choices[myChoice].isVoted = true;
}
} else {
const vote = await PollVotes.findOneBy({
userId: meId,
noteId: note.id,
});
if (vote) {
choices[vote.choice].isVoted = true;
}
}
}
return {
multiple: poll.multiple,
expiresAt: poll.expiresAt,
choices,
};
}
async function populateMyReaction(note: Note, meId: User['id'], _hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
}) {
if (_hint_?.myReactions) {
const reaction = _hint_.myReactions.get(note.id);
if (reaction) {
return convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
return undefined;
}
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
const reaction = await NoteReactions.findOneBy({
userId: meId,
noteId: note.id,
});
if (reaction) {
return convertLegacyReaction(reaction.reaction);
}
return undefined;
}
export const NoteRepository = db.getRepository(Note).extend({
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
// visibility が specified かつ自分が指定されていなかったら非表示 // visibility が specified かつ自分が指定されていなかったら非表示
if (note.visibility === 'specified') { if (note.visibility === 'specified') {
if (meId == null) { if (meId == null) {
@ -45,7 +168,7 @@ export class NoteRepository extends Repository<Note> {
return true; return true;
} else { } else {
// フォロワーかどうか // フォロワーかどうか
const following = await Followings.findOne({ const following = await Followings.findOneBy({
followeeId: note.userId, followeeId: note.userId,
followerId: meId, followerId: meId,
}); });
@ -59,69 +182,9 @@ export class NoteRepository extends Repository<Note> {
} }
return true; return true;
} },
private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { async pack(
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOne({
followeeId: packedNote.userId,
followerId: meId,
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
}
public async pack(
src: Note['id'] | Note, src: Note['id'] | Note,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
@ -138,68 +201,9 @@ export class NoteRepository extends Repository<Note> {
}, options); }, options);
const meId = me ? me.id : null; const meId = me ? me.id : null;
const note = typeof src === 'object' ? src : await this.findOneOrFail(src); const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const host = note.userHost; const host = note.userHost;
async function populatePoll() {
const poll = await Polls.findOneOrFail(note.id);
const choices = poll.choices.map(c => ({
text: c,
votes: poll.votes[poll.choices.indexOf(c)],
isVoted: false,
}));
if (poll.multiple) {
const votes = await PollVotes.find({
userId: meId!,
noteId: note.id,
});
const myChoices = votes.map(v => v.choice);
for (const myChoice of myChoices) {
choices[myChoice].isVoted = true;
}
} else {
const vote = await PollVotes.findOne({
userId: meId!,
noteId: note.id,
});
if (vote) {
choices[vote.choice].isVoted = true;
}
}
return {
multiple: poll.multiple,
expiresAt: poll.expiresAt,
choices,
};
}
async function populateMyReaction() {
if (options?._hint_?.myReactions) {
const reaction = options._hint_.myReactions.get(note.id);
if (reaction) {
return convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
return undefined;
}
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
const reaction = await NoteReactions.findOne({
userId: meId!,
noteId: note.id,
});
if (reaction) {
return convertLegacyReaction(reaction.reaction);
}
return undefined;
}
let text = note.text; let text = note.text;
if (note.name && (note.url ?? note.uri)) { if (note.name && (note.url ?? note.uri)) {
@ -209,7 +213,7 @@ export class NoteRepository extends Repository<Note> {
const channel = note.channelId const channel = note.channelId
? note.channel ? note.channel
? note.channel ? note.channel
: await Channels.findOne(note.channelId) : await Channels.findOneBy({ id: note.channelId })
: null; : null;
const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, ''));
@ -255,10 +259,10 @@ export class NoteRepository extends Repository<Note> {
_hint_: options?._hint_, _hint_: options?._hint_,
}) : undefined, }) : undefined,
poll: note.hasPoll ? populatePoll() : undefined, poll: note.hasPoll ? populatePoll(note, meId) : undefined,
...(meId ? { ...(meId ? {
myReaction: populateMyReaction(), myReaction: populateMyReaction(note, meId, options?._hint_),
} : {}), } : {}),
} : {}), } : {}),
}); });
@ -275,13 +279,13 @@ export class NoteRepository extends Repository<Note> {
} }
if (!opts.skipHide) { if (!opts.skipHide) {
await this.hideNote(packed, meId); await hideNote(packed, meId);
} }
return packed; return packed;
} },
public async packMany( async packMany(
notes: Note[], notes: Note[],
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
@ -296,7 +300,7 @@ export class NoteRepository extends Repository<Note> {
if (meId) { if (meId) {
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...notes.map(n => n.id), ...renoteIds]; const targets = [...notes.map(n => n.id), ...renoteIds];
const myReactions = await NoteReactions.find({ const myReactions = await NoteReactions.findBy({
userId: meId, userId: meId,
noteId: In(targets), noteId: In(targets),
}); });
@ -314,5 +318,5 @@ export class NoteRepository extends Repository<Note> {
myReactions: myReactionsMap, myReactions: myReactionsMap,
}, },
}))); })));
} },
} });

View File

@ -1,4 +1,4 @@
import { EntityRepository, In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js'; import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js';
import { Notification } from '@/models/entities/notification.js'; import { Notification } from '@/models/entities/notification.js';
import { awaitAll } from '@/prelude/await-all.js'; import { awaitAll } from '@/prelude/await-all.js';
@ -8,10 +8,10 @@ import { NoteReaction } from '@/models/entities/note-reaction.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { notificationTypes } from '@/types.js'; import { notificationTypes } from '@/types.js';
import { db } from '@/db/postgre.js';
@EntityRepository(Notification) export const NotificationRepository = db.getRepository(Notification).extend({
export class NotificationRepository extends Repository<Notification> { async pack(
public async pack(
src: Notification['id'] | Notification, src: Notification['id'] | Notification,
options: { options: {
_hintForEachNotes_?: { _hintForEachNotes_?: {
@ -19,8 +19,8 @@ export class NotificationRepository extends Repository<Notification> {
}; };
} }
): Promise<Packed<'Notification'>> { ): Promise<Packed<'Notification'>> {
const notification = typeof src === 'object' ? src : await this.findOneOrFail(src); const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null; const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
return await awaitAll({ return await awaitAll({
id: notification.id, id: notification.id,
@ -82,9 +82,9 @@ export class NotificationRepository extends Repository<Notification> {
icon: notification.customIcon || token?.iconUrl, icon: notification.customIcon || token?.iconUrl,
} : {}), } : {}),
}); });
} },
public async packMany( async packMany(
notifications: Notification[], notifications: Notification[],
meId: User['id'] meId: User['id']
) { ) {
@ -95,7 +95,7 @@ export class NotificationRepository extends Repository<Notification> {
const myReactionsMap = new Map<Note['id'], NoteReaction | null>(); const myReactionsMap = new Map<Note['id'], NoteReaction | null>();
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...noteIds, ...renoteIds]; const targets = [...noteIds, ...renoteIds];
const myReactions = await NoteReactions.find({ const myReactions = await NoteReactions.findBy({
userId: meId, userId: meId,
noteId: In(targets), noteId: In(targets),
}); });
@ -111,5 +111,5 @@ export class NotificationRepository extends Repository<Notification> {
myReactions: myReactionsMap, myReactions: myReactionsMap,
}, },
}))); })));
} },
} });

View File

@ -1,26 +1,25 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { PageLike } from '@/models/entities/page-like.js'; import { PageLike } from '@/models/entities/page-like.js';
import { Pages } from '../index.js'; import { Pages } from '../index.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(PageLike) export const PageLikeRepository = db.getRepository(PageLike).extend({
export class PageLikeRepository extends Repository<PageLike> { async pack(
public async pack(
src: PageLike['id'] | PageLike, src: PageLike['id'] | PageLike,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined
) { ) {
const like = typeof src === 'object' ? src : await this.findOneOrFail(src); const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: like.id, id: like.id,
page: await Pages.pack(like.page || like.pageId, me), page: await Pages.pack(like.page || like.pageId, me),
}; };
} },
public packMany( packMany(
likes: any[], likes: any[],
me: { id: User['id'] } me: { id: User['id'] }
) { ) {
return Promise.all(likes.map(x => this.pack(x, me))); return Promise.all(likes.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Page } from '@/models/entities/page.js'; import { Page } from '@/models/entities/page.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { Users, DriveFiles, PageLikes } from '../index.js'; import { Users, DriveFiles, PageLikes } from '../index.js';
@ -6,20 +6,19 @@ import { awaitAll } from '@/prelude/await-all.js';
import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFile } from '@/models/entities/drive-file.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
@EntityRepository(Page) export const PageRepository = db.getRepository(Page).extend({
export class PageRepository extends Repository<Page> { async pack(
public async pack(
src: Page['id'] | Page, src: Page['id'] | Page,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Page'>> { ): Promise<Packed<'Page'>> {
const meId = me ? me.id : null; const meId = me ? me.id : null;
const page = typeof src === 'object' ? src : await this.findOneOrFail(src); const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const attachedFiles: Promise<DriveFile | undefined>[] = []; const attachedFiles: Promise<DriveFile | undefined>[] = [];
const collectFile = (xs: any[]) => { const collectFile = (xs: any[]) => {
for (const x of xs) { for (const x of xs) {
if (x.type === 'image') { if (x.type === 'image') {
attachedFiles.push(DriveFiles.findOne({ attachedFiles.push(DriveFiles.findOneBy({
id: x.fileId, id: x.fileId,
userId: page.userId, userId: page.userId,
})); }));
@ -76,14 +75,14 @@ export class PageRepository extends Repository<Page> {
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
likedCount: page.likedCount, likedCount: page.likedCount,
isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
}); });
} },
public packMany( packMany(
pages: Page[], pages: Page[],
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
) { ) {
return Promise.all(pages.map(x => this.pack(x, me))); return Promise.all(pages.map(x => this.pack(x, me)));
} },
} });

View File

@ -1,6 +1,5 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Relay } from '@/models/entities/relay.js'; import { Relay } from '@/models/entities/relay.js';
@EntityRepository(Relay) export const RelayRepository = db.getRepository(Relay).extend({
export class RelayRepository extends Repository<Relay> { });
}

View File

@ -1,11 +1,10 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { Signin } from '@/models/entities/signin.js'; import { Signin } from '@/models/entities/signin.js';
@EntityRepository(Signin) export const SigninRepository = db.getRepository(Signin).extend({
export class SigninRepository extends Repository<Signin> { async pack(
public async pack(
src: Signin, src: Signin,
) { ) {
return src; return src;
} },
} });

View File

@ -1,23 +1,22 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js';
import { UserGroups } from '../index.js'; import { UserGroups } from '../index.js';
@EntityRepository(UserGroupInvitation) export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({
export class UserGroupInvitationRepository extends Repository<UserGroupInvitation> { async pack(
public async pack(
src: UserGroupInvitation['id'] | UserGroupInvitation, src: UserGroupInvitation['id'] | UserGroupInvitation,
) { ) {
const invitation = typeof src === 'object' ? src : await this.findOneOrFail(src); const invitation = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return { return {
id: invitation.id, id: invitation.id,
group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId), group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId),
}; };
} },
public packMany( packMany(
invitations: any[], invitations: any[],
) { ) {
return Promise.all(invitations.map(x => this.pack(x))); return Promise.all(invitations.map(x => this.pack(x)));
} },
} });

View File

@ -1,16 +1,15 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { UserGroup } from '@/models/entities/user-group.js'; import { UserGroup } from '@/models/entities/user-group.js';
import { UserGroupJoinings } from '../index.js'; import { UserGroupJoinings } from '../index.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
@EntityRepository(UserGroup) export const UserGroupRepository = db.getRepository(UserGroup).extend({
export class UserGroupRepository extends Repository<UserGroup> { async pack(
public async pack(
src: UserGroup['id'] | UserGroup, src: UserGroup['id'] | UserGroup,
): Promise<Packed<'UserGroup'>> { ): Promise<Packed<'UserGroup'>> {
const userGroup = typeof src === 'object' ? src : await this.findOneOrFail(src); const userGroup = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const users = await UserGroupJoinings.find({ const users = await UserGroupJoinings.findBy({
userGroupId: userGroup.id, userGroupId: userGroup.id,
}); });
@ -21,5 +20,5 @@ export class UserGroupRepository extends Repository<UserGroup> {
ownerId: userGroup.userId, ownerId: userGroup.userId,
userIds: users.map(x => x.userId), userIds: users.map(x => x.userId),
}; };
} },
} });

View File

@ -1,16 +1,15 @@
import { EntityRepository, Repository } from 'typeorm'; import { db } from '@/db/postgre.js';
import { UserList } from '@/models/entities/user-list.js'; import { UserList } from '@/models/entities/user-list.js';
import { UserListJoinings } from '../index.js'; import { UserListJoinings } from '../index.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
@EntityRepository(UserList) export const UserListRepository = db.getRepository(UserList).extend({
export class UserListRepository extends Repository<UserList> { async pack(
public async pack(
src: UserList['id'] | UserList, src: UserList['id'] | UserList,
): Promise<Packed<'UserList'>> { ): Promise<Packed<'UserList'>> {
const userList = typeof src === 'object' ? src : await this.findOneOrFail(src); const userList = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const users = await UserListJoinings.find({ const users = await UserListJoinings.findBy({
userListId: userList.id, userListId: userList.id,
}); });
@ -20,5 +19,5 @@ export class UserListRepository extends Repository<UserList> {
name: userList.name, name: userList.name,
userIds: users.map(x => x.userId), userIds: users.map(x => x.userId),
}; };
} },
} });

View File

@ -1,13 +1,18 @@
import { EntityRepository, Repository, In, Not } from 'typeorm'; import { EntityRepository, Repository, In, Not } from 'typeorm';
import Ajv from 'ajv'; import Ajv from 'ajv';
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { Packed } from '@/misc/schema.js'; import { Packed } from '@/misc/schema.js';
import { awaitAll, Promiseable } from '@/prelude/await-all.js'; import { awaitAll, Promiseable } from '@/prelude/await-all.js';
import { populateEmojis } from '@/misc/populate-emojis.js'; import { populateEmojis } from '@/misc/populate-emojis.js';
import { getAntennas } from '@/misc/antenna-cache.js'; import { getAntennas } from '@/misc/antenna-cache.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import { Cache } from '@/misc/cache.js';
import { db } from '@/db/postgre.js';
import { Instance } from '../entities/instance.js';
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> = type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
@ -19,51 +24,69 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
const ajv = new Ajv(); const ajv = new Ajv();
@EntityRepository(User) const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export class UserRepository extends Repository<User> { const passwordSchema = { type: 'string', minLength: 1 } as const;
public localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public passwordSchema = { type: 'string', minLength: 1 } as const; const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
public nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
public locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; function isLocalUser(user: User): user is ILocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
function isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null;
}
function isRemoteUser(user: User): user is IRemoteUser;
function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user);
}
export const UserRepository = db.getRepository(User).extend({
localUsernameSchema,
passwordSchema,
nameSchema,
descriptionSchema,
locationSchema,
birthdaySchema,
//#region Validators //#region Validators
public validateLocalUsername = ajv.compile(this.localUsernameSchema); validateLocalUsername: ajv.compile(localUsernameSchema),
public validatePassword = ajv.compile(this.passwordSchema); validatePassword: ajv.compile(passwordSchema),
public validateName = ajv.compile(this.nameSchema); validateName: ajv.compile(nameSchema),
public validateDescription = ajv.compile(this.descriptionSchema); validateDescription: ajv.compile(descriptionSchema),
public validateLocation = ajv.compile(this.locationSchema); validateLocation: ajv.compile(locationSchema),
public validateBirthday = ajv.compile(this.birthdaySchema); validateBirthday: ajv.compile(birthdaySchema),
//#endregion //#endregion
public async getRelation(me: User['id'], target: User['id']) { async getRelation(me: User['id'], target: User['id']) {
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
Followings.findOne({ Followings.findOneBy({
followerId: me, followerId: me,
followeeId: target, followeeId: target,
}), }),
Followings.findOne({ Followings.findOneBy({
followerId: target, followerId: target,
followeeId: me, followeeId: me,
}), }),
FollowRequests.findOne({ FollowRequests.findOneBy({
followerId: me, followerId: me,
followeeId: target, followeeId: target,
}), }),
FollowRequests.findOne({ FollowRequests.findOneBy({
followerId: target, followerId: target,
followeeId: me, followeeId: me,
}), }),
Blockings.findOne({ Blockings.findOneBy({
blockerId: me, blockerId: me,
blockeeId: target, blockeeId: target,
}), }),
Blockings.findOne({ Blockings.findOneBy({
blockerId: target, blockerId: target,
blockeeId: me, blockeeId: me,
}), }),
Mutings.findOne({ Mutings.findOneBy({
muterId: me, muterId: me,
muteeId: target, muteeId: target,
}), }),
@ -79,17 +102,17 @@ export class UserRepository extends Repository<User> {
isBlocked: fromBlocked != null, isBlocked: fromBlocked != null,
isMuted: mute != null, isMuted: mute != null,
}; };
} },
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> { async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({ const mute = await Mutings.findBy({
muterId: userId, muterId: userId,
}); });
const joinings = await UserGroupJoinings.find({ userId: userId }); const joinings = await UserGroupJoinings.findBy({ userId: userId });
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message') const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
.where(`message.groupId = :groupId`, { groupId: j.userGroupId }) .where('message.groupId = :groupId', { groupId: j.userGroupId })
.andWhere('message.userId != :userId', { userId: userId }) .andWhere('message.userId != :userId', { userId: userId })
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない .andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
@ -108,44 +131,44 @@ export class UserRepository extends Repository<User> {
]); ]);
return withUser || withGroups.some(x => x); return withUser || withGroups.some(x => x);
} },
public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> { async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
const reads = await AnnouncementReads.find({ const reads = await AnnouncementReads.findBy({
userId: userId, userId: userId,
}); });
const count = await Announcements.count(reads.length > 0 ? { const count = await Announcements.countBy(reads.length > 0 ? {
id: Not(In(reads.map(read => read.announcementId))), id: Not(In(reads.map(read => read.announcementId))),
} : {}); } : {});
return count > 0; return count > 0;
} },
public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> { async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
const myAntennas = (await getAntennas()).filter(a => a.userId === userId); const myAntennas = (await getAntennas()).filter(a => a.userId === userId);
const unread = myAntennas.length > 0 ? await AntennaNotes.findOne({ const unread = myAntennas.length > 0 ? await AntennaNotes.findOneBy({
antennaId: In(myAntennas.map(x => x.id)), antennaId: In(myAntennas.map(x => x.id)),
read: false, read: false,
}) : null; }) : null;
return unread != null; return unread != null;
} },
public async getHasUnreadChannel(userId: User['id']): Promise<boolean> { async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
const channels = await ChannelFollowings.find({ followerId: userId }); const channels = await ChannelFollowings.findBy({ followerId: userId });
const unread = channels.length > 0 ? await NoteUnreads.findOne({ const unread = channels.length > 0 ? await NoteUnreads.findOneBy({
userId: userId, userId: userId,
noteChannelId: In(channels.map(x => x.followeeId)), noteChannelId: In(channels.map(x => x.followeeId)),
}) : null; }) : null;
return unread != null; return unread != null;
} },
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> { async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({ const mute = await Mutings.findBy({
muterId: userId, muterId: userId,
}); });
const mutedUserIds = mute.map(m => m.muteeId); const mutedUserIds = mute.map(m => m.muteeId);
@ -160,17 +183,17 @@ export class UserRepository extends Repository<User> {
}); });
return count > 0; return count > 0;
} },
public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise<boolean> { async getHasPendingReceivedFollowRequest(userId: User['id']): Promise<boolean> {
const count = await FollowRequests.count({ const count = await FollowRequests.countBy({
followeeId: userId, followeeId: userId,
}); });
return count > 0; return count > 0;
} },
public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
if (user.hideOnlineStatus) return 'unknown'; if (user.hideOnlineStatus) return 'unknown';
if (user.lastActiveDate == null) return 'unknown'; if (user.lastActiveDate == null) return 'unknown';
const elapsed = Date.now() - user.lastActiveDate.getTime(); const elapsed = Date.now() - user.lastActiveDate.getTime();
@ -179,28 +202,38 @@ export class UserRepository extends Repository<User> {
elapsed < USER_ACTIVE_THRESHOLD ? 'active' : elapsed < USER_ACTIVE_THRESHOLD ? 'active' :
'offline' 'offline'
); );
} },
public getAvatarUrl(user: User): string { async getAvatarUrl(user: User): Promise<string> {
// TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング if (user.avatar) {
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
} else if (user.avatarId) {
const avatar = await DriveFiles.findOneByOrFail({ id: user.avatarId });
return DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id);
} else {
return this.getIdenticonUrl(user.id);
}
},
getAvatarUrlSync(user: User): string {
if (user.avatar) { if (user.avatar) {
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id); return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
} else { } else {
return this.getIdenticonUrl(user.id); return this.getIdenticonUrl(user.id);
} }
} },
public getIdenticonUrl(userId: User['id']): string { getIdenticonUrl(userId: User['id']): string {
return `${config.url}/identicon/${userId}`; return `${config.url}/identicon/${userId}`;
} },
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User, src: User['id'] | User,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
detail?: D, detail?: D,
includeSecrets?: boolean, includeSecrets?: boolean,
} },
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> { ): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
@ -211,11 +244,15 @@ export class UserRepository extends Repository<User> {
if (typeof src === 'object') { if (typeof src === 'object') {
user = src; user = src;
if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOne(src.avatarId) ?? null; if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOneBy({ id: src.avatarId }) ?? null;
if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOne(src.bannerId) ?? null; if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOneBy({ id: src.bannerId }) ?? null;
} else { } else {
user = await this.findOneOrFail(src, { user = await this.findOneOrFail({
relations: ['avatar', 'banner'], where: { id: src },
relations: {
avatar: true,
banner: true,
},
}); });
} }
@ -228,7 +265,7 @@ export class UserRepository extends Repository<User> {
.innerJoinAndSelect('pin.note', 'note') .innerJoinAndSelect('pin.note', 'note')
.orderBy('pin.id', 'DESC') .orderBy('pin.id', 'DESC')
.getMany() : []; .getMany() : [];
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null;
const followingCount = profile == null ? null : const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount : (profile.ffVisibility === 'public') || isMe ? user.followingCount :
@ -247,15 +284,17 @@ export class UserRepository extends Repository<User> {
name: user.name, name: user.name,
username: user.username, username: user.username,
host: user.host, host: user.host,
avatarUrl: this.getAvatarUrl(user), avatarUrl: this.getAvatarUrlSync(user),
avatarBlurhash: user.avatar?.blurhash || null, avatarBlurhash: user.avatar?.blurhash || null,
avatarColor: null, // 後方互換性のため avatarColor: null, // 後方互換性のため
isAdmin: user.isAdmin || falsy, isAdmin: user.isAdmin || falsy,
isModerator: user.isModerator || falsy, isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy, isBot: user.isBot || falsy,
isCat: user.isCat || falsy, isCat: user.isCat || falsy,
showTimelineReplies: user.showTimelineReplies || falsy, instance: user.host ? userInstanceCache.fetch(user.host,
instance: user.host ? Instances.findOne({ host: user.host }).then(instance => instance ? { () => Instances.findOneBy({ host: user.host! }),
v => v != null,
).then(instance => instance ? {
name: instance.name, name: instance.name,
softwareName: instance.softwareName, softwareName: instance.softwareName,
softwareVersion: instance.softwareVersion, softwareVersion: instance.softwareVersion,
@ -297,7 +336,7 @@ export class UserRepository extends Repository<User> {
twoFactorEnabled: profile!.twoFactorEnabled, twoFactorEnabled: profile!.twoFactorEnabled,
usePasswordLessLogin: profile!.usePasswordLessLogin, usePasswordLessLogin: profile!.usePasswordLessLogin,
securityKeys: profile!.twoFactorEnabled securityKeys: profile!.twoFactorEnabled
? UserSecurityKeys.count({ ? UserSecurityKeys.countBy({
userId: user.id, userId: user.id,
}).then(result => result >= 1) }).then(result => result >= 1)
: false, : false,
@ -334,6 +373,7 @@ export class UserRepository extends Repository<User> {
mutedInstances: profile!.mutedInstances, mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: profile!.mutingNotificationTypes, mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes,
showTimelineReplies: user.showTimelineReplies || falsy,
} : {}), } : {}),
...(opts.includeSecrets ? { ...(opts.includeSecrets ? {
@ -344,7 +384,11 @@ export class UserRepository extends Repository<User> {
where: { where: {
userId: user.id, userId: user.id,
}, },
select: ['id', 'name', 'lastUsed'], select: {
id: true,
name: true,
lastUsed: true,
},
}) })
: [], : [],
} : {}), } : {}),
@ -361,28 +405,19 @@ export class UserRepository extends Repository<User> {
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>; } as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
return await awaitAll(packed); return await awaitAll(packed);
} },
public packMany<D extends boolean = false>( packMany<D extends boolean = false>(
users: (User['id'] | User)[], users: (User['id'] | User)[],
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
detail?: D, detail?: D,
includeSecrets?: boolean, includeSecrets?: boolean,
} },
): Promise<IsUserDetailed<D>[]> { ): Promise<IsUserDetailed<D>[]> {
return Promise.all(users.map(u => this.pack(u, me, options))); return Promise.all(users.map(u => this.pack(u, me, options)));
} },
public isLocalUser(user: User): user is ILocalUser; isLocalUser,
public isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; }; isRemoteUser,
public isLocalUser(user: User | { host: User['host'] }): boolean { });
return user.host == null;
}
public isRemoteUser(user: User): user is IRemoteUser;
public isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
public isRemoteUser(user: User | { host: User['host'] }): boolean {
return !this.isLocalUser(user);
}
}

View File

@ -27,6 +27,7 @@ export const packedEmojiSchema = {
host: { host: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
description: 'The local host is represented with `null`.',
}, },
url: { url: {
type: 'string', type: 'string',

View File

@ -21,6 +21,7 @@ export const packedUserLiteSchema = {
type: 'string', type: 'string',
nullable: true, optional: false, nullable: true, optional: false,
example: 'misskey.example.com', example: 'misskey.example.com',
description: 'The local host is represented with `null`.',
}, },
avatarUrl: { avatarUrl: {
type: 'string', type: 'string',

View File

@ -1,4 +1,5 @@
import httpSignature from 'http-signature'; import httpSignature from 'http-signature';
import { v4 as uuid } from 'uuid';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { envOption } from '../env.js'; import { envOption } from '../env.js';
@ -8,13 +9,15 @@ import processInbox from './processors/inbox.js';
import processDb from './processors/db/index.js'; import processDb from './processors/db/index.js';
import processObjectStorage from './processors/object-storage/index.js'; import processObjectStorage from './processors/object-storage/index.js';
import processSystemQueue from './processors/system/index.js'; import processSystemQueue from './processors/system/index.js';
import processWebhookDeliver from './processors/webhook-deliver.js';
import { endedPollNotification } from './processors/ended-poll-notification.js'; import { endedPollNotification } from './processors/ended-poll-notification.js';
import { queueLogger } from './logger.js'; import { queueLogger } from './logger.js';
import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFile } from '@/models/entities/drive-file.js';
import { getJobInfo } from './get-job-info.js'; import { getJobInfo } from './get-job-info.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js'; import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js';
import { ThinUser } from './types.js'; import { ThinUser } from './types.js';
import { IActivity } from '@/remote/activitypub/type.js'; import { IActivity } from '@/remote/activitypub/type.js';
import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js';
function renderError(e: Error): any { function renderError(e: Error): any {
return { return {
@ -26,6 +29,7 @@ function renderError(e: Error): any {
const systemLogger = queueLogger.createSubLogger('system'); const systemLogger = queueLogger.createSubLogger('system');
const deliverLogger = queueLogger.createSubLogger('deliver'); const deliverLogger = queueLogger.createSubLogger('deliver');
const webhookLogger = queueLogger.createSubLogger('webhook');
const inboxLogger = queueLogger.createSubLogger('inbox'); const inboxLogger = queueLogger.createSubLogger('inbox');
const dbLogger = queueLogger.createSubLogger('db'); const dbLogger = queueLogger.createSubLogger('db');
const objectStorageLogger = queueLogger.createSubLogger('objectStorage'); const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
@ -70,6 +74,14 @@ objectStorageQueue
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
webhookDeliverQueue
.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
export function deliver(user: ThinUser, content: unknown, to: string | null) { export function deliver(user: ThinUser, content: unknown, to: string | null) {
if (content == null) return null; if (content == null) return null;
if (to == null) return null; if (to == null) return null;
@ -251,18 +263,43 @@ export function createCleanRemoteFilesJob() {
}); });
} }
export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) {
const data = {
type,
content,
webhookId: webhook.id,
userId: webhook.userId,
to: webhook.url,
secret: webhook.secret,
createdAt: Date.now(),
eventId: uuid(),
};
return webhookDeliverQueue.add(data, {
attempts: 4,
timeout: 1 * 60 * 1000, // 1min
backoff: {
type: 'apBackoff',
},
removeOnComplete: true,
removeOnFail: true,
});
}
export default function() { export default function() {
if (envOption.onlyServer) return; if (envOption.onlyServer) return;
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver); deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox); inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
endedPollNotificationQueue.process(endedPollNotification); endedPollNotificationQueue.process(endedPollNotification);
webhookDeliverQueue.process(64, processWebhookDeliver);
processDb(dbQueue); processDb(dbQueue);
processObjectStorage(objectStorageQueue); processObjectStorage(objectStorageQueue);
systemQueue.add('tickCharts', { systemQueue.add('tickCharts', {
}, { }, {
repeat: { cron: '55 * * * *' }, repeat: { cron: '55 * * * *' },
removeOnComplete: true,
}); });
systemQueue.add('resyncCharts', { systemQueue.add('resyncCharts', {
@ -278,6 +315,7 @@ export default function() {
systemQueue.add('checkExpiredMutings', { systemQueue.add('checkExpiredMutings', {
}, { }, {
repeat: { cron: '*/5 * * * *' }, repeat: { cron: '*/5 * * * *' },
removeOnComplete: true,
}); });
processSystemQueue(systemQueue); processSystemQueue(systemQueue);

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