Compare commits

..

77 commits

Author SHA1 Message Date
かっこかり
e594fb0037
enhance(dev): frontendの検索インデックス作成を単独のコマンドで行えるように (#15653)
Some checks failed
Lint / locale_verify (push) Has been cancelled
API report (misskey.js) / report (pull_request) Has been cancelled
Check the description in CHANGELOG.md / check-changelog (pull_request) Has been cancelled
Check Misskey JS version / Check version (pull_request) Has been cancelled
Check SPDX-License-Identifier / check-spdx-license-id (pull_request) Has been cancelled
Dockle / dockle (pull_request) Has been cancelled
Get api.json from Misskey / get-from-misskey (api-base.json, 22.11.0, ${{ github.base_ref }}) (pull_request) Has been cancelled
Get api.json from Misskey / get-from-misskey (api-head.json, 22.11.0, refs/pull/${{ github.event.number }}/merge) (pull_request) Has been cancelled
Get api.json from Misskey / save-pr-number (pull_request) Has been cancelled
Lint / pnpm_install (pull_request) Has been cancelled
Lint / locale_verify (pull_request) Has been cancelled
Test (backend) / Unit tests (backend) (pull_request) Has been cancelled
Test (backend) / E2E tests (backend) (pull_request) Has been cancelled
Test (federation) / Federation test (pull_request) Has been cancelled
Test (frontend) / Unit tests (frontend) (pull_request) Has been cancelled
Test (frontend) / E2E tests (frontend) (pull_request) Has been cancelled
Test (misskey.js) / Unit tests (misskey.js) (pull_request) Has been cancelled
Test (production install and build) / Production build (pull_request) Has been cancelled
api.json validation / validate-api-json (22.11.0) (pull_request) Has been cancelled
Lint / lint (backend) (pull_request) Has been cancelled
Lint / lint (frontend) (pull_request) Has been cancelled
Lint / lint (frontend-embed) (pull_request) Has been cancelled
Lint / lint (frontend-shared) (pull_request) Has been cancelled
Lint / lint (misskey-bubble-game) (pull_request) Has been cancelled
Lint / lint (misskey-js) (pull_request) Has been cancelled
Lint / lint (misskey-reversi) (pull_request) Has been cancelled
Lint / lint (sw) (pull_request) Has been cancelled
Lint / typecheck (backend) (pull_request) Has been cancelled
Lint / typecheck (misskey-js) (pull_request) Has been cancelled
Lint / typecheck (sw) (pull_request) Has been cancelled
2025-03-12 14:37:57 +09:00
syuilo
a369721791 remove todo 2025-03-12 14:35:22 +09:00
syuilo
f8e244f48d enhance(frontend): アカウントオーバーライド設定とデバイス間同期の併用に対応 2025-03-12 14:34:10 +09:00
syuilo
8410611512 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-12 13:04:44 +09:00
syuilo
caab1ec7c3 🎨 2025-03-12 13:04:41 +09:00
github-actions[bot]
ffade9740e Bump version to 2025.3.2-alpha.7 2025-03-12 03:03:37 +00:00
syuilo
b03bcf26cd enhance(frontend): 設定値の同期を実装(実験的) 2025-03-12 11:39:05 +09:00
syuilo
ddbc83b2e4 chore(frontend): tweak settings page 2025-03-11 20:42:06 +09:00
syuilo
d185785f20 enhance(frontend): improve settings page 2025-03-11 14:52:04 +09:00
syuilo
02d7fbefc4 🎨 2025-03-11 12:08:15 +09:00
syuilo
f7ea92c68c chore: remove unused files 2025-03-11 12:02:41 +09:00
syuilo
e891d5c5d3 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-11 11:44:34 +09:00
syuilo
57a6b630b7 chore: add note 2025-03-11 11:44:25 +09:00
github-actions[bot]
eda768a08c Bump version to 2025.3.2-alpha.6 2025-03-11 02:43:27 +00:00
syuilo
1f345eb839 enhance(frontend): deckをpreferences管理に 2025-03-11 11:14:55 +09:00
syuilo
1f2801af02 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-10 21:42:30 +09:00
syuilo
a4ba096e2a chore(frontend): improve preference store stability 2025-03-10 21:42:17 +09:00
ろむねこ
6841cdfa76
enhance(frontend): CWの注釈テキストが入力されていない場合はPostボタンを非アクティブに (#15639)
* add condition to disable post button when CW text is empty

* standardize condition by using 1<= inserted of 0<

* unify CW text length condition to improve readability

* add missing CW state check

* fix state check, add empty/null check, improve max length validation

* simplify CW validation by removing minimum length check

* Update CHANGELOG

* remove CW text validation in post()

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-03-10 10:35:37 +00:00
github-actions[bot]
794f360bc2 Bump version to 2025.3.2-alpha.5 2025-03-10 09:40:41 +00:00
かっこかり
f797765b1d
enhance(frontend): テーマ設定で簡易プレビューを表示するように (#15643)
* enhance(frontend): テーマ設定で簡易プレビューを表示するように

* Update Changelog

* fix lint

* 🎨

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-03-10 09:35:51 +00:00
syuilo
9dce512fbb enhance(frontend): add navbar transition animation 2025-03-10 15:47:00 +09:00
syuilo
9e91f85370 refactor(frontend): use Symbol for vue provide/inject 2025-03-10 15:08:40 +09:00
syuilo
9998cb84e8 refactor(frontend): page-metadata -> page 2025-03-10 13:47:38 +09:00
renovate[bot]
5ed1101bbd
chore(deps): update [root] update dependencies (#15624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 12:30:37 +09:00
syuilo
6c9153300d chore(frontend): tweak lockdown setting 2025-03-10 12:03:54 +09:00
syuilo
7957ee5191 fix(frontend): rename pizzax fields 2025-03-10 11:28:54 +09:00
syuilo
b200743845 refactor(frontend): rename store.set -> store.commit 2025-03-10 11:27:07 +09:00
syuilo
08f7e7d9b3 refactor(frontend): rename pizzax fields 2025-03-10 10:51:54 +09:00
github-actions[bot]
16ad6b3f6c Bump version to 2025.3.2-alpha.4 2025-03-10 01:09:42 +00:00
syuilo
4df9083bf0 fix(frontend): テーマ切り替え時に一部の色が変わらない問題を修正 2025-03-10 10:05:50 +09:00
taichan
6419af2179
fix(frontend, dev): storybookのビルドエラー修正のため、as構文にリファクタ (#15640) 2025-03-10 09:34:45 +09:00
syuilo
d9858b03c9 enhance(frontend): improve plugin management 2025-03-10 09:28:07 +09:00
taichan
88efc0a3be
fix(dev): 検索インデックス対象ファイルでHMRが効かない問題を修正 (#15638) 2025-03-09 22:45:17 +00:00
github-actions[bot]
ac21fa7194 Bump version to 2025.3.2-alpha.3 2025-03-09 13:01:46 +00:00
syuilo
c76afce9a7 enhance(frontend): improve plugin management 2025-03-09 21:57:56 +09:00
github-actions[bot]
8e3304344f Bump version to 2025.3.2-alpha.2 2025-03-09 12:32:54 +00:00
饺子w (Yumechi)
db5c127cdd
fix(backend): fix handling of invalid urls in user profile (#15635)
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2025-03-09 12:28:47 +00:00
syuilo
0402866b43 enhance(frontend): improve plugin management 2025-03-09 21:23:36 +09:00
syuilo
6cefabc6b6 chore(frontend): remove unused binding 2025-03-09 17:19:21 +09:00
syuilo
c9c04d8391 enhance(frontend): migrate overridedDeviceKind to preference 2025-03-09 17:14:48 +09:00
syuilo
27e8805dcb refactor(frontend): relocate plugin consts 2025-03-09 17:02:46 +09:00
github-actions[bot]
933abedc90 Bump version to 2025.3.2-alpha.1 2025-03-09 06:16:49 +00:00
syuilo
69eee9f050 enhance(frontend): ウィジェットもpreference管理に 2025-03-09 15:13:49 +09:00
syuilo
2918fb2609 refactor(frontend): relocate theme script 2025-03-09 14:32:29 +09:00
syuilo
fcd7fa62ba Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-09 14:28:06 +09:00
syuilo
be7e3b9a0c refactor(frontend): scripts -> utility 2025-03-09 14:28:01 +09:00
github-actions[bot]
06e7272ca1 Bump version to 2025.3.2-alpha.0 2025-03-09 05:22:26 +00:00
かっこかり
f35eb0f6d9
enhnace(frontend): 文字列比較のためのローマナイズを強化(設定の検索) (#15632)
* enhnace(frontend): 文字列比較のためのローマナイズを強化

* docs

* fix

* fix

* fix

* comment

* wanakanaの初回ロードをコンポーネント内に移動

* comment

* fix

* add tests

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-03-09 14:21:23 +09:00
syuilo
bdb74539d4 enhance(frontend): tweak settings page 2025-03-09 14:18:50 +09:00
syuilo
abc1e9168d refactor 2025-03-09 12:39:43 +09:00
syuilo
d30ddd4c2e
Refine preferences (#15597)
* wip

* wip

* wip

* test

* wip rollup pluginでsearchIndexの情報生成

* wip

* SPDX

* wip: markerIdを自動付与

* rollupでビルド時・devモード時に毎回uuidを生成するように

* 開発サーバーでだけ必要な挙動は開発サーバーのみで

* 条件が逆

* wip: childrenの生成

* update comment

* update comment

* rename auto generated file

* hashをパスと行数から決定

* Update privacy.vue

* Update privacy.vue

* wip

* Update general.vue

* Update general.vue

* wip

* wip

* Update SearchMarker.vue

* wip

* Update profile.vue

* Update mute-block.vue

* Update mute-block.vue

* Update general.vue

* Update general.vue

* childrenがduplicate key errorを吐く問題をいったん解決

* マーカーの形を成形

* loggerを置きかえ

* とりあえず省略記法に対応

* Refactor and Format codes

* wip

* Update settings-search-index.ts

* wip

* wip

* とりあえず不確定要因の仮置きidを削除

* hashの生成を正規化(絶対パスになっていたのを緩和)

* pathの入力を省略可能に

* adminでもパス生成できるように

* Update settings-search-index.ts

* Update privacy.vue

* wip

* build searchIndex

* wip

* build

* Update general.vue

* build

* Update sounds.vue

* build

* build

* Update sounds.vue

* 🎨

* 🎨

* Update privacy.vue

* Update privacy.vue

* Update security.vue

* create-search-indexを多少改善

* build

* Update 2fa.vue

* wip

* 必ずtransformCodeCacheを利用するように, キャッシュの明確な受け渡しを定義

* キャッシュはdevServerでなくても更新

* Revert "wip"

This reverts commit 41bffd3a13f55618bf939dc1c9acb2a77ead4054.

* inlining

* wip

* Update theme.vue

* 🎨

* wip normalize

* Update theme.vue

* キャッシュのパス変換

* build

* wip

* wip

* Update SearchMarker.vue

* i18n.ts['key'] の形式が取り出せない問題のFix

* build

* 仮でpath入れ

* 必ず絶対パスが使われるように

* wip

* 🎨

* storybookビルド時はcreateSearchIndexをしない

* inliningの構造化

* format code

* Update index.vue

* wip

* wip

* 🎨

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* clean up

* wip

* wip

* wip

* Update rollup-plugin-unwind-css-module-class-name.test.ts

* Update navbar.vue

* clean up

* wip

* wip

* wip

* wip

* wip

* Update preferences-backups.vue

* Update common.ts

* Update preferences.ts

* wip

* wip

* wip

* wip

* Update MkPreferenceContainer.vue

* Update MkPreferenceContainer.vue

* Update MkPreferenceContainer.vue

* enhance: 検索で上下矢印を使用することで検索結果を移動できるように

* Update main-boot.ts

* refactor

* wip

* Update sounds.vue

* fix(frontend): PageWindowでSearchMarkerが動作するように

* enhance(frontend): SearchMarkerの点滅を一定時間で止める

* wip

* lint fix

* fix: 子要素監視が抜けていたのを修正

* アニメーションの回数はCSSで制御するように

* refactor

* enhance(frontend): 検索インデックス作成時のログを削減

* revert

* fix

* fix

* Update preferences.ts

* Update preferences.ts

* wip

* Update preferences.ts

* wip

* 🎨

* wip

* Update MkPreferenceContainer.vue

* wip

* Update preferences.ts

* wip

* Update preferences.ts

* Update preferences.ts

* wip

* wip

* Update preferences.ts

* wip

* wip

* Update preferences.ts

* Update CHANGELOG.md

* Update preferences.ts

* Update deck-store.ts

* deckStoreをdefaultStoreに統合

* wip

* defaultStore -> store

* Update profile.ts

* wip

* refactor

* wip: plugin

* plugin

* plugin

* plugin

* Update plugin.ts

* wip

* Update plugin.vue

* Update preferences.ts

* Update main-boot.ts

* wip

* fix test

* Update plugin.vue

* Update plugin.vue

* Update utility.ts

* wip

* wip

* Update utility.ts

* wip

* wip

* clean up

* Update utility.ts

---------

Co-authored-by: tai-cha <dev@taichan.site>
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-03-09 12:34:08 +09:00
github-actions[bot]
05cdc095c0 [skip ci] Update CHANGELOG.md (prepend template) 2025-03-09 03:30:00 +00:00
github-actions[bot]
7c1dc3d632 Release: 2025.3.1 2025-03-09 03:29:54 +00:00
github-actions[bot]
c53349c3b4 Bump version to 2025.3.1-beta.3 2025-03-09 00:33:40 +00:00
饺子w (Yumechi)
a710af54ed
fix(backend): fix ApPersonService unsound type cast (#15629)
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2025-03-09 00:11:34 +00:00
github-actions[bot]
ac07bb8d92 Bump version to 2025.3.1-beta.2 2025-03-08 10:15:42 +00:00
かっこかり
698505030e
fix(test): fix federation test (#15630) 2025-03-08 19:11:09 +09:00
かっこかり
e16a14dcef
fix(deps): pnpm v10でre2のインストールに失敗することがある問題を修正 (#15627)
* fix(deps): pnpm v10でre2のインストールに失敗することがある問題を修正

* fix

* fix docker build

* fix build failure on Windows
2025-03-08 18:56:53 +09:00
github-actions[bot]
6d93725084 Bump version to 2025.3.1-beta.1 2025-03-08 01:03:17 +00:00
おさむのひと
cb9981d4eb
fix: Dockerのrunnerにpnpmのインストール手順が欠けていたのを修正 (#15623) 2025-03-08 10:02:15 +09:00
かっこかり
bee4db82bb
fix(backend): follow-up of #15620
Removes unnecessary arg `disableGlobbing` from chokidar FSWatcher, as it is no longer supports globging
2025-03-07 20:10:32 +09:00
renovate[bot]
d7706ef1b5
fix(deps): update [root] update dependencies (major) (#15620)
* fix(deps): update [root] update dependencies

* fix: migrate tar library

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-03-07 19:42:39 +09:00
renovate[bot]
baf3f4a1d1
chore(deps): update [frontend] update dependencies to v10 (#15619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 19:41:34 +09:00
renovate[bot]
c7a56c2c2b
fix(deps): update [root] update dependencies (#15543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 19:12:50 +09:00
github-actions[bot]
8dfff79ca2 Bump version to 2025.3.1-beta.0 2025-03-07 07:07:13 +00:00
かっこかり
83c3bb839f
deps: update pnpm to v10 (#15588)
* Revert "fix(build): corepackのバグの回避 (#15387)"

This reverts commit 9c70a4e631.

* deps: update pnpm to v10

* fix broken lockfile

* update changelog

* fix

* fix

* Revert "fix"

This reverts commit 4abc6c194edc20989f5ec97d343307a4b8c9047d.

* fix

* fix

* attempt to fix docker build

* lint fixes

* fix: revertしすぎた

* detect pnpm version and install it

* fix: そもそもpnpmを2回入れる必要がないかも

* fix

* refactor

* fix

* refactor: remove unnecessary arg

* Update Dockerfile

* update pnpm to v10.6.1

* Update Changelog

* chore: use node to avoid installing jq
2025-03-07 07:03:52 +00:00
syuilo
a9fe7eff0a
New translations ja-jp.yml (Chinese Traditional) (#15618) 2025-03-07 16:01:32 +09:00
syuilo
d49ecab792
Update CHANGELOG.md 2025-03-07 14:46:10 +09:00
renovate[bot]
56459bbe68
chore(deps): update [tools] update dependencies (#15616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 11:44:20 +09:00
renovate[bot]
6c150ef1fb
fix(deps): update [frontend] update dependencies (#15617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 11:44:04 +09:00
syuilo
c78f45ea20
Update CHANGELOG.md 2025-03-07 09:12:18 +09:00
syuilo
82481c01e0 tweak MkDisableSection style 🎨 2025-03-07 09:11:51 +09:00
syuilo
741cbc34e6 Update CHANGELOG.md 2025-03-07 08:53:03 +09:00
github-actions[bot]
5e86550de3 Bump version to 2025.3.1-alpha.0 2025-03-06 23:51:21 +00:00
syuilo
92aef300ee
New Crowdin updates (#15611)
* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

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

* New translations ja-jp.yml (Italian)
2025-03-07 08:50:54 +09:00
syuilo
9ce1b68fd7 Update CHANGELOG.md 2025-03-07 08:50:32 +09:00
饺子w (Yumechi)
5be5c8bec4
fix(backend): fixup migration for incorrect extraction on system accounts table (#15613)
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2025-03-06 23:47:30 +00:00
775 changed files with 10278 additions and 21866 deletions

View file

@ -153,13 +153,6 @@ redis:
id: 'aidx'
# ┌──────────┐
#───┘ Metrics └──────────────────────────────────────────
#prometheusMetrics:
# enable: false
# scrapeToken: '' # Set non-empty to require a bearer token for scraping
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
@ -175,36 +168,12 @@ id: 'aidx'
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ──────────────┐
#──┘ Web Security └─────────────────────────────────────
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks.
# browserSandboxing:
# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true.
# strictOriginReferrer: true
# csp:
# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution.
# disable: false
# # Merge additional directives into the CSP header. The default is an empty object.
# # You may want to list your CDN or other trusted domains here.
# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added.
# appendDirectives:
# 'script-src':
# - "'unsafe-eval'" # do not use this ... just an example
# - 'https://example.com'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Number of worker processes
#clusterLimit: 1

View file

@ -165,13 +165,6 @@ fulltextSearch:
id: 'aidx'
# ┌──────────┐
#───┘ Metrics └──────────────────────────────────────────
#prometheusMetrics:
# enable: false
# scrapeToken: '' # Set non-empty to require a bearer token for scraping
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
@ -187,36 +180,12 @@ id: 'aidx'
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ──────────────┐
#──┘ Web Security └─────────────────────────────────────
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks.
# browserSandboxing:
# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true.
# strictOriginReferrer: true
# csp:
# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution.
# disable: false
# # Merge additional directives into the CSP header. The default is an empty object.
# # You may want to list your CDN or other trusted domains here.
# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added.
# appendDirectives:
# 'script-src':
# - "'unsafe-eval'" # do not use this ... just an example
# - 'https://example.com'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Number of worker processes
#clusterLimit: 1

View file

@ -247,13 +247,6 @@ fulltextSearch:
id: 'aidx'
# ┌──────────┐
#───┘ Metrics └──────────────────────────────────────────
#prometheusMetrics:
# enable: false
# scrapeToken: '' # Set non-empty to require a bearer token for scraping
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
@ -269,36 +262,12 @@ id: 'aidx'
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ──────────────┐
#──┘ Web Security └─────────────────────────────────────
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks.
# browserSandboxing:
# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true.
# strictOriginReferrer: true
# csp:
# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution.
# disable: false
# # Merge additional directives into the CSP header. The default is an empty object.
# # You may want to list your CDN or other trusted domains here.
# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added.
# appendDirectives:
# 'script-src':
# - "'unsafe-eval'" # do not use this ... just an example
# - 'https://example.com'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Number of worker processes
#clusterLimit: 1

View file

@ -1,33 +0,0 @@
#!/bin/bash
set -e
vault lease revoke -sync -prefix misskey-db/creds/misskey-test0-runtime
CREDS_JSON=$(vault read -format json misskey-db/creds/misskey-test0-runtime)
if [ "$?" -ne 0 ]; then
echo "Failed to get credentials"
exit 1
fi
export POSTGRES_USER=$(echo "$CREDS_JSON" | jq -r '.data.username')
export POSTGRES_PASSWORD=$(echo "$CREDS_JSON" | jq -r '.data.password')
export POSTGRES_HOST=misskey-db
export POSTGRES_PORT=5432
export POSTGRES_DB=misskey
export POSTGRES_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
podman run --pod misskey-web -d \
--replace \
--network misskey \
--env "POSTGRES_*" \
--volume ../var/files:/misskey/files:U \
--volume .config/:/misskey/.config:ro \
--volume ../run/misskey-podman:/run/misskey:U \
--health-cmd "misskey-auto-deploy-entrypoint :healthcheck" \
--name misskey-web \
--restart always \
misskey-podman

View file

@ -7,8 +7,8 @@
"ghcr.io/devcontainers/features/node:1": {
"version": "22.11.0"
},
"ghcr.io/devcontainers-extra/features/corepack:1": {
"version": "0.31.0"
"ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "10.6.1"
}
},
"forwardPorts": [3000],

View file

@ -161,12 +161,6 @@ id: 'aidx'
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Number of worker processes
#clusterLimit: 1

View file

@ -7,8 +7,6 @@ sudo apt-get update
sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
git config --global --add safe.directory /workspace
git submodule update --init
corepack install
corepack enable
pnpm config set store-dir /home/node/.local/share/pnpm/store
pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml

View file

@ -1,10 +1,8 @@
.autogen
.github
.forgejo
.travis
.vscode
.config
Dockerfile
build/
built/
@ -30,5 +28,3 @@ fluent-emojis/
.idea/
packages/*/.vscode/
packages/backend/test/compose.yml
/yume-mods/target

View file

@ -1,17 +0,0 @@
url: 'http://misskey.local'
setupPassword: example_password_please_change_this_or_you_will_get_hacked
# ローカルでテストするときにポートを被らないようにするためデフォルトのものとは変える(以下同じ)
port: 61812
db:
host: postgres
port: 5432
db: test-misskey
user: postgres
pass: ''
redis:
host: redis
port: 6379
id: aidx

View file

@ -1,60 +0,0 @@
name: Publish Docker image
on:
push:
branches:
- master
- develop
pull_request:
workflow_dispatch:
env:
REGISTRY_IMAGE: l1drm/yumechi-no-kuni
TAGS: |
type=edge
type=ref,event=pr
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
jobs:
# see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
build:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v4.1.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: ${{ env.TAGS }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push to Docker Hub
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
platforms: ${{ matrix.platform }}
provenance: true
labels: ${{ steps.meta.outputs.labels }}

View file

@ -1,111 +0,0 @@
name: Lint
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/frontend/**
- packages/frontend-shared/**
- packages/frontend-embed/**
- packages/sw/**
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/shared/eslint.config.js
- .forgejo/workflows/lint.yml
pull_request:
paths:
- packages/backend/**
- packages/frontend/**
- packages/frontend-shared/**
- packages/frontend-embed/**
- packages/sw/**
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/shared/eslint.config.js
- .forgejo/workflows/lint.yml
jobs:
pnpm_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.0.4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
lint:
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace:
- backend
- frontend
- frontend-shared
- frontend-embed
- sw
- misskey-js
- misskey-bubble-game
- misskey-reversi
env:
eslint-cache-version: v1
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
steps:
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.0.4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Restore eslint cache
uses: actions/cache@v4.1.0
with:
path: ${{ env.eslint-cache-path }}
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
- run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location ${{ env.eslint-cache-path }} --cache-strategy content
typecheck:
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace:
- backend
- sw
- misskey-js
steps:
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.0.4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- run: pnpm --filter misskey-js run build
if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'sw' }}
- run: pnpm --filter misskey-reversi run build
if: ${{ matrix.workspace == 'backend' }}
- run: pnpm --filter ${{ matrix.workspace }} run typecheck

View file

@ -1,98 +0,0 @@
name: Test (backend)
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
- .forgejo/workflows/test-backend.yml
- .forgejo/misskey/**
pull_request:
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
- .forgejo/workflows/test-backend.yml
- .forgejo/misskey/**
jobs:
unit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.11.0]
services:
postgres:
image: l1drm/postgres-pgroonga:alpine-15-znver4
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:7
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install FFmpeg
uses: https://github.com/FedericoCarboni/setup-ffmpeg@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .forgejo/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage
# e2e:
# runs-on: ubuntu-latest
#
# strategy:
# matrix:
# node-version: [22.11.0]
#
# services:
# postgres:
# image: l1drm/postgres-pgroonga:alpine-15-znver4
# env:
# POSTGRES_DB: test-misskey
# POSTGRES_HOST_AUTH_METHOD: trust
# redis:
# image: redis:7
#
# steps:
# - uses: actions/checkout@v4.1.1
# with:
# submodules: true
# - name: Install pnpm
# uses: pnpm/action-setup@v4
# - name: Use Node.js ${{ matrix.node-version }}
# uses: actions/setup-node@v4.0.4
# with:
# node-version: ${{ matrix.node-version }}
# cache: 'pnpm'
# - run: corepack enable
# - run: pnpm i --frozen-lockfile
# - name: Check pnpm-lock.yaml
# run: git diff --exit-code pnpm-lock.yaml
# - name: Copy Configure
# run: cp .forgejo/misskey/test.yml .config
# - name: Build
# run: pnpm build
# - name: Test
# run: pnpm --filter backend test-and-coverage:e2e

View file

@ -1,39 +0,0 @@
name: Test (production install and build)
on:
push:
branches:
- master
- develop
pull_request:
env:
NODE_ENV: production
jobs:
production:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config/default.yml
- name: Build
run: pnpm build

View file

@ -0,0 +1,97 @@
name: 🐛 Bug Report
description: Create a report to help us improve
labels: ["⚠bug?"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting!
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
- type: textarea
attributes:
label: 💡 Summary
description: Tell us what the bug is
validations:
required: true
- type: textarea
attributes:
label: 🥰 Expected Behavior
description: Tell us what should happen
validations:
required: true
- type: textarea
attributes:
label: 🤬 Actual Behavior
description: |
Tell us what happens instead of the expected behavior.
Please include errors from the developer console and/or server log files if you have access to them.
validations:
required: true
- type: textarea
attributes:
label: 📝 Steps to Reproduce
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
attributes:
label: 💻 Frontend Environment
description: |
Tell us where on the platform it happens
DO NOT WRITE "latest". Please provide the specific version.
Examples:
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
* Browser: Chrome 113.0.5672.126
* Server URL: misskey.example.com
* Misskey: 2025.x.x
value: |
* Model and OS of the device(s):
* Browser:
* Server URL:
* Misskey:
render: markdown
validations:
required: false
- type: textarea
attributes:
label: 🛰 Backend Environment (for server admin)
description: |
Tell us where on the platform it happens
DO NOT WRITE "latest". Please provide the specific version.
If you are using a managed service, put that after the version.
Examples:
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
* Misskey: 2025.x.x
* Node: 20.x.x
* PostgreSQL: 15.x.x
* Redis: 7.x.x
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
value: |
* Installation Method or Hosting Service:
* Misskey:
* Node:
* PostgreSQL:
* Redis:
* OS and Architecture:
render: markdown
validations:
required: false
- type: checkboxes
attributes:
label: Do you want to address this bug yourself?
options:
- label: Yes, I will patch the bug myself and send a pull request

View file

@ -0,0 +1,22 @@
name: ✨ Feature Request
description: Suggest an idea for this project
labels: ["✨Feature"]
body:
- type: textarea
attributes:
label: Summary
description: Tell us what the suggestion is
validations:
required: true
- type: textarea
attributes:
label: Purpose
description: Describe the specific problem or need you think this feature will solve, and who it will help.
validations:
required: true
- type: checkboxes
attributes:
label: Do you want to implement this feature yourself?
options:
- label: Yes, I will implement this by myself and send a pull request

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
contact_links:
- name: 💬 Misskey official Discord
url: https://discord.gg/Wp8gVStHW3
about: Chat freely about Misskey
# 仮
- name: 💬 Start discussion
url: https://github.com/misskey-dev/misskey/discussions
about: The official forum to join conversation and ask question

23
.github/PULL_REQUEST_TEMPLATE/01_bug.md vendored Normal file
View file

@ -0,0 +1,23 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View file

@ -0,0 +1,23 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View file

@ -0,0 +1,20 @@
## Summary
This is a release PR.
For more information on the release instructions, please see:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md#release
## For reviewers
- CHANGELOGに抜け漏れは無いか
- バージョンの上げ方は適切か
- 他にこのリリースに含めなければならない変更は無いか
- 全体的な変更内容を俯瞰し問題は無いか
- レビューされていないコミットがある場合は、それが問題ないか
- 最終的な動作確認を行い問題は無いか
などを確認し、リリースする準備が整っていると思われる場合は approve してください。
## Checklist
- [ ] package.jsonのバージョンが正しく更新されている
- [ ] CHANGELOGが過不足無く更新されている
- [ ] CIが全て通っている

View file

@ -1,7 +1,7 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
@ -17,7 +17,7 @@ https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md)
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Add story of storybook
- [ ] (If needed) Update CHANGELOG.md

View file

@ -9,10 +9,6 @@ on:
paths:
- packages/misskey-js/**
- .github/workflows/api-misskey-js.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
report:
@ -22,7 +18,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.2.2
- run: corepack enable
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Setup Node.js
uses: actions/setup-node@v4.2.0

View file

@ -9,10 +9,6 @@ on:
paths:
- packages/backend/**
- .github/workflows/get-api-diff.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
get-from-misskey:
runs-on: ubuntu-latest
@ -34,14 +30,13 @@ jobs:
with:
ref: ${{ matrix.ref }}
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml

View file

@ -28,10 +28,6 @@ on:
- packages/misskey-reversi/**
- packages/shared/eslint.config.js
- .github/workflows/lint.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
pnpm_install:
runs-on: ubuntu-latest
@ -40,12 +36,12 @@ jobs:
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
lint:
@ -71,12 +67,12 @@ jobs:
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Restore eslint cache
uses: actions/cache@v4.2.2
@ -101,12 +97,12 @@ jobs:
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- run: pnpm --filter misskey-js run build
if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'sw' }}

View file

@ -9,10 +9,6 @@ on:
paths:
- locales/**
- .github/workflows/locale.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
locale_verify:
runs-on: ubuntu-latest
@ -22,11 +18,11 @@ jobs:
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- run: cd locales && node verify.js

View file

@ -6,9 +6,6 @@ on:
workflow_dispatch:
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
publish-misskey-js:
name: Publish misskey-js
@ -26,8 +23,8 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
@ -36,7 +33,6 @@ jobs:
registry-url: 'https://registry.npmjs.org'
- name: Publish package
run: |
corepack enable
pnpm i --frozen-lockfile
pnpm build
pnpm --filter misskey-js publish --access public --no-git-checks --provenance

View file

@ -13,9 +13,6 @@ on:
# This is a waste of chromatic build quota, so we don't run storybook CI on pull requests targets master.
- master
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
build:
# chromatic is not likely to be available for fork repositories, so we disable for fork repositories.
@ -43,14 +40,13 @@ jobs:
run: |
echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT
git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3)
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js 20.x
uses: actions/setup-node@v4.2.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml

View file

@ -18,10 +18,6 @@ on:
- packages/misskey-js/**
- .github/workflows/test-backend.yml
- .github/misskey/test.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
unit:
name: Unit tests (backend)
@ -48,8 +44,8 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Install FFmpeg
run: |
for i in {1..3}; do
@ -70,7 +66,6 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
@ -111,14 +106,13 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml

View file

@ -15,9 +15,6 @@ on:
- packages/misskey-js/**
- .github/workflows/test-federation.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
test:
name: Federation test
@ -29,8 +26,8 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Install FFmpeg
run: |
for i in {1..3}; do
@ -53,7 +50,6 @@ jobs:
cache: 'pnpm'
- name: Build Misskey
run: |
corepack enable && corepack prepare
pnpm i --frozen-lockfile
pnpm build
- name: Setup

View file

@ -22,10 +22,6 @@ on:
- packages/backend/**
- .github/workflows/test-frontend.yml
- .github/misskey/test.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
vitest:
name: Unit tests (frontend)
@ -39,14 +35,13 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
@ -95,14 +90,13 @@ jobs:
# if: ${{ matrix.browser == 'firefox' }}
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Copy Configure
run: cp .github/misskey/test.yml .config

View file

@ -14,10 +14,6 @@ on:
paths:
- packages/misskey-js/**
- .github/workflows/test-misskey-js.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
test:
name: Unit tests (misskey.js)
@ -33,7 +29,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.2.2
- run: corepack enable
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0

View file

@ -9,7 +9,6 @@ on:
env:
NODE_ENV: production
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
production:
@ -24,14 +23,13 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml

View file

@ -12,10 +12,6 @@ on:
paths:
- packages/backend/**
- .github/workflows/validate-api-json.yml
env:
COREPACK_DEFAULT_TO_LATEST: 0
jobs:
validate-api-json:
runs-on: ubuntu-latest
@ -28,8 +24,8 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.1.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.2.0
with:
@ -37,7 +33,6 @@ jobs:
cache: 'pnpm'
- name: Install Redocly CLI
run: npm i -g @redocly/cli
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml

1
.gitignore vendored
View file

@ -36,7 +36,6 @@ coverage
!/.config/docker_example.yml
!/.config/docker_example.env
!/.config/cypress-devcontainer.yml
!/.config/podman_apply_example.sh
docker-compose.yml
./compose.yml
.devcontainer/compose.yml

2
.npmrc
View file

@ -1 +1,3 @@
engine-strict = true
save-exact = true
shell-emulator = true

View file

@ -0,0 +1,6 @@
build:
misskey:
args:
- NODE_ENV=development
deploy:
- helm upgrade --install misskey chart --set image=${OKTETO_BUILD_MISSKEY_IMAGE} --set url="https://misskey-$(kubectl config view --minify -o jsonpath='{..namespace}').cloud.okteto.net" --set environment=development

View file

@ -11,8 +11,5 @@
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"editor.formatOnSave": false,
"rust-analyzer.linkedProjects": [
"yume-mods/Cargo.toml",
]
"editor.formatOnSave": false
}

View file

@ -1,8 +1,36 @@
## 2025.3.2
## 2025.3.0-yumechinokuni.0
### General
-
### Client
- Feat: 設定の管理が強化されました
- 自動でバックアップされるように
- 任意の設定項目をデバイス間で同期できるように(実験的)
- Enhance: プラグインの管理が強化されました
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
- Enhance: テーマ設定画面のデザインを改善
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
### Server
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
## 2025.3.1
### General
- pnpmをv10に更新
- Corepackを削除
### Client
- Feat: 設定の検索を追加(実験的)
- Enhance: 設定項目の再配置
### Server
- Fix: DBマイグレーション際にシステムアカウントのユーザーID判定が正しくない問題を修正
- Fix: user.featured列が状況によってJSON文字列になっていたのを修正
## 2025.3.0
@ -25,14 +53,12 @@
- Fix: S3互換オブジェクトストレージでファイルのアップロードに失敗することがある問題を修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/895)
## 2025.2.1-yumechinokuni.0
- Reflect upstream changes
## 2025.2.1
### General
- Feat: アクセストークン発行時に通知するように
- Feat: 実験的なGoogleAnalyticsサポートを追加
- 依存関係の更新
### Client
@ -69,17 +95,6 @@
- Fix: クリップの説明欄を更新する際に空にできない問題を修正
- Fix: フォロワーではないユーザーにリートもしくは返信された場合にートのDeleteアクティビティが送られていない問題を修正
## 2025.2.0-yumechinokuni.2
- Enhance: 成り済まし対策として、ActivityPub照会された時にリモートのリダイレクトを拒否できるように (config.disallowExternalApRedirect)
- Fix: リモートのリダイレクトのOriginが正しくチェックされるように
- Revert: 2025.2.0-yumechinokuni.1で追加した一時措置を元に戻す
## 2025.2.0-yumechinokuni.1
- Security: Revert miskey-dev/misskey#14897
- Security: AP請求を外部ドメーンにリダイレクトしないように
## 2025.2.0
### General
@ -177,52 +192,8 @@
- Fix: 連合OFFで投稿されたートに対する冗長な処理を抑止 ( #15018 )
- Fix: `/api.json`のレスポンスが2回目のリクエスト以降おかしくなる問題を修正
## 2024.11.0-yumechinokuni.8
- Frontend: SSRでユーザープロフィールが表示されない問題を修正
- Security: SSRプライバシー方面の改善
- Security: AP Payloadの検証を強化
## 2024.11.0-yumechinokuni.7
- Misskey Trademark内容をWebUIから削除
- Service Worker キャッシュが正しく動作しない問題を修正
## 2024.11.0-yumechinokuni.6
- Upstream: 2024.11.0-alpha.4 タッグをマージする
- Performance: EmojiのリクエストをProxyでキャッシュするように
- Performance: Service Workerのキャッシュを最適化
- Security: AP Payloadの検証を強化
- Security: Image/Video Processorはドライブ機能だけを使うように
## 2024.11.0-yumechinokuni.5
- Upstream: 2024.11.0-alpha.2 タッグをマージする
- Reliability: Activitypub event deduplication
- DevOps: Prometheus サーバーメトリクス
- Enhance: ハッシュタグランギングを改善
- Enhance: PgroongaのCWサーチ + パフォーマンス改善
## 2024.11.0-yumechinokuni.4
- Upstream: 2024.11.0-alpha.1 タッグをマージする
- DevOps: 管理者アクセストークンがユーザー登録できるようになる (write:admin:create-account)
- Frontend: Stream再接続ロジックdata raceを修正
- Security: CSPにCDNなどの外部ホストはホワイトリストできるように
### 2024.11.0-yumechinokuni.4p1
PgroongaのCWサーチ (github.com/paricafe/misskey#d30db97b59d264450901c1dd86808dcb43875ea9)
### 2024.11.0-yumechinokuni.4p2
- fix(backend): アナウンスメントを作成ときにWebUIフォームの画像URLを後悔できないのを修正 (/admin/announcement/create)
## 2024.11.0-yumechinokuni.3
- Security: CSPの設定を強化
- Fix: flaky testの修正
### Misskey.js
- Feat: allow setting `binaryType` of WebSocket connection
## 2024.11.0

View file

@ -1,81 +0,0 @@
# IMPORTANT:
#
# This container has no user isolation and is designed specifically for use with Podman and ideally protected by a MAC (SELinux, AppArmor, etc).
#
# For general Docker usage, please make sure use the official Dockerfile instead.
ARG NODE_VERSION=22.11.0-bookworm
FROM rust:1-bookworm AS rust-builder
RUN apt-get update \
&& apt-get install -yqq --no-install-recommends \
build-essential libcap-ng-dev libapparmor-dev
COPY ./yume-mods /yume-mods
WORKDIR /yume-mods
ARG ENTRYPOINT_FEATURES=""
RUN cargo build -p misskey-auto-deploy-entrypoint --release --features "${ENTRYPOINT_FEATURES}"
FROM node:${NODE_VERSION} AS builder
ENV COREPACK_DEFAULT_TO_LATEST=0
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
rm -f /etc/apt/apt.conf.d/docker-clean \
; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
&& apt-get update \
&& apt-get install -yqq --no-install-recommends \
build-essential
RUN corepack enable
WORKDIR /misskey
ENV COREPACK_DEFAULT_TO_LATEST=0
COPY . ./
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output
RUN git submodule update --init
RUN pnpm build
RUN rm -rf .git/
RUN chmod -R -w /misskey
FROM --platform=$TARGETPLATFORM node:${NODE_VERSION}-slim AS runner
ENV COREPACK_DEFAULT_TO_LATEST=0
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ffmpeg curl libjemalloc-dev libjemalloc2 libcap-ng0 libapparmor1 \
&& ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
&& corepack enable \
&& mkdir -p /misskey \
&& find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \
&& find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists
WORKDIR /misskey
# add package.json to add pnpm
COPY ./package.json ./package.json
RUN corepack install
COPY --from=rust-builder --chown=0:0 --chmod=755 /yume-mods/target/release/misskey-auto-deploy-entrypoint /usr/local/bin/misskey-auto-deploy-entrypoint
COPY --from=builder --chown=0:0 /misskey/ ./
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD [":healthcheck"]
ENTRYPOINT ["/usr/local/bin/misskey-auto-deploy-entrypoint"]
CMD ["/usr/local/bin/pnpm", "run", "migrateandstart"]

View file

@ -6,8 +6,6 @@ ARG NODE_VERSION=22.11.0-bookworm
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION} AS native-builder
ENV COREPACK_DEFAULT_TO_LATEST=0
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
rm -f /etc/apt/apt.conf.d/docker-clean \
@ -16,8 +14,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
&& apt-get install -yqq --no-install-recommends \
build-essential
RUN corepack enable
WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
@ -33,6 +29,8 @@ COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bu
ARG NODE_ENV=production
RUN node -e "console.log(JSON.parse(require('node:fs').readFileSync('./package.json')).packageManager)" | xargs npm install -g
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output
@ -46,14 +44,10 @@ RUN rm -rf .git/
FROM --platform=$TARGETPLATFORM node:${NODE_VERSION} AS target-builder
ENV COREPACK_DEFAULT_TO_LATEST=0
RUN apt-get update \
&& apt-get install -yqq --no-install-recommends \
build-essential
RUN corepack enable
WORKDIR /misskey
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
@ -65,6 +59,8 @@ COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bu
ARG NODE_ENV=production
RUN node -e "console.log(JSON.parse(require('node:fs').readFileSync('./package.json')).packageManager)" | xargs npm install -g
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output
@ -72,13 +68,11 @@ FROM --platform=$TARGETPLATFORM node:${NODE_VERSION}-slim AS runner
ARG UID="991"
ARG GID="991"
ENV COREPACK_DEFAULT_TO_LATEST=0
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ffmpeg tini curl libjemalloc-dev libjemalloc2 \
&& ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
&& corepack enable \
&& groupadd -g "${GID}" misskey \
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \
&& find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \
@ -86,13 +80,13 @@ RUN apt-get update \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists
# add package.json to add pnpm
COPY ./package.json ./package.json
RUN node -e "console.log(JSON.parse(require('node:fs').readFileSync('./package.json')).packageManager)" | xargs npm install -g
USER misskey
WORKDIR /misskey
# add package.json to add pnpm
COPY --chown=misskey:misskey ./package.json ./package.json
RUN corepack install
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules

View file

@ -1,31 +1,49 @@
# ゆめちのくに
<div align="center">
<a href="https://misskey-hub.net">
<img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="300"/>
</a>
YumechiNoKuni is a fork of Misskey, with a focus on security, observability and reliability.
**🌎 **Misskey** is an open source, federated social media platform that's free forever! 🚀**
[mi.yumechi.jp](https://mi.yumechi.jp) is running this version.
[Learn more](https://misskey-hub.net/)
[Learn more about Misskey](https://misskey-hub.net/)
---
## Main differences
<a href="https://misskey-hub.net/servers/">
<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
### Unique features
<a href="https://misskey-hub.net/docs/for-admin/install/guides/">
<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
- Strict ActivityPub sanitization by whitelisting properties and normalizing all referential properties.
- Strict Content Security Policy.
- Require TLSv1.2+ over port 443 for all ActivityPub requests.
- Strongly-typed inbox filtering in Rust.
- Reduce needless retries by marking more errors as permanent.
- Detailed prometheus metrics for slow requests, DB queries, AP processing, failed auths, etc.
- Disable unauthenticated media processing and use custom AppArmored media proxy.
- Enable active users in nodeinfo back.
- Advertise Git information over nodeinfo for better observability and easy tracking of the actual code running.
- Logical replication for the database over mTLS.
- More atomic operations in API handlers.
<a href="./CONTRIBUTING.md">
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a>
### Picked from github.com/paricafe/misskey
<a href="https://discord.gg/Wp8gVStHW3">
<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a>
- pgroonga full-text search (with modifications).
- Better Service Worker caching.
- Better hashtag statistics.
- Better handling of deep recursive AP objects.
<a href="https://www.patreon.com/syuilo">
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
</div>
## Thanks
<a href="https://sentry.io/"><img src="https://github.com/misskey-dev/misskey/assets/4439005/98576556-222f-467a-94be-e98dbda1d852" height="30" alt="Sentry" /></a>
Thanks to [Sentry](https://sentry.io/) for providing the error tracking platform that helps us catch unexpected errors.
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
<a href="https://about.codecov.io/for/open-source/"><img src="https://about.codecov.io/wp-content/themes/codecov/assets/brand/sentry-cobranding/logos/codecov-by-sentry-logo.svg" height="30" alt="Codecov" /></a>
Thanks to [Codecov](https://about.codecov.io/for/open-source/) for providing the code coverage platform that helps us improve our test coverage.
<a href="https://crowdin.com/"><img src="https://user-images.githubusercontent.com/20679825/230709597-1299a011-171a-4294-a91e-355a9b37c672.svg" height="30" alt="Crowdin" /></a>
Thanks to [Crowdin](https://crowdin.com/) for providing the localization platform that helps us translate Misskey into many languages.
<a href="https://hub.docker.com/"><img src="https://user-images.githubusercontent.com/20679825/230148221-f8e73a32-a49b-47c3-9029-9a15c3824f92.png" height="30" alt="Docker" /></a>
Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.

View file

@ -1,11 +1,11 @@
# Reporting Security Issues
If you discover a security issue in this project, please use the `git blame` command to identify the source of the issue,
if it was introduced by this fork please contact me at secity<at>yumechi.jp.
If you discover a security issue in Misskey, please report it by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
For upstream issues please report by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
This will allow us to assess the risk, and make a fix available before we add a
bug report to the GitHub repository.
Thanks for helping make YumechiNoKuni safe for everyone.
Thanks for helping make Misskey safe for everyone.
> [!note]
> CNA [requires](https://www.cve.org/ResourcesSupport/AllResources/CNARules#section_5-2_Description) that CVEs include a description in English for inclusion in the CVE Catalog.
@ -14,4 +14,7 @@ Thanks for helping make YumechiNoKuni safe for everyone.
## When create a patch
If you can also create a patch to fix the vulnerability, please send a diff file with the report.
If you can also create a patch to fix the vulnerability, please create a PR on the private fork.
> [!note]
> There is a GitHub bug that prevents merging if a PR not following the develop branch of upstream, so please keep follow the develop branch.

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: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

View file

@ -182,12 +182,6 @@ id: "aidx"
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Number of worker processes
#clusterLimit: 1

View file

@ -15,7 +15,7 @@ services:
db:
restart: always
image: l1drm/postgres-pgroonga:alpine-15-znver4
image: postgres:15-alpine
ports:
- "5432:5432"
env_file:
@ -27,18 +27,6 @@ services:
interval: 5s
retries: 20
# db-publish:
# restart: always
# image: alpine/socat
# ports:
# - "5433:5433"
# volumes:
# - ./replication:/data
# command: [
# "OPENSSL-LISTEN:5433,openssl-min-proto-version=TLS1.2,cert=/data/certs/server.pub.pem,key=/data/certs/server.key.pem,cafile=/data/certs/ca.pem,fork,reuseaddr",
# "TCP4:db:5432"
# ]
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4

View file

@ -1,31 +1,7 @@
services:
nyuukyou:
build: yume-mods/nyuukyou
restart: always
user: "${MISSKEY_UID}:${MISSKEY_GID}"
profiles: [web]
links:
- web
depends_on:
web:
condition: service_healthy
networks:
- internal_network
- external_network
ports:
- "3001:3001"
volumes:
- ./nyuukyou:/store
web:
build:
context: .
args:
- UID: "${MISSKEY_UID}"
- GID: "${MISSKEY_GID}"
profiles: [web]
build: .
restart: always
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
@ -50,7 +26,6 @@ services:
redis:
restart: always
image: redis:7-alpine
user: "${MISSKEY_UID}:${MISSKEY_GID}"
networks:
- internal_network
volumes:
@ -62,9 +37,7 @@ services:
db:
restart: always
image: l1drm/postgres-pgroonga:alpine-15-znver4
user: "${MISSKEY_UID}:${MISSKEY_GID}"
shm_size: 2gb
image: postgres:15-alpine
networks:
- internal_network
env_file:
@ -76,106 +49,6 @@ services:
interval: 5s
retries: 20
replikey:
restart: always
image: l1drm/replikey:latest
profiles: [replikey-master]
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
networks:
- internal_network
- external_network
ports:
- "5443:5443"
volumes:
- ./replikey:/etc/replikey:ro
command: [
"network",
"reverse-proxy",
"--listen",
"0.0.0.0:5443",
"--cert",
"/etc/replikey/cert.pem",
"--key",
"/etc/replikey/key.pem",
"--ca",
"/etc/replikey/ca.pem",
"--redis-sni",
"${MTLS_REDIS_SNI}",
"--redis-target",
"redis:6379",
"--postgres-sni",
"${MTLS_POSTGRES_SNI}",
"--postgres-target",
"db:5432",
]
replikey-postgres-slave:
restart: always
image: l1drm/replikey:latest
profiles: [replikey-slave]
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
networks:
- internal_network
- external_network
ports:
# - "4001:4001"
volumes:
- ./replikey:/etc/replikey:ro
command: [
"network",
"forward-proxy",
"--listen",
"0.0.0.0:4001",
"--sni",
"${MTLS_POSTGRES_SNI}",
"--target",
"db:5432",
"--cert",
"/etc/replikey/cert.pem",
"--key",
"/etc/replikey/key.pem",
"--ca",
"/etc/replikey/ca.pem",
]
replikey-redis-slave:
restart: always
image: l1drm/replikey:latest
profiles: [replikey-slave]
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
networks:
- internal_network
- external_network
ports:
# - "4002:4002"
volumes:
- ./replikey:/etc/replikey:ro
command: [
"network",
"forward-proxy",
"--listen",
"0.0.0.0:4002",
"--sni",
"${MTLS_REDIS_SNI}",
"--target",
"redis:6379",
"--cert",
"/etc/replikey/cert.pem",
"--key",
"/etc/replikey/key.pem",
"--ca",
"/etc/replikey/ca.pem",
]
# mcaptcha:
# restart: always
# image: mcaptcha/mcaptcha:latest

View file

@ -111,7 +111,7 @@ followRequests: "Peticions de seguiment"
unfollow: "Deixar de seguir"
followRequestPending: "Sol·licituds de seguiment pendents"
enterEmoji: "Introduir un emoji"
renote: "Impulsos"
renote: "Impulsar"
unrenote: "Anul·la l'impuls"
renoted: "S'ha impulsat"
renotedToX: "Impulsat per {name}."
@ -1114,7 +1114,7 @@ forceShowAds: "Mostra els anuncis sempre "
addMemo: "Afegir recordatori"
editMemo: "Editar recordatori"
reactionsList: "Reaccions"
renotesList: "Impulsos"
renotesList: "Llistat d'impulsos "
notificationDisplay: "Notificacions"
leftTop: "Dalt a l'esquerra "
rightTop: "Dalt a la dreta "
@ -1190,7 +1190,7 @@ pastAnnouncements: "Informes passats"
youHaveUnreadAnnouncements: "Tens informes per llegir."
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
replies: "Respostes"
renotes: "Impulsos"
renotes: "Impulsar"
loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació "
pinnedList: "Llista fixada"
@ -2452,7 +2452,7 @@ _notification:
follow: "Segueix-me"
mention: "Menció"
reply: "Respostes"
renote: "Renotar"
renote: "Impulsar"
quote: "Citar"
reaction: "Reaccions"
pollEnded: "Enquesta terminada"
@ -2467,7 +2467,7 @@ _notification:
_actions:
followBack: "També et segueix"
reply: "Respondre"
renote: "Renotar"
renote: "Impulsos"
_deck:
alwaysShowMainColumn: "Mostrar sempre la columna principal"
columnAlign: "Alinea les columnes"

View file

@ -1311,6 +1311,8 @@ federationSpecified: "This server is operated in a whitelist federation. Interac
federationDisabled: "Federation is disabled on this server. You cannot interact with users on other servers."
confirmOnReact: "Confirm when reacting"
reactAreYouSure: "Would you like to add a \"{emoji}\" reaction?"
markAsSensitiveConfirm: "Do you want to set this media as sensitive?"
unmarkAsSensitiveConfirm: "Do you want to remove the sensitive designation for this media?"
_accountSettings:
requireSigninToViewContents: "Require sign-in to view contents"
requireSigninToViewContentsDescription1: "Require login to view all notes and other content you have created. This will have the effect of preventing crawlers from collecting your information."
@ -2134,7 +2136,6 @@ _permissions:
"read:flash-likes": "View list of liked Plays"
"write:flash-likes": "Edit list of liked Plays"
"read:admin:abuse-user-reports": "View user reports"
"write:admin:create-account": "Create user account"
"write:admin:delete-account": "Delete user account"
"write:admin:delete-all-files-of-a-user": "Delete all files of a user"
"read:admin:index-stats": "View database index stats"
@ -2595,6 +2596,7 @@ _moderationLogTypes:
deletePage: "Page deleted"
deleteFlash: "Play deleted"
deleteGalleryPost: "Gallery post deleted"
updateProxyAccountDescription: "Update the description of the proxy account"
_fileViewer:
title: "File details"
type: "File type"
@ -2858,4 +2860,8 @@ _bootErrors:
_search:
searchScopeAll: "All"
searchScopeLocal: "Local"
searchScopeServer: "Specific server"
searchScopeUser: "Specific user"
pleaseEnterServerHost: "Enter the server host"
pleaseSelectUser: "Select user"
serverHostPlaceholder: "Example: misskey.example.com"

182
locales/index.d.ts vendored
View file

@ -5278,6 +5278,176 @@ export interface Locale extends ILocale {
*
*/
"accessibility": string;
/**
*
*/
"preferencesProfile": string;
/**
* IDをコピー
*/
"copyPreferenceId": string;
/**
*
*/
"resetToDefaultValue": string;
/**
*
*/
"overrideByAccount": string;
/**
*
*/
"untitled": string;
/**
*
*/
"noName": string;
/**
*
*/
"skip": string;
/**
*
*/
"restore": string;
/**
*
*/
"syncBetweenDevices": string;
/**
*
*/
"preferenceSyncConflictTitle": string;
/**
*
*/
"preferenceSyncConflictText": string;
/**
*
*/
"preferenceSyncConflictChoiceServer": string;
/**
*
*/
"preferenceSyncConflictChoiceDevice": string;
/**
*
*/
"preferenceSyncConflictChoiceCancel": string;
"_settings": {
/**
* 使
*/
"driveBanner": string;
/**
*
*/
"pluginBanner": string;
/**
*
*/
"notificationsBanner": string;
/**
* API
*/
"api": string;
/**
* Webhook
*/
"webhook": string;
/**
*
*/
"serviceConnection": string;
/**
* Webhookの管理と設定が行えます
*/
"serviceConnectionBanner": string;
/**
*
*/
"accountData": string;
/**
* /
*/
"accountDataBanner": string;
/**
*
*/
"muteAndBlockBanner": string;
/**
* 使
*/
"accessibilityBanner": string;
/**
*
*/
"privacyBanner": string;
/**
*
*/
"securityBanner": string;
/**
*
*/
"preferencesBanner": string;
/**
*
*/
"appearanceBanner": string;
/**
*
*/
"soundsBanner": string;
};
"_preferencesProfile": {
/**
*
*/
"profileName": string;
/**
*
*/
"profileNameDescription": string;
/**
* : PC
*/
"profileNameDescription2": string;
};
"_preferencesBackup": {
/**
*
*/
"autoBackup": string;
/**
*
*/
"restoreFromBackup": string;
/**
*
*/
"noBackupsFoundTitle": string;
/**
*
*/
"noBackupsFoundDescription": string;
/**
*
*/
"selectBackupToRestore": string;
/**
*
*/
"youNeedToNameYourProfileToEnableAutoBackup": string;
/**
*
*/
"autoPreferencesBackupIsNotEnabledForThisDevice": string;
/**
*
*/
"backupFound": string;
};
"_accountSettings": {
/**
*
@ -5315,6 +5485,10 @@ export interface Locale extends ILocale {
*
*/
"mayNotEffectForFederatedNotes": string;
/**
*
*/
"mayNotEffectSomeSituations": string;
/**
*
*/
@ -7662,6 +7836,10 @@ export interface Locale extends ILocale {
*
*/
"builtinThemes": string;
/**
*
*/
"instanceTheme": string;
/**
*
*/
@ -8306,10 +8484,6 @@ export interface Locale extends ILocale {
*
*/
"read:admin:abuse-user-reports": string;
/**
*
*/
"write:admin:create-account": string;
/**
*
*/

View file

@ -1313,6 +1313,8 @@ confirmOnReact: "Confermare le reazioni"
reactAreYouSure: "Vuoi davvero reagire con {emoji} ?"
markAsSensitiveConfirm: "Vuoi davvero indicare questo contenuto multimediale come esplicito?"
unmarkAsSensitiveConfirm: "Vuoi davvero indicare come non esplicito il contenuto multimediale?"
preferences: "Preferenze"
accessibility: "Accessibilità"
_accountSettings:
requireSigninToViewContents: "Per vedere il contenuto, è necessaria l'iscrizione"
requireSigninToViewContentsDescription1: "Richiedere l'iscrizione per visualizzare tutte le Note e gli altri contenuti che hai creato. Probabilmente l'effetto è impedire la raccolta di informazioni da parte dei bot crawler."
@ -1475,7 +1477,7 @@ _serverSettings:
_accountMigration:
moveFrom: "Migra un altro profilo dentro a questo"
moveFromSub: "Crea un alias verso un altro profilo remoto"
moveFromLabel: "Profilo da cui migrare #{n}"
moveFromLabel: "Profilo da cui migrare n. {n}"
moveFromDescription: "Se desideri spostare i Follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@vecchia.istanza.it"
moveTo: "Migrare questo profilo verso un un altro"
moveToLabel: "Profilo verso cui migrare"

View file

@ -1315,6 +1315,53 @@ markAsSensitiveConfirm: "このメディアをセンシティブとして設定
unmarkAsSensitiveConfirm: "このメディアのセンシティブ指定を解除しますか?"
preferences: "環境設定"
accessibility: "アクセシビリティ"
preferencesProfile: "設定のプロファイル"
copyPreferenceId: "設定IDをコピー"
resetToDefaultValue: "初期値に戻す"
overrideByAccount: "アカウントで上書き"
untitled: "無題"
noName: "名前はありません"
skip: "スキップ"
restore: "復元"
syncBetweenDevices: "デバイス間で同期"
preferenceSyncConflictTitle: "サーバーに設定値が存在します"
preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?"
preferenceSyncConflictChoiceServer: "サーバーの設定値"
preferenceSyncConflictChoiceDevice: "デバイスの設定値"
preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
_settings:
driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。"
pluginBanner: "プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。"
notificationsBanner: "サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。"
api: "API"
webhook: "Webhook"
serviceConnection: "サービス連携"
serviceConnectionBanner: "外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。"
accountData: "アカウントのデータ"
accountDataBanner: "アカウントのデータをエクスポート/インポートして管理できます。"
muteAndBlockBanner: "非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。"
accessibilityBanner: "クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。"
privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。"
securityBanner: "パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。"
preferencesBanner: "好みに応じた、クライアントの全体的な動作の設定が行えます。"
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
_preferencesProfile:
profileName: "プロファイル名"
profileNameDescription: "このデバイスを識別する名前を設定してください。"
profileNameDescription2: "例: 「メインPC」、「スマホ」など"
_preferencesBackup:
autoBackup: "自動バックアップ"
restoreFromBackup: "バックアップから復元"
noBackupsFoundTitle: "バックアップが見つかりませんでした"
noBackupsFoundDescription: "自動で作成されたバックアップは見つかりませんでしたが、バックアップファイルを手動で保存している場合、それをインポートして復元することはできます。"
selectBackupToRestore: "復元するバックアップを選択してください"
youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効にするにはプロファイル名の設定が必要です。"
autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になっていません。"
backupFound: "設定のバックアップが見つかりました"
_accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@ -1326,6 +1373,7 @@ _accountSettings:
makeNotesHiddenBefore: "過去のノートを非公開化する"
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
mayNotEffectSomeSituations: "これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。"
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
@ -2007,6 +2055,7 @@ _theme:
installed: "{name}をインストールしました"
installedThemes: "インストールされたテーマ"
builtinThemes: "標準のテーマ"
instanceTheme: "サーバーのテーマ"
alreadyInstalled: "そのテーマは既にインストールされています"
invalid: "テーマの形式が間違っています"
make: "テーマを作る"
@ -2180,7 +2229,6 @@ _permissions:
"read:flash-likes": "Playのいいねを見る"
"write:flash-likes": "Playのいいねを操作する"
"read:admin:abuse-user-reports": "ユーザーからの通報を見る"
"write:admin:create-account": "ユーザーアカウントを作成する"
"write:admin:delete-account": "ユーザーアカウントを削除する"
"write:admin:delete-all-files-of-a-user": "ユーザーのすべてのファイルを削除する"
"read:admin:index-stats": "データベースインデックスに関する情報を見る"

View file

@ -1313,6 +1313,8 @@ confirmOnReact: "发送回应前需要确认"
reactAreYouSure: "要用「{emoji}」进行回应吗?"
markAsSensitiveConfirm: "要将此媒体标记为敏感吗?"
unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?"
preferences: "设置"
accessibility: "辅助功能"
_accountSettings:
requireSigninToViewContents: "需要登录才能显示内容"
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"

View file

@ -1313,6 +1313,8 @@ confirmOnReact: "反應時確認"
reactAreYouSure: "用「 {emoji} 」反應嗎?"
markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?"
unmarkAsSensitiveConfirm: "要解除這個媒體的敏感設定嗎?"
preferences: "環境設定"
accessibility: "輔助工具"
_accountSettings:
requireSigninToViewContents: "須登入以顯示內容"
requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。"

View file

@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2025.3.0-yumechinokuni.0",
"version": "2025.3.2-alpha.7",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://forge.yumechi.jp/yume.yumechi-no-kuni.git"
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@9.15.4",
"packageManager": "pnpm@10.6.1",
"workspaces": [
"packages/frontend-shared",
"packages/frontend",
@ -24,15 +24,16 @@
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
"start": "pnpm check:connect && cd packages/backend && cross-env RUN_MODE=web node ./built/boot/entry.js",
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test RUN_MODE=test node ./built/boot/entry.js",
"build-frontend-search-index": "pnpm --filter frontend build-search-index",
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
"init": "pnpm migrate",
"migrate": "cd packages/backend && pnpm migrate",
"revert": "cd packages/backend && pnpm revert",
"check:connect": "cd packages/backend && pnpm check:connect",
"migrateandstart": "pnpm migrate && pnpm start",
"watch": "pnpm dev",
"dev": "cross-env RUN_MODE=dev node scripts/dev.mjs",
"dev": "node scripts/dev.mjs",
"lint": "pnpm -r lint",
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
@ -47,32 +48,44 @@
"cleanall": "pnpm clean-all"
},
"resolutions": {
"chokidar": "3.6.0",
"chokidar": "4.0.3",
"lodash": "4.17.21"
},
"dependencies": {
"cssnano": "7.0.6",
"execa": "8.0.1",
"execa": "9.5.2",
"fast-glob": "3.3.3",
"ignore-walk": "6.0.5",
"ignore-walk": "7.0.0",
"js-yaml": "4.1.0",
"postcss": "8.5.2",
"tar": "6.2.1",
"postcss": "8.5.3",
"tar": "7.4.3",
"terser": "5.39.0",
"typescript": "5.7.3",
"typescript": "5.8.2",
"esbuild": "0.25.0",
"glob": "11.0.1"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "2.1.0",
"@types/node": "22.13.4",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"@types/node": "22.13.10",
"@typescript-eslint/eslint-plugin": "8.26.0",
"@typescript-eslint/parser": "8.26.0",
"cross-env": "7.0.3",
"cypress": "14.0.3",
"eslint": "9.20.1",
"globals": "15.15.0",
"cypress": "14.1.0",
"eslint": "9.22.0",
"globals": "16.0.0",
"ncp": "2.0.0",
"pnpm": "10.6.1",
"start-server-and-test": "2.0.10"
},
"optionalDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
},
"pnpm": {
"overrides": {
"@aiscript-dev/aiscript-languageserver": "-"
},
"patchedDependencies": {
"re2": "scripts/dependency-patches/re2.patch"
}
}
}

View file

@ -1,18 +0,0 @@
export class Pgroonga1730937958242 {
name = 'Pgroonga1730937958242'
async up(queryRunner) {
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_f27f5d88941e57442be75ba9c8" ON "note" USING "pgroonga" ("text")`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_7cc8d9b0ee7861b4e5dc86ad85" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2)`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_065d4d8f3b5adb4a08841eae3c" ON "user" USING "pgroonga" ("name" pgroonga_varchar_full_text_search_ops_v2)`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_fcb770976ff8240af5799e3ffc" ON "user_profile" USING "pgroonga" ("description" pgroonga_varchar_full_text_search_ops_v2) `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_f27f5d88941e57442be75ba9c8"`);
await queryRunner.query(`DROP INDEX "public"."IDX_7cc8d9b0ee7861b4e5dc86ad85"`);
await queryRunner.query(`DROP INDEX "public"."IDX_065d4d8f3b5adb4a08841eae3c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_fcb770976ff8240af5799e3ffc"`);
}
}

View file

@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class IncreaseCharacterLimits1731757142918 {
name = 'IncreaseCharacterLimits1731757142918'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_7cc8d9b0ee7861b4e5dc86ad85"`);
await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(32768)`);
await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE text`);
await queryRunner.query(`CREATE INDEX "IDX_7cc8d9b0ee7861b4e5dc86ad85" ON "note" USING "pgroonga" ("cw")`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE varchar(512)`);
await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(8192)`);
await queryRunner.query(`DROP INDEX "IDX_7cc8d9b0ee7861b4e5dc86ad85"`);
await queryRunner.query(`CREATE INDEX "IDX_7cc8d9b0ee7861b4e5dc86ad85" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops)`);
}
}

View file

@ -1,16 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project and yumechi
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class IndexUserDeleted1732071810971 {
name = 'IndexUserDeleted1732071810971'
async up(queryRunner) {
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_199b79e682bdc5ba946f491686" ON "user" ("isDeleted")`);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_199b79e682bdc5ba946f491686"`);
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class GoogleAnalytics1739006797620 {
name = 'GoogleAnalytics1739006797620'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsMeasurementId" character varying(64)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsMeasurementId"`);
}
}

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class UserFeaturedFixup1741424411879 {
name = 'UserFeaturedFixup1741424411879'
async up(queryRunner) {
await queryRunner.query(`CREATE OR REPLACE FUNCTION pg_temp.extract_ap_id(text) RETURNS text AS $$
SELECT
CASE
WHEN $1 ~ '^https?://' THEN $1
WHEN $1 LIKE '{%' THEN COALESCE(jsonb_extract_path_text($1::jsonb, 'id'), null)
ELSE null
END;
$$ LANGUAGE sql IMMUTABLE;`);
// "host" is NOT NULL is not needed but just in case add it to prevent overwriting irreplaceable data
await queryRunner.query(`UPDATE "user" SET "featured" = pg_temp.extract_ap_id("featured") WHERE "host" IS NOT NULL`);
}
async down(queryRunner) {
// fixup migration, no down migration
}
}

View file

@ -48,6 +48,8 @@
"@swc/core-win32-arm64-msvc": "1.10.16",
"@swc/core-win32-ia32-msvc": "1.10.16",
"@swc/core-win32-x64-msvc": "1.10.16",
"@tensorflow/tfjs": "4.22.0",
"@tensorflow/tfjs-node": "4.22.0",
"bufferutil": "4.0.9",
"slacc-android-arm-eabi": "0.0.10",
"slacc-android-arm64": "0.0.10",
@ -144,6 +146,7 @@
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"nodemailer": "6.10.0",
"nsfwjs": "4.2.0",
"oauth": "0.10.0",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
@ -153,7 +156,6 @@
"pg": "8.13.3",
"pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3",
"prom-client": "^15.1.3",
"promise-limit": "2.7.0",
"pug": "3.0.3",
"qrcode": "1.5.4",

View file

@ -22,7 +22,6 @@ async function connectToRedis(redisOptions) {
lazyConnect: true,
reconnectOnError: false,
showFriendlyErrorStack: true,
connectTimeout: 10000,
});
redis.on('error', e => reject(e));
@ -51,9 +50,7 @@ const promises = Array
]))
.map(connectToRedis)
.concat([
connectToPostgres().then(() => { console.log('Connected to PostgreSQL.'); }),
connectToPostgres()
]);
await Promise.allSettled(promises);
process.exit(0);
await Promise.all(promises);

View file

@ -24,7 +24,7 @@ const $config: Provider = {
const $db: Provider = {
provide: DI.db,
useFactory: async (config) => {
const db = createPostgresDataSource(config, true);
const db = createPostgresDataSource(config);
return await db.initialize();
},
inject: [DI.config],

View file

@ -4,7 +4,6 @@
*/
import { NestFactory } from '@nestjs/core';
import * as prom from 'prom-client';
import { ChartManagementService } from '@/core/chart/ChartManagementService.js';
import { QueueProcessorService } from '@/queue/QueueProcessorService.js';
import { NestLogger } from '@/NestLogger.js';
@ -13,9 +12,8 @@ import { QueueStatsService } from '@/daemons/QueueStatsService.js';
import { ServerStatsService } from '@/daemons/ServerStatsService.js';
import { ServerService } from '@/server/ServerService.js';
import { MainModule } from '@/MainModule.js';
import { MetricsService } from '@/server/api/MetricsService.js';
export async function server(workerRegistry?: prom.AggregatorRegistry<prom.PrometheusContentType>) {
export async function server() {
const app = await NestFactory.createApplicationContext(MainModule, {
logger: new NestLogger(),
});
@ -24,9 +22,6 @@ export async function server(workerRegistry?: prom.AggregatorRegistry<prom.Prome
await serverService.launch();
if (process.env.NODE_ENV !== 'test') {
if (workerRegistry) {
app.get(MetricsService).setWorkerRegistry(workerRegistry);
}
app.get(ChartManagementService).start();
app.get(QueueStatsService).start();
app.get(ServerStatsService).start();

View file

@ -8,7 +8,6 @@
*/
import cluster from 'node:cluster';
import * as prom from 'prom-client';
import { EventEmitter } from 'node:events';
import chalk from 'chalk';
import Xev from 'xev';
@ -18,15 +17,6 @@ import { masterMain } from './master.js';
import { workerMain } from './worker.js';
import { readyRef } from './ready.js';
const workerRegistry = new prom.AggregatorRegistry<prom.PrometheusContentType>();
prom.collectDefaultMetrics({
labels: {
cluster_type: `${cluster.isPrimary ? 'master' : 'worker'}`,
worker_id: cluster.worker?.id.toString() || 'none'
}
});
import 'reflect-metadata';
process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
@ -81,7 +71,7 @@ process.on('exit', code => {
if (!envOption.disableClustering) {
if (cluster.isPrimary) {
logger.info(`Start main process... pid: ${process.pid}`);
await masterMain(workerRegistry);
await masterMain();
ev.mount();
} else if (cluster.isWorker) {
logger.info(`Start worker process... pid: ${process.pid}`);

View file

@ -7,7 +7,6 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import * as os from 'node:os';
import * as prom from 'prom-client';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
@ -19,7 +18,6 @@ import type { Config } from '@/config.js';
import { showMachineInfo } from '@/misc/show-machine-info.js';
import { envOption } from '@/env.js';
import { jobQueue, server } from './common.js';
import { metricGauge } from '@/server/api/MetricsService.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -31,24 +29,6 @@ const bootLogger = logger.createSubLogger('boot', 'magenta');
const themeColor = chalk.hex('#86b300');
const mBuildInfo = metricGauge({
name: 'misskey_build_info',
help: 'Misskey build information',
labelNames: ['gitCommit', 'gitDescribe', 'node_version']
});
mBuildInfo?.set({
gitCommit: meta.gitCommit || 'unknown',
gitDescribe: meta.gitDescribe || 'unknown',
node_version: process.version
}, 1);
const mStartupTime = metricGauge({
name: 'misskey_startup_time',
help: 'Misskey startup time',
labelNames: ['pid']
});
function greet() {
if (!envOption.quiet) {
//#region Misskey logo
@ -74,7 +54,7 @@ function greet() {
/**
* Init master process
*/
export async function masterMain(workerRegistry?: prom.AggregatorRegistry<prom.PrometheusContentType>) {
export async function masterMain() {
let config!: Config;
// initialize app
@ -84,7 +64,6 @@ export async function masterMain(workerRegistry?: prom.AggregatorRegistry<prom.P
await showMachineInfo(bootLogger);
showNodejsVersion();
config = loadConfigBoot();
//await connectDb();
if (config.pidFile) fs.writeFileSync(config.pidFile, process.pid.toString());
} catch (e) {
@ -112,28 +91,36 @@ export async function masterMain(workerRegistry?: prom.AggregatorRegistry<prom.P
});
}
mStartupTime?.set({ pid: process.pid }, Date.now());
bootLogger.info(
`mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]`,
);
if (!envOption.disableClustering) {
// clusterモジュール有効時
if (envOption.disableClustering) {
if (envOption.onlyServer) {
await server(workerRegistry);
// onlyServer かつ enableCluster な場合、メインプロセスはforkのみに制限する(listenしない)。
// ワーカープロセス側でlistenすると、メインプロセスでポートへの着信を受け入れてワーカープロセスへの分配を行う動作をする。
// そのため、メインプロセスでも直接listenするとポートの競合が発生して起動に失敗してしまう。
// see: https://nodejs.org/api/cluster.html#cluster
} else if (envOption.onlyQueue) {
await jobQueue();
} else {
await server(workerRegistry);
await jobQueue();
}
} else {
if (envOption.onlyServer) {
// nop
} else if (envOption.onlyQueue) {
// nop
await jobQueue();
} else {
await server(workerRegistry);
await server();
}
await spawnWorkers(config.clusterLimit);
} else {
// clusterモジュール無効時
if (envOption.onlyServer) {
await server();
} else if (envOption.onlyQueue) {
await jobQueue();
} else {
await server();
await jobQueue();
}
}
if (envOption.onlyQueue) {

View file

@ -4,7 +4,6 @@
*/
import cluster from 'node:cluster';
import { collectDefaultMetrics, AggregatorRegistry, RegistryContentType } from 'prom-client';
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';
import { envOption } from '@/env.js';

View file

@ -9,7 +9,6 @@ import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
import * as Sentry from '@sentry/node';
import type { RedisOptions } from 'ioredis';
import { type CSPHashed, hashResource, hashSourceFile } from './server/csp.js';
type RedisOptionsSource = Partial<RedisOptions> & {
host: string;
@ -20,18 +19,6 @@ type RedisOptionsSource = Partial<RedisOptions> & {
prefix?: string;
};
type BrowserSandboxing = {
// send Referrer-Policy: strict-origin
strictOriginReferrer?: boolean;
csp?: {
disable?: boolean;
appendDirectives?: {
[directive: string]: string | string[];
}
};
};
/**
*
*/
@ -41,7 +28,6 @@ type Source = {
socket?: string;
chmodSocket?: string;
disableHsts?: boolean;
hstsPreload?: boolean;
db: {
host: string;
port: number;
@ -75,16 +61,11 @@ type Source = {
index: string;
scope?: 'local' | 'global' | string[];
};
prometheusMetrics?: { enable: boolean, scrapeToken?: string };
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
browserSandboxing?: BrowserSandboxing;
setupPassword?: string;
proxy?: string;
@ -137,7 +118,6 @@ export type Config = {
socket: string | undefined;
chmodSocket: string | undefined;
disableHsts: boolean | undefined;
hstsPreload: boolean | undefined;
db: {
host: string;
port: number;
@ -193,13 +173,7 @@ export type Config = {
}
}
browserSandboxing: BrowserSandboxing;
cspPrerenderedContent: Map<string, CSPHashed>;
version: string;
gitDescribe: string;
gitCommit: string;
publishTarballInsteadOfProvideRepositoryUrl: boolean;
setupPassword: string | undefined;
host: string;
@ -223,12 +197,8 @@ export type Config = {
redisForJobQueue: RedisOptions & RedisOptionsSource;
redisForTimelines: RedisOptions & RedisOptionsSource;
redisForReactions: RedisOptions & RedisOptionsSource;
prometheusMetrics: { enable: boolean, scrapeToken?: string } | undefined;
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
@ -269,7 +239,7 @@ export function loadConfig(): Config {
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
const { version, gitDescribe, gitCommit } = meta;
const version = meta.version;
const host = url.host;
const hostname = url.hostname;
const scheme = url.protocol.replace(/:$/, '');
@ -284,21 +254,9 @@ export function loadConfig(): Config {
: null;
const internalMediaProxy = `${scheme}://${host}/proxy`;
const redis = convertRedisOptions(config.redis, host);
const htmlScriptPrelude = `var VERSION = ${JSON.stringify(version)}; var CLIENT_ENTRY = ${JSON.stringify(frontendManifest['src/_boot_.ts'].file)};`;
const htmlEmbedScriptPrelude = `var VERSION = ${JSON.stringify(version)}; var CLIENT_ENTRY = ${JSON.stringify(frontendEmbedManifest['src/boot.ts'].file)};`;
const cspPrerenderedContent = new Map([
['.prelude.js', hashResource(htmlScriptPrelude)],
['.prelude.embed.js', hashResource(htmlEmbedScriptPrelude)],
...['boot.js', 'style.css', 'style.embed.css', 'boot.embed.js',
'bios.css', 'bios.js', 'cli.css', 'cli.js', 'error.css'
].map((file) => [file, hashSourceFile(`${_dirname}/server/web/${file}`)] as [string, CSPHashed]),
]);
return {
version,
gitCommit,
gitDescribe,
browserSandboxing: config.browserSandboxing ?? { strictOriginReferrer: true },
publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
setupPassword: config.setupPassword,
url: url.origin,
@ -306,8 +264,6 @@ export function loadConfig(): Config {
socket: config.socket,
chmodSocket: config.chmodSocket,
disableHsts: config.disableHsts,
hstsPreload: config.hstsPreload ?? false,
cspPrerenderedContent,
host,
hostname,
scheme,
@ -326,7 +282,6 @@ export function loadConfig(): Config {
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
prometheusMetrics: config.prometheusMetrics,
sentryForBackend: config.sentryForBackend,
sentryForFrontend: config.sentryForFrontend,
id: config.id,

View file

@ -1,32 +0,0 @@
import crypto from 'node:crypto';
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class ActorKeySignerService {
private proxyUri?: string;
constructor() {
this.proxyUri = process.env.MISSKEY_ACTOR_KEY_PROXY_URL;
}
public async sign(id: string, privateKey: string, data: string): Promise<string> {
if (this.proxyUri && privateKey === 'proxy') {
const response = await fetch(`${this.proxyUri}signature/${id}`, {
method: 'POST',
body: data,
});
if (!response.ok) {
throw new Error(`Failed to sign data: ${response.statusText}`);
}
return response.text();
}
const signer = crypto.createSign('sha256');
signer.update(data);
signer.end();
const signature = signer.sign(privateKey);
return signature.toString('base64');
}
}

View file

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Injectable } from '@nestjs/common';
import * as nsfw from 'nsfwjs';
import si from 'systeminformation';
import { Mutex } from 'async-mutex';
import fetch from 'node-fetch';
import { bindThis } from '@/decorators.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const REQUIRED_CPU_FLAGS_X64 = ['avx2', 'fma'];
let isSupportedCpu: undefined | boolean = undefined;
@Injectable()
export class AiService {
private model: nsfw.NSFWJS;
private modelLoadMutex: Mutex = new Mutex();
constructor(
) {
}
@bindThis
public async detectSensitive(path: string): Promise<nsfw.PredictionType[] | null> {
try {
if (isSupportedCpu === undefined) {
isSupportedCpu = await this.computeIsSupportedCpu();
}
if (!isSupportedCpu) {
console.error('This CPU cannot use TensorFlow.');
return null;
}
const tf = await import('@tensorflow/tfjs-node');
tf.env().global.fetch = fetch;
if (this.model == null) {
await this.modelLoadMutex.runExclusive(async () => {
if (this.model == null) {
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
}
});
}
const buffer = await fs.promises.readFile(path);
const image = await tf.node.decodeImage(buffer, 3) as any;
try {
const predictions = await this.model.classify(image);
return predictions;
} finally {
image.dispose();
}
} catch (err) {
console.error(err);
return null;
}
}
private async computeIsSupportedCpu(): Promise<boolean> {
switch (process.arch) {
case 'x64': {
const cpuFlags = await this.getCpuFlags();
return REQUIRED_CPU_FLAGS_X64.every(required => cpuFlags.includes(required));
}
case 'arm64': {
// As far as I know, no required CPU flags for ARM64.
return true;
}
default: {
return false;
}
}
}
@bindThis
private async getCpuFlags(): Promise<string[]> {
const str = await si.cpuFlags();
return str.split(/\s+/);
}
}

View file

@ -17,6 +17,7 @@ import { WebhookTestService } from '@/core/WebhookTestService.js';
import { FlashService } from '@/core/FlashService.js';
import { AccountMoveService } from './AccountMoveService.js';
import { AccountUpdateService } from './AccountUpdateService.js';
import { AiService } from './AiService.js';
import { AnnouncementService } from './AnnouncementService.js';
import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js';
@ -34,7 +35,7 @@ import { GlobalEventService } from './GlobalEventService.js';
import { HashtagService } from './HashtagService.js';
import { HttpRequestService } from './HttpRequestService.js';
import { IdService } from './IdService.js';
import { __YUME_PRIVATE_ImageProcessingService } from './ImageProcessingService.js';
import { ImageProcessingService } from './ImageProcessingService.js';
import { SystemAccountService } from './SystemAccountService.js';
import { InternalStorageService } from './InternalStorageService.js';
import { MetaService } from './MetaService.js';
@ -65,7 +66,7 @@ import { UserMutingService } from './UserMutingService.js';
import { UserRenoteMutingService } from './UserRenoteMutingService.js';
import { UserSuspendService } from './UserSuspendService.js';
import { UserAuthService } from './UserAuthService.js';
import { __YUME_PRIVATE_VideoProcessingService } from './VideoProcessingService.js';
import { VideoProcessingService } from './VideoProcessingService.js';
import { UserWebhookService } from './UserWebhookService.js';
import { UtilityService } from './UtilityService.js';
import { FileInfoService } from './FileInfoService.js';
@ -150,7 +151,6 @@ import { QueueModule } from './QueueModule.js';
import { QueueService } from './QueueService.js';
import { LoggerService } from './LoggerService.js';
import type { Provider } from '@nestjs/common';
import { ActorKeySignerService } from './ActorKeySignerService.js';
//#region 文字列ベースでのinjection用(循環参照対応のため)
const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService };
@ -158,6 +158,7 @@ const $AbuseReportService: Provider = { provide: 'AbuseReportService', useExisti
const $AbuseReportNotificationService: Provider = { provide: 'AbuseReportNotificationService', useExisting: AbuseReportNotificationService };
const $AccountMoveService: Provider = { provide: 'AccountMoveService', useExisting: AccountMoveService };
const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService };
const $AiService: Provider = { provide: 'AiService', useExisting: AiService };
const $AnnouncementService: Provider = { provide: 'AnnouncementService', useExisting: AnnouncementService };
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
@ -175,7 +176,7 @@ const $GlobalEventService: Provider = { provide: 'GlobalEventService', useExisti
const $HashtagService: Provider = { provide: 'HashtagService', useExisting: HashtagService };
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
const $ImageProcessingService: Provider = { provide: '__YUME_PRIVATE_ImageProcessingService', useExisting: __YUME_PRIVATE_ImageProcessingService };
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
@ -207,7 +208,7 @@ const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService',
const $UserSearchService: Provider = { provide: 'UserSearchService', useExisting: UserSearchService };
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
const $VideoProcessingService: Provider = { provide: '__YUME_PRIVATE_VideoProcessingService', useExisting: __YUME_PRIVATE_VideoProcessingService };
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService };
@ -306,6 +307,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AbuseReportNotificationService,
AccountMoveService,
AccountUpdateService,
AiService,
AnnouncementService,
AntennaService,
AppLockService,
@ -323,7 +325,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
HashtagService,
HttpRequestService,
IdService,
__YUME_PRIVATE_ImageProcessingService,
ImageProcessingService,
InternalStorageService,
MetaService,
MfmService,
@ -355,7 +357,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserSearchService,
UserSuspendService,
UserAuthService,
__YUME_PRIVATE_VideoProcessingService,
VideoProcessingService,
UserWebhookService,
SystemWebhookService,
WebhookTestService,
@ -432,7 +434,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ApMfmService,
ApRendererService,
ApRequestService,
ActorKeySignerService,
ApResolverService,
JsonLdService,
RemoteLoggerService,
@ -451,6 +452,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AbuseReportNotificationService,
$AccountMoveService,
$AccountUpdateService,
$AiService,
$AnnouncementService,
$AntennaService,
$AppLockService,
@ -596,6 +598,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AbuseReportNotificationService,
AccountMoveService,
AccountUpdateService,
AiService,
AnnouncementService,
AntennaService,
AppLockService,
@ -613,7 +616,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
HashtagService,
HttpRequestService,
IdService,
__YUME_PRIVATE_ImageProcessingService,
ImageProcessingService,
InternalStorageService,
MetaService,
MfmService,
@ -645,7 +648,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserSearchService,
UserSuspendService,
UserAuthService,
__YUME_PRIVATE_VideoProcessingService,
VideoProcessingService,
UserWebhookService,
SystemWebhookService,
WebhookTestService,
@ -739,6 +742,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AbuseReportNotificationService,
$AccountMoveService,
$AccountUpdateService,
$AiService,
$AnnouncementService,
$AntennaService,
$AppLockService,

View file

@ -57,10 +57,6 @@ export class DeleteAccountService {
});
}
if (!(await this.usersRepository.update({ id: user.id, isDeleted: false }, { isDeleted: true })).affected) {
return;
}
// 物理削除する前にDelete activityを送信する
if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信

View file

@ -44,14 +44,6 @@ export class DownloadService {
const maxSize = this.config.maxFileSize;
const urlObj = new URL(url);
if (urlObj.protocol && urlObj.protocol !== 'https:') {
throw new Error(`Unsupported protocol: ${urlObj.protocol}, only HTTPS is supported`);
}
urlObj.protocol = 'https:';
if (urlObj.port && urlObj.port !== '443') {
throw new Error(`Unsupported port: ${urlObj.port}, only 443 is supported`);
}
let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
const req = got.stream(url, {
@ -68,7 +60,8 @@ export class DownloadService {
request: operationTimeout, // whole operation timeout
},
agent: {
https: this.httpRequestService.httpsAgent,
http: this.httpRequestService.getAgentForHttp(urlObj, true),
https: this.httpRequestService.getAgentForHttps(urlObj, true),
},
http2: false, // default
retry: {

View file

@ -22,8 +22,8 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { contentDisposition } from '@/misc/content-disposition.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { __YUME_PRIVATE_VideoProcessingService } from '@/core/VideoProcessingService.js';
import { __YUME_PRIVATE_ImageProcessingService } from '@/core/ImageProcessingService.js';
import { VideoProcessingService } from '@/core/VideoProcessingService.js';
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import type { IImage } from '@/core/ImageProcessingService.js';
import { QueueService } from '@/core/QueueService.js';
import type { MiDriveFolder } from '@/models/DriveFolder.js';
@ -120,8 +120,8 @@ export class DriveService {
private downloadService: DownloadService,
private internalStorageService: InternalStorageService,
private s3Service: S3Service,
private privateImageProcessingService: __YUME_PRIVATE_ImageProcessingService,
private privateVideoProcessingService: __YUME_PRIVATE_VideoProcessingService,
private imageProcessingService: ImageProcessingService,
private videoProcessingService: VideoProcessingService,
private globalEventService: GlobalEventService,
private queueService: QueueService,
private roleService: RoleService,
@ -278,7 +278,7 @@ export class DriveService {
}
try {
const thumbnail = await this.privateVideoProcessingService.generateVideoThumbnail(path);
const thumbnail = await this.videoProcessingService.generateVideoThumbnail(path);
return {
webpublic: null,
thumbnail,
@ -332,9 +332,9 @@ export class DriveService {
try {
if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) {
webpublic = await this.privateImageProcessingService.convertSharpToWebp(img, 2048, 2048);
webpublic = await this.imageProcessingService.convertSharpToWebp(img, 2048, 2048);
} else if (['image/png', 'image/bmp', 'image/svg+xml'].includes(type)) {
webpublic = await this.privateImageProcessingService.convertSharpToPng(img, 2048, 2048);
webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
} else {
this.registerLogger.debug('web image not created (not an required image)');
}
@ -353,9 +353,9 @@ export class DriveService {
try {
if (isAnimated) {
thumbnail = await this.privateImageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 });
thumbnail = await this.imageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 });
} else {
thumbnail = await this.privateImageProcessingService.convertSharpToWebp(img, 498, 422);
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 422);
}
} catch (err) {
this.registerLogger.warn('thumbnail not created (an error occurred)', err as Error);

View file

@ -145,7 +145,6 @@ export class EmailService {
try {
// TODO: htmlサニタイズ
const info = await transporter.sendMail({
replyTo: this.meta.maintainerEmail ? { name: this.meta.maintainerName || 'Instance Maintainer', address: this.meta.maintainerEmail } : undefined,
from: this.meta.email!,
to: to,
subject: subject,

View file

@ -12,7 +12,7 @@ import { bindThis } from '@/decorators.js';
const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
export const GALLERY_POSTS_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
const PER_USER_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 7; // 1週間ごと
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60 * 48; // 48時間ごと
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60; // 1時間ごと
const featuredEpoc = new Date('2023-01-01T00:00:00Z').getTime();

View file

@ -15,9 +15,12 @@ import isSvg from 'is-svg';
import probeImageSize from 'probe-image-size';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import * as blurhash from 'blurhash';
import { createTempDir } from '@/misc/create-temp.js';
import { AiService } from '@/core/AiService.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import type { PredictionType } from 'nsfwjs';
export type FileInfo = {
size: number;
@ -50,6 +53,7 @@ export class FileInfoService {
private logger: Logger;
constructor(
private aiService: AiService,
private loggerService: LoggerService,
) {
this.logger = this.loggerService.getLogger('file-info');
@ -163,13 +167,107 @@ export class FileInfoService {
@bindThis
private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> {
return [false, false];
let sensitive = false;
let porn = false;
function judgePrediction(result: readonly PredictionType[]): [sensitive: boolean, porn: boolean] {
let sensitive = false;
let porn = false;
if ((result.find(x => x.className === 'Sexy')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
if ((result.find(x => x.className === 'Hentai')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThresholdForPorn) porn = true;
return [sensitive, porn];
}
if ([
'image/jpeg',
'image/png',
'image/webp',
].includes(mime)) {
const result = await this.aiService.detectSensitive(source);
if (result) {
[sensitive, porn] = judgePrediction(result);
}
} else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
const [outDir, disposeOutDir] = await createTempDir();
try {
const command = FFmpeg()
.input(source)
.inputOptions([
'-skip_frame', 'nokey', // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない)
'-lowres', '3', // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない)
])
.noAudio()
.videoFilters([
{
filter: 'select', // フレームのフィルタリング
options: {
e: 'eq(pict_type,PICT_TYPE_I)', // I-Frame のみをフィルタするVP9 とかはデコードしてみないとわからないっぽい)
},
},
{
filter: 'blackframe', // 暗いフレームの検出
options: {
amount: '0', // 暗さに関わらず全てのフレームで測定値を取る
},
},
{
filter: 'metadata',
options: {
mode: 'select', // フレーム選択モード
key: 'lavfi.blackframe.pblack', // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する)
value: '50',
function: 'less', // 50% 未満のフレームを選択する50% 以上暗部があるフレームだと誤検知を招くかもしれないので)
},
},
{
filter: 'scale',
options: {
w: 299,
h: 299,
},
},
])
.format('image2')
.output(join(outDir, '%d.png'))
.outputOptions(['-vsync', '0']); // 可変フレームレートにすることで穴埋めをさせない
const results: ReturnType<typeof judgePrediction>[] = [];
let frameIndex = 0;
let targetIndex = 0;
let nextIndex = 1;
for await (const path of this.asyncIterateFrames(outDir, command)) {
try {
const index = frameIndex++;
if (index !== targetIndex) {
continue;
}
targetIndex = nextIndex;
nextIndex += index; // fibonacci sequence によってフレーム数制限を掛ける
const result = await this.aiService.detectSensitive(path);
if (result) {
results.push(judgePrediction(result));
}
} finally {
fs.promises.unlink(path);
}
}
sensitive = results.filter(x => x[0]).length >= Math.ceil(results.length * sensitiveThreshold);
porn = results.filter(x => x[1]).length >= Math.ceil(results.length * sensitiveThresholdForPorn);
} finally {
disposeOutDir();
}
}
return [sensitive, porn];
}
private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator<string, void> {
const watcher = new FSWatcher({
cwd,
disableGlobbing: true,
});
let finished = false;
command.once('end', () => {

View file

@ -9,7 +9,7 @@ import * as net from 'node:net';
import ipaddr from 'ipaddr.js';
import CacheableLookup from 'cacheable-lookup';
import fetch from 'node-fetch';
import { HttpsProxyAgent } from 'hpagent';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@ -19,7 +19,7 @@ import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/val
import { assertActivityMatchesUrls, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from '@/core/activitypub/type.js';
import type { Response } from 'node-fetch';
import { URL } from 'node:url';
import type { URL } from 'node:url';
export type HttpRequestSendOptions = {
throwErrorWhenResponseNotOk: boolean;
@ -59,12 +59,14 @@ class HttpRequestServiceAgent extends http.Agent {
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}
@ -96,12 +98,14 @@ class HttpsRequestServiceAgent extends https.Agent {
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}
@ -153,15 +157,30 @@ export class HttpRequestService {
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,
localAddress: config.outgoingAddress,
minVersion: 'TLSv1.2' as const,
};
this.httpNative = new http.Agent(agentOption);
this.httpsNative = new https.Agent(agentOption);
this.http = new HttpRequestServiceAgent(config, agentOption);
this.https = new HttpsRequestServiceAgent(config, agentOption);
const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128);
this.httpAgent = config.proxy
? new HttpProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
localAddress: config.outgoingAddress,
})
: this.http;
this.httpsAgent = config.proxy
? new HttpsProxyAgent({
keepAlive: true,
@ -303,15 +322,7 @@ export class HttpRequestService {
controller.abort();
}, timeout);
const urlParsed = new URL(url);
if (urlParsed.protocol !== 'https:') {
throw new Error('Invalid protocol');
}
if (urlParsed.port && urlParsed.port !== '443') {
throw new Error('Invalid port');
}
const isLocalAddressAllowed = args.isLocalAddressAllowed ?? false;
const res = await fetch(url, {
method: args.method ?? 'GET',
@ -321,7 +332,7 @@ export class HttpRequestService {
},
body: args.body,
size: args.size ?? 10 * 1024 * 1024,
agent: (url) => this.getAgentByUrl(url, false),
agent: (url) => this.getAgentByUrl(url, false, isLocalAddressAllowed),
signal: controller.signal,
});

View file

@ -46,9 +46,7 @@ import { bindThis } from '@/decorators.js';
import { Readable } from 'node:stream';
@Injectable()
// Prevent accidental import by upstream merge
// eslint-disable-next-line
export class __YUME_PRIVATE_ImageProcessingService {
export class ImageProcessingService {
constructor(
) {
}

View file

@ -7,7 +7,6 @@ import { setImmediate } from 'node:timers/promises';
import * as mfm from 'mfm-js';
import { In, DataSource, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis';
import * as Bull from 'bullmq';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
@ -294,7 +293,7 @@ export class NoteCreateService implements OnApplicationShutdown {
case 'followers':
// 他人のfollowers noteはreject
if (data.renote.userId !== user.id) {
throw new Bull.UnrecoverableError('Renote target is not public or home');
throw new Error('Renote target is not public or home');
}
// Renote対象がfollowersならfollowersにする
@ -302,7 +301,7 @@ export class NoteCreateService implements OnApplicationShutdown {
break;
case 'specified':
// specified / direct noteはreject
throw new Bull.UnrecoverableError('Renote target is not public or home');
throw new Error('Renote target is not public or home');
}
}

View file

@ -21,13 +21,6 @@ import type { Config } from '@/config.js';
import { UserListService } from '@/core/UserListService.js';
import type { FilterUnionByProperty } from '@/types.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { metricCounter } from '@/server/api/MetricsService.js';
const mNotificationsCreated = metricCounter({
name: 'misskey_notifications_created',
help: 'Notifications created',
labelNames: ['event_type'],
});
@Injectable()
export class NotificationService implements OnApplicationShutdown {
@ -172,8 +165,6 @@ export class NotificationService implements OnApplicationShutdown {
if (packed == null) return null;
mNotificationsCreated?.inc({ event_type: notification.type });
// Publish notification event
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);

View file

@ -13,19 +13,6 @@ import { getNoteSummary } from '@/misc/get-note-summary.js';
import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { RedisKVCache } from '@/misc/cache.js';
import { metricCounter } from '@/server/api/MetricsService.js';
const mWebPushCreated = metricCounter({
name: 'misskey_webpush_created',
help: 'WebPush event',
labelNames: ['event_type'],
});
const mWebPushError = metricCounter({
name: 'misskey_webpush_error',
help: 'WebPush error',
labelNames: ['event_type', 'status'],
});
// Defined also packages/sw/types.ts#L13
type PushNotificationsTypes = {
@ -108,8 +95,6 @@ export class PushNotificationService implements OnApplicationShutdown {
},
};
mWebPushCreated?.inc({ event_type: type });
push.sendNotification(pushSubscription, JSON.stringify({
type,
body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body,
@ -131,8 +116,6 @@ export class PushNotificationService implements OnApplicationShutdown {
}).then(() => {
this.refreshCache(userId);
});
} else {
mWebPushError?.inc({ event_type: type, status: err.statusCode || 'unknown' });
}
});
}

View file

@ -18,7 +18,6 @@ import {
SystemWebhookDeliverJobData,
} from '../queue/types.js';
import type { Provider } from '@nestjs/common';
import { mActiveJobs, mDelayedJobs, mFailedJobs, mJobBlockedCounter, mWaitingJobs } from '@/queue/metrics.js';
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
@ -30,74 +29,57 @@ export type ObjectStorageQueue = Bull.Queue;
export type UserWebhookDeliverQueue = Bull.Queue<UserWebhookDeliverJobData>;
export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>;
function withMetrics<T>(queue: Bull.Queue<T>): Bull.Queue<T> {
if (process.env.NODE_ENV !== 'test') {
setInterval(async () => {
mActiveJobs?.set({ queue: queue.name }, await queue.getActiveCount());
mDelayedJobs?.set({ queue: queue.name }, await queue.getDelayedCount());
mWaitingJobs?.set({ queue: queue.name }, await queue.getWaitingCount());
mFailedJobs?.set({ queue: queue.name }, await queue.getFailedCount());
}, 2000);
queue.on('waiting', () => {
mJobBlockedCounter?.inc({ queue: queue.name });
});
}
return queue;
}
const $system: Provider = {
provide: 'queue:system',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)),
inject: [DI.config],
};
const $endedPollNotification: Provider = {
provide: 'queue:endedPollNotification',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION)),
inject: [DI.config],
};
const $deliver: Provider = {
provide: 'queue:deliver',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
inject: [DI.config],
};
const $inbox: Provider = {
provide: 'queue:inbox',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX)),
inject: [DI.config],
};
const $db: Provider = {
provide: 'queue:db',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)),
inject: [DI.config],
};
const $relationship: Provider = {
provide: 'queue:relationship',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP)),
inject: [DI.config],
};
const $objectStorage: Provider = {
provide: 'queue:objectStorage',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE)),
inject: [DI.config],
};
const $userWebhookDeliver: Provider = {
provide: 'queue:userWebhookDeliver',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.USER_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.USER_WEBHOOK_DELIVER))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.USER_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.USER_WEBHOOK_DELIVER)),
inject: [DI.config],
};
const $systemWebhookDeliver: Provider = {
provide: 'queue:systemWebhookDeliver',
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.SYSTEM_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.SYSTEM_WEBHOOK_DELIVER))),
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.SYSTEM_WEBHOOK_DELIVER)),
inject: [DI.config],
};

View file

@ -30,9 +30,6 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
import { UserWebhookPayload, UserWebhookService } from './UserWebhookService.js';
import { QueueService } from './QueueService.js';
import { Packed } from '@/misc/json-schema.js';
const FALLBACK = '\u2764';
@ -97,8 +94,6 @@ export class ReactionService {
private reactionsBufferingService: ReactionsBufferingService,
private idService: IdService,
private featuredService: FeaturedService,
private queueService: QueueService,
private webhookService: UserWebhookService,
private globalEventService: GlobalEventService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
@ -169,10 +164,6 @@ export class ReactionService {
}
}
if (/['\\]/.test(reaction)) {
throw new IdentifiableError('cf61d38c-598a-49e2-b75a-6c38671fcc43', 'Invalid reaction.');
}
const record: MiNoteReaction = {
id: this.idService.gen(),
noteId: note.id,
@ -263,33 +254,12 @@ export class ReactionService {
userId: user.id,
});
// リアクションされたユーザーがローカルユーザーなら通知を作成してWebhookを送信
// リアクションされたユーザーがローカルユーザーなら通知を作成
if (note.userHost === null) {
this.notificationService.createNotification(note.userId, 'reaction', {
noteId: note.id,
reaction: reaction,
}, user.id);
this.webhookService.getActiveWebhooks().then(async webhooks => {
webhooks = webhooks.filter(x => x.userId === note.userId && x.on.includes('reaction'));
if (webhooks.length === 0) return;
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
const userObj = await this.userEntityService.pack(user.id, null, { schema: 'UserLite' });
const payload: UserWebhookPayload<'reaction'> = {
note: noteObj,
reaction: {
id: record.id,
user: userObj,
reaction: reaction,
},
};
for (const webhook of webhooks) {
this.queueService.userWebhookDeliver(webhook, 'reaction', payload);
}
});
}
//#region 配信

View file

@ -18,7 +18,6 @@ import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { bindThis } from '@/decorators.js';
import { ApResolverService } from './activitypub/ApResolverService.js';
@Injectable()
export class RemoteUserResolveService {
@ -36,7 +35,6 @@ export class RemoteUserResolveService {
private remoteLoggerService: RemoteLoggerService,
private apDbResolverService: ApDbResolverService,
private apPersonService: ApPersonService,
private apResolverService: ApResolverService,
) {
this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user');
}
@ -93,7 +91,7 @@ export class RemoteUserResolveService {
}
this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
return await this.apPersonService.createPerson(self.href, this.apResolverService.createResolver());
return await this.apPersonService.createPerson(self.href);
}
// ユーザー情報が古い場合は、WebFingerからやりなおして返す

View file

@ -478,7 +478,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
return ids.length > 0
? await this.usersRepository.findBy({
id: In(ids),
isDeleted: false,
})
: [];
}

View file

@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets, In } from 'typeorm';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import { type Config, FulltextSearchProvider } from '@/config.js';
import { bindThis } from '@/decorators.js';

View file

@ -15,14 +15,6 @@ import { QueueService } from '@/core/QueueService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'reaction' ? {
reaction: {
id: string,
user: Packed<'UserLite'>,
reaction: string,
}
note: Packed<'Note'>,
} :
T extends 'note' | 'reply' | 'renote' | 'mention' ? {
note: Packed<'Note'>,
} :

View file

@ -7,21 +7,19 @@ import { Inject, Injectable } from '@nestjs/common';
import FFmpeg from 'fluent-ffmpeg';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { __YUME_PRIVATE_ImageProcessingService } from '@/core/ImageProcessingService.js';
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import type { IImage } from '@/core/ImageProcessingService.js';
import { createTempDir } from '@/misc/create-temp.js';
import { bindThis } from '@/decorators.js';
import { appendQuery, query } from '@/misc/prelude/url.js';
@Injectable()
// Prevent accidental import by upstream merge
// eslint-disable-next-line
export class __YUME_PRIVATE_VideoProcessingService {
export class VideoProcessingService {
constructor(
@Inject(DI.config)
private config: Config,
private imageProcessingService: __YUME_PRIVATE_ImageProcessingService,
private imageProcessingService: ImageProcessingService,
) {
}

View file

@ -83,11 +83,7 @@ export class WebAuthnService {
}
@bindThis
public async verifyRegistration(
userId: MiUser['id'],
response: RegistrationResponseJSON,
twoFactorOnly: boolean = false,
): Promise<{
public async verifyRegistration(userId: MiUser['id'], response: RegistrationResponseJSON): Promise<{
credentialID: string;
credentialPublicKey: Uint8Array;
attestationObject: Uint8Array;
@ -115,7 +111,7 @@ export class WebAuthnService {
expectedChallenge: challenge,
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
requireUserVerification: !twoFactorOnly,
requireUserVerification: true,
});
} catch (error) {
console.error(error);
@ -247,12 +243,8 @@ export class WebAuthnService {
}
@bindThis
public async verifyAuthentication(
userId: MiUser['id'],
response: AuthenticationResponseJSON,
twoFactorOnly: boolean = false,
): Promise<boolean> {
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> {
const challenge = await this.redisClient.getdel(`webauthn:challenge:${userId}`);
if (!challenge) {
throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found');
@ -306,7 +298,7 @@ export class WebAuthnService {
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
},
requireUserVerification: !twoFactorOnly,
requireUserVerification: true,
});
} catch (error) {
console.error(error);

View file

@ -252,20 +252,6 @@ function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailed
};
}
function generateDummyReactionPayload(note_override?: Partial<MiNote>): UserWebhookPayload<'reaction'> {
const dummyNote = generateDummyNote(note_override);
const dummyReaction = {
id: 'dummy-reaction-1',
user: toPackedUserLite(generateDummyUser()),
reaction: 'test_reaction',
};
return {
note: toPackedNote(dummyNote),
reaction: dummyReaction,
};
}
const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({
id: 'dummy-user-2',
@ -292,10 +278,6 @@ const dummyUser3 = generateDummyUser({
notesCount: 15900,
});
function wrapBodyEnum<T extends string, U>(tag: T, body: U): { [K in T]: U } {
return { [tag]: body } as { [K in T]: U };
}
@Injectable()
export class WebhookTestService {
public static NoSuchWebhookError = class extends Error {

View file

@ -17,7 +17,6 @@ import { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { getApId } from './type.js';
import { ApPersonService } from './models/ApPersonService.js';
import type { IObject } from './type.js';
import { toASCII } from 'node:punycode';
export type UriParseResult = {
/** wether the URI was generated by us */

View file

@ -5,7 +5,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import * as prom from 'prom-client';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
@ -29,7 +28,8 @@ import { bindThis } from '@/decorators.js';
import type { MiRemoteUser } from '@/models/User.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { AbuseReportService } from '@/core/AbuseReportService.js';
import { getApHrefNullable, getApId, getApIds, getApType, isActor, isCollection, isCollectionOrOrderedCollection, isPost, isTombstone, validActor, validPost, yumeDowncastAccept, yumeDowncastAdd, yumeDowncastAnnounce, yumeDowncastBlock, yumeDowncastCreate, yumeDowncastDelete, yumeDowncastFlag, yumeDowncastFollow, yumeDowncastLike, yumeDowncastMove, yumeDowncastReject, yumeDowncastRemove, yumeDowncastUndo, yumeDowncastUpdate } from './type.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js';
import { ApDbResolverService } from './ApDbResolverService.js';
@ -39,14 +39,6 @@ import { ApPersonService } from './models/ApPersonService.js';
import { ApQuestionService } from './models/ApQuestionService.js';
import type { Resolver } from './ApResolverService.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
import { metricCounter } from '@/server/api/MetricsService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
const mInboxReceived = metricCounter({
name: 'misskey_ap_inbox_received_total',
help: 'Total number of activities received by AP inbox',
labelNames: ['host', 'type'],
});
@Injectable()
export class ApInboxService {
@ -151,93 +143,38 @@ export class ApInboxService {
public async performOneActivity(actor: MiRemoteUser, activity: IObject, resolver?: Resolver): Promise<string | void> {
if (actor.isSuspended) return;
const create = yumeDowncastCreate(activity);
if (create) {
mInboxReceived?.inc({ host: actor.host, type: 'create' });
return await this.create(actor, create);
}
const update = yumeDowncastUpdate(activity);
if (update) {
mInboxReceived?.inc({ host: actor.host, type: 'update' });
return await this.update(actor, update);
}
const del = yumeDowncastDelete(activity);
if (del) {
mInboxReceived?.inc({ host: actor.host, type: 'delete' });
return await this.delete(actor, del);
}
const follow = yumeDowncastFollow(activity);
if (follow) {
mInboxReceived?.inc({ host: actor.host, type: 'follow' });
return await this.follow(actor, follow);
}
const accept = yumeDowncastAccept(activity);
if (accept) {
mInboxReceived?.inc({ host: actor.host, type: 'accept' });
return await this.accept(actor, accept);
}
const reject = yumeDowncastReject(activity);
if (reject) {
mInboxReceived?.inc({ host: actor.host, type: 'reject' });
return await this.reject(actor, reject);
}
const add = yumeDowncastAdd(activity);
if (add) {
mInboxReceived?.inc({ host: actor.host, type: 'add' });
return await this.add(actor, add);
}
const remove = yumeDowncastRemove(activity);
if (remove) {
mInboxReceived?.inc({ host: actor.host, type: 'remove' });
return await this.remove(actor, remove);
}
const announce = yumeDowncastAnnounce(activity);
if (announce) {
mInboxReceived?.inc({ host: actor.host, type: 'announce' });
return await this.announce(actor, announce);
}
const like = yumeDowncastLike(activity);
if (like) {
mInboxReceived?.inc({ host: actor.host, type: 'like' });
return await this.like(actor, like);
}
const move = yumeDowncastMove(activity);
if (move) {
mInboxReceived?.inc({ host: actor.host, type: 'move' });
return await this.move(actor, move);
}
const undo = yumeDowncastUndo(activity);
if (undo) {
mInboxReceived?.inc({ host: actor.host, type: 'undo' });
return await this.undo(actor, undo);
}
const block = yumeDowncastBlock(activity);
if (block) {
mInboxReceived?.inc({ host: actor.host, type: 'block' });
return await this.block(actor, block);
}
const flag = yumeDowncastFlag(activity);
if (flag) {
mInboxReceived?.inc({ host: actor.host, type: 'flag' });
return await this.flag(actor, flag);
}
mInboxReceived?.inc({ host: actor.host, type: 'unknown' });
if (isCreate(activity)) {
return await this.create(actor, activity, resolver);
} else if (isDelete(activity)) {
return await this.delete(actor, activity);
} else if (isUpdate(activity)) {
return await this.update(actor, activity, resolver);
} else if (isFollow(activity)) {
return await this.follow(actor, activity);
} else if (isAccept(activity)) {
return await this.accept(actor, activity, resolver);
} else if (isReject(activity)) {
return await this.reject(actor, activity, resolver);
} else if (isAdd(activity)) {
return await this.add(actor, activity, resolver);
} else if (isRemove(activity)) {
return await this.remove(actor, activity, resolver);
} else if (isAnnounce(activity)) {
return await this.announce(actor, activity, resolver);
} else if (isLike(activity)) {
return await this.like(actor, activity);
} else if (isUndo(activity)) {
return await this.undo(actor, activity, resolver);
} else if (isBlock(activity)) {
return await this.block(actor, activity);
} else if (isFlag(activity)) {
return await this.flag(actor, activity);
} else if (isMove(activity)) {
return await this.move(actor, activity, resolver);
} else {
return `unrecognized activity type: ${activity.type}`;
}
}
@bindThis
private async follow(actor: MiRemoteUser, activity: IFollow): Promise<string> {
@ -291,8 +228,7 @@ export class ApInboxService {
throw err;
});
const follow = yumeDowncastFollow(object);
if (follow) return await this.acceptFollow(actor, follow);
if (isFollow(object)) return await this.acceptFollow(actor, object);
return `skip: Unknown Accept type: ${getApType(object)}`;
}
@ -554,9 +490,9 @@ export class ApInboxService {
formerType = 'Note';
}
if (validPost?.includes(formerType)) {
if (validPost.includes(formerType)) {
return await this.deleteNote(actor, uri);
} else if (validActor?.includes(formerType)) {
} else if (validActor.includes(formerType)) {
return await this.deleteActor(actor, uri);
} else {
return `Unknown type ${formerType}`;
@ -575,10 +511,11 @@ export class ApInboxService {
return 'skip: already deleted or actor not found';
}
const job = await this.queueService.createDeleteAccountJob(actor);
this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id });
return 'ok';
return `ok: queued ${job.name} ${job.id}`;
}
@bindThis
@ -645,8 +582,7 @@ export class ApInboxService {
throw e;
});
const follow = yumeDowncastFollow(object);
if (follow) return await this.rejectFollow(actor, follow);
if (isFollow(object)) return await this.rejectFollow(actor, object);
return `skip: Unknown Reject type: ${getApType(object)}`;
}
@ -714,20 +650,11 @@ export class ApInboxService {
});
// don't queue because the sender may attempt again when timeout
const follow = yumeDowncastFollow(object);
if (follow) return await this.undoFollow(actor, follow);
const block = yumeDowncastBlock(object);
if (block) return await this.undoBlock(actor, block);
const like = yumeDowncastLike(object);
if (like) return await this.undoLike(actor, like);
const announce = yumeDowncastAnnounce(object);
if (announce) return await this.undoAnnounce(actor, announce);
const accept = yumeDowncastAccept(object);
if (accept) return await this.undoAccept(actor, accept);
if (isFollow(object)) return await this.undoFollow(actor, object);
if (isBlock(object)) return await this.undoBlock(actor, object);
if (isLike(object)) return await this.undoLike(actor, object);
if (isAnnounce(object)) return await this.undoAnnounce(actor, object);
if (isAccept(object)) return await this.undoAccept(actor, object);
return `skip: unknown object type ${getApType(object)}`;
}

View file

@ -31,7 +31,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { JsonLdService } from './JsonLdService.js';
import { ApMfmService } from './ApMfmService.js';
import { CONTEXT } from './misc/contexts.js';
import { markOutgoing, type IAccept, type IActivity, type IAdd, type IAnnounce, type IApDocument, type IApEmoji, type IApHashtag, type IApImage, type IApMention, type IBlock, type ICreate, type IDelete, type IFlag, type IFollow, type IKey, type ILike, type IMove, type IObject, type IPost, type IQuestion, type IReject, type IRemove, type ITombstone, type IUndo, type IUpdate } from './type.js';
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
@Injectable()
export class ApRendererService {
@ -71,21 +71,21 @@ export class ApRendererService {
@bindThis
public renderAccept(object: string | IObject, user: { id: MiUser['id']; host: null }): IAccept {
return markOutgoing({
return {
type: 'Accept',
actor: this.userEntityService.genLocalUserUri(user.id),
object,
}, undefined);
};
}
@bindThis
public renderAdd(user: MiLocalUser, target: string | IObject | undefined, object: string | IObject): IAdd {
return markOutgoing({
return {
type: 'Add',
actor: this.userEntityService.genLocalUserUri(user.id),
target,
object,
}, undefined);
};
}
@bindThis
@ -108,7 +108,7 @@ export class ApRendererService {
throw new Error('renderAnnounce: cannot render non-public note');
}
return markOutgoing({
return {
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Announce',
@ -116,7 +116,7 @@ export class ApRendererService {
to,
cc,
object,
}, undefined);
};
}
/**
@ -130,23 +130,23 @@ export class ApRendererService {
throw new Error('renderBlock: missing blockee uri');
}
return markOutgoing({
return {
type: 'Block',
id: `${this.config.url}/blocks/${block.id}`,
actor: this.userEntityService.genLocalUserUri(block.blockerId),
object: block.blockee.uri,
}, undefined);
};
}
@bindThis
public renderCreate(object: IObject, note: MiNote): ICreate {
const activity: ICreate = markOutgoing({
const activity: ICreate = {
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Create',
published: this.idService.parse(note.id).date.toISOString(),
object,
}, undefined);
};
if (object.to) activity.to = object.to;
if (object.cc) activity.cc = object.cc;
@ -156,28 +156,28 @@ export class ApRendererService {
@bindThis
public renderDelete(object: IObject | string, user: { id: MiUser['id']; host: null }): IDelete {
return markOutgoing({
return {
type: 'Delete',
actor: this.userEntityService.genLocalUserUri(user.id),
object,
published: new Date().toISOString(),
}, undefined);
};
}
@bindThis
public renderDocument(file: MiDriveFile): IApDocument {
return markOutgoing({
return {
type: 'Document',
mediaType: file.webpublicType ?? file.type,
url: this.driveFileEntityService.getPublicUrl(file),
name: file.comment,
sensitive: file.isSensitive,
}, undefined);
};
}
@bindThis
public renderEmoji(emoji: MiEmoji): IApEmoji {
return markOutgoing( {
return {
id: `${this.config.url}/emojis/${emoji.name}`,
type: 'Emoji',
name: `:${emoji.name}:`,
@ -191,28 +191,28 @@ export class ApRendererService {
_misskey_license: {
freeText: emoji.license,
},
}, undefined);
};
}
// to anonymise reporters, the reporting actor must be a system user
@bindThis
public renderFlag(user: MiLocalUser, object: IObject | string, content: string): IFlag {
return markOutgoing({
return {
type: 'Flag',
actor: this.userEntityService.genLocalUserUri(user.id),
content,
object,
}, undefined);
};
}
@bindThis
public renderFollowRelay(relay: MiRelay, relayActor: MiLocalUser): IFollow {
return markOutgoing({
return {
id: `${this.config.url}/activities/follow-relay/${relay.id}`,
type: 'Follow',
actor: this.userEntityService.genLocalUserUri(relayActor.id),
object: 'https://www.w3.org/ns/activitystreams#Public',
}, undefined);
};
}
/**
@ -231,31 +231,31 @@ export class ApRendererService {
followee: MiPartialLocalUser | MiPartialRemoteUser,
requestId?: string,
): IFollow {
return markOutgoing({
return {
id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow',
actor: this.userEntityService.getUserUri(follower),
object: this.userEntityService.getUserUri(followee),
}, undefined);
};
}
@bindThis
public renderHashtag(tag: string): IApHashtag {
return markOutgoing({
return {
type: 'Hashtag',
href: `${this.config.url}/tags/${encodeURIComponent(tag)}`,
name: `#${tag}`,
}, undefined);
};
}
@bindThis
public renderImage(file: MiDriveFile): IApImage {
return markOutgoing({
return {
type: 'Image',
url: this.driveFileEntityService.getPublicUrl(file),
sensitive: file.isSensitive,
name: file.comment,
}, undefined);
};
}
@bindThis
@ -292,7 +292,7 @@ export class ApRendererService {
@bindThis
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
return markOutgoing({
return {
id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
type: 'Key',
owner: this.userEntityService.genLocalUserUri(user.id),
@ -300,21 +300,21 @@ export class ApRendererService {
type: 'spki',
format: 'pem',
}),
}, undefined);
};
}
@bindThis
public async renderLike(noteReaction: MiNoteReaction, note: { uri: string | null }): Promise<ILike> {
const reaction = noteReaction.reaction;
const object: ILike = markOutgoing({
const object: ILike = {
type: 'Like',
id: `${this.config.url}/likes/${noteReaction.id}`,
actor: `${this.config.url}/users/${noteReaction.userId}`,
object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`,
content: reaction,
_misskey_reaction: reaction,
}, undefined);
};
if (reaction.startsWith(':')) {
const name = reaction.replaceAll(':', '');
@ -328,11 +328,11 @@ export class ApRendererService {
@bindThis
public renderMention(mention: MiPartialLocalUser | MiPartialRemoteUser): IApMention {
return markOutgoing({
return {
type: 'Mention',
href: this.userEntityService.getUserUri(mention),
name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as MiLocalUser).username}`,
}, undefined);
};
}
@bindThis
@ -342,13 +342,13 @@ export class ApRendererService {
): IMove {
const actor = this.userEntityService.getUserUri(src);
const target = this.userEntityService.getUserUri(dst);
return markOutgoing({
return {
id: `${this.config.url}/moves/${src.id}/${dst.id}`,
actor,
type: 'Move',
object: actor,
target,
}, undefined);
};
}
@bindThis
@ -462,7 +462,7 @@ export class ApRendererService {
})),
} as const : {};
return markOutgoing({
return {
id: `${this.config.url}/notes/${note.id}`,
type: 'Note',
attributedTo,
@ -485,7 +485,7 @@ export class ApRendererService {
sensitive: note.cw != null || files.some(file => file.isSensitive),
tag,
...asPoll,
}, undefined);
};
}
@bindThis
@ -499,11 +499,28 @@ export class ApRendererService {
this.userProfilesRepository.findOneByOrFail({ userId: user.id }),
]);
const tryRewriteUrl = (maybeUrl: string) => {
const urlSafeRegex = /^(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/;
try {
const match = maybeUrl.match(urlSafeRegex);
if (!match) {
return maybeUrl;
}
const urlPart = match[0];
const urlPartParsed = new URL(urlPart);
const restPart = maybeUrl.slice(match[0].length);
return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
} catch (e) {
return maybeUrl;
}
};
const attachment = profile.fields.map(field => ({
type: 'PropertyValue',
name: field.name,
value: (field.value.startsWith('http://') || field.value.startsWith('https://'))
? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
? tryRewriteUrl(field.value)
: field.value,
}));
@ -569,7 +586,7 @@ export class ApRendererService {
@bindThis
public renderQuestion(user: { id: MiUser['id'] }, note: MiNote, poll: MiPoll): IQuestion {
return markOutgoing({
return {
type: 'Question',
id: `${this.config.url}/questions/${note.id}`,
actor: this.userEntityService.genLocalUserUri(user.id),
@ -582,78 +599,78 @@ export class ApRendererService {
totalItems: poll.votes[i],
},
})),
}, 'question');
};
}
@bindThis
public renderReject(object: string | IObject, user: { id: MiUser['id'] }): IReject {
return markOutgoing({
return {
type: 'Reject',
actor: this.userEntityService.genLocalUserUri(user.id),
object,
}, undefined);
};
}
@bindThis
public renderRemove(user: { id: MiUser['id'] }, target: string | IObject | undefined, object: string | IObject): IRemove {
return markOutgoing({
return {
type: 'Remove',
actor: this.userEntityService.genLocalUserUri(user.id),
target,
object,
}, undefined);
};
}
@bindThis
public renderTombstone(id: string): ITombstone {
return markOutgoing({
return {
id,
type: 'Tombstone',
}, undefined);
};
}
@bindThis
public renderUndo(object: string | IObject, user: { id: MiUser['id'] }): IUndo {
const id = typeof object !== 'string' && typeof object.id === 'string' && this.utilityService.isUriLocal(object.id) ? `${object.id}/undo` : undefined;
return markOutgoing({
return {
type: 'Undo',
...(id ? { id } : {}),
actor: this.userEntityService.genLocalUserUri(user.id),
object,
published: new Date().toISOString(),
}, undefined);
};
}
@bindThis
public renderUpdate(object: string | IObject, user: { id: MiUser['id'] }): IUpdate {
return markOutgoing( {
return {
id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
actor: this.userEntityService.genLocalUserUri(user.id),
type: 'Update',
to: ['https://www.w3.org/ns/activitystreams#Public'],
object,
published: new Date().toISOString(),
}, undefined);
};
}
@bindThis
public renderVote(user: { id: MiUser['id'] }, vote: MiPollVote, note: MiNote, poll: MiPoll, pollOwner: MiRemoteUser): ICreate {
return markOutgoing({
return {
id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`,
actor: this.userEntityService.genLocalUserUri(user.id),
type: 'Create',
to: [pollOwner.uri],
published: new Date().toISOString(),
object: markOutgoing({
object: {
id: `${this.config.url}/users/${user.id}#votes/${vote.id}`,
type: 'Note',
attributedTo: this.userEntityService.genLocalUserUri(user.id),
to: [pollOwner.uri],
inReplyTo: note.uri,
name: poll.choices[vote.choice],
}, undefined),
}, undefined);
},
};
}
@bindThis

View file

@ -19,7 +19,6 @@ import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { assertActivityMatchesUrls, FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from './type.js';
import { ActorKeySignerService } from '../ActorKeySignerService.js';
type Request = {
url: string;
@ -40,8 +39,7 @@ type PrivateKey = {
};
export class ApRequestCreator {
static async createSignedPost(signer: ActorKeySignerService, args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Promise<Signed> {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);
const digestHeader = args.digest ?? this.createDigest(args.body);
@ -56,7 +54,7 @@ export class ApRequestCreator {
}, args.additionalHeaders),
};
const result = await this.#signToRequest(signer, request, args.key, ['(request-target)', 'date', 'host', 'digest']);
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']);
return {
request,
@ -70,7 +68,7 @@ export class ApRequestCreator {
return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
}
static async createSignedGet(signer: ActorKeySignerService, args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Promise<Signed> {
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);
const request: Request = {
@ -83,7 +81,7 @@ export class ApRequestCreator {
}, args.additionalHeaders),
};
const result = await this.#signToRequest(signer, request, args.key, ['(request-target)', 'date', 'host', 'accept']);
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
return {
request,
@ -93,9 +91,9 @@ export class ApRequestCreator {
};
}
static async #signToRequest(signer: ActorKeySignerService, request: Request, key: PrivateKey, includeHeaders: string[]): Promise<Signed> {
static #signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed {
const signingString = this.#genSigningString(request, includeHeaders);
const signature = await signer.sign(key.keyId, key.privateKeyPem, signingString);
const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64');
const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`;
request.headers = this.#objectAssignWithLcKey(request.headers, {
@ -151,7 +149,6 @@ export class ApRequestService {
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
private utilityService: UtilityService,
private actorKeySignerService: ActorKeySignerService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
@ -163,7 +160,7 @@ export class ApRequestService {
const keypair = await this.userKeypairService.getUserKeypair(user.id);
const req = await ApRequestCreator.createSignedPost(this.actorKeySignerService, {
const req = ApRequestCreator.createSignedPost({
key: {
privateKeyPem: keypair.privateKey,
keyId: `${this.config.url}/users/${user.id}#main-key`,
@ -192,7 +189,7 @@ export class ApRequestService {
const _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id);
const req = await ApRequestCreator.createSignedGet(this.actorKeySignerService, {
const req = ApRequestCreator.createSignedGet({
key: {
privateKeyPem: keypair.privateKey,
keyId: `${this.config.url}/users/${user.id}#main-key`,

View file

@ -5,7 +5,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm';
import * as Bull from 'bullmq';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
import type { Config } from '@/config.js';
@ -17,13 +16,12 @@ import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { SystemAccountService } from '@/core/SystemAccountService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { isCollectionOrOrderedCollection, yumeNormalizeObject } from './type.js';
import { isCollectionOrOrderedCollection } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
import type { IObject, ICollection, IOrderedCollection, IUnsanitizedObject } from './type.js';
import { yumeAssertAcceptableURL } from './misc/validator.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
export class Resolver {
private history: Set<string>;
@ -65,7 +63,7 @@ export class Resolver {
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
const collection = typeof value === 'string'
? await this.resolve(value)
: yumeNormalizeObject(value);
: value;
if (isCollectionOrOrderedCollection(collection)) {
return collection;
@ -75,13 +73,11 @@ export class Resolver {
}
@bindThis
private async resolveNotNormalized(value: string | IObject, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IUnsanitizedObject> {
public async resolve(value: string | IObject, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
if (typeof value !== 'string') {
return value;
}
yumeAssertAcceptableURL(value);
if (value.includes('#')) {
// URLs with fragment parts cannot be resolved correctly because
// the fragment part does not get transmitted over HTTP(S).
@ -94,7 +90,7 @@ export class Resolver {
}
if (this.history.size > this.recursionLimit) {
throw new Bull.UnrecoverableError(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
}
this.history.add(value);
@ -113,8 +109,8 @@ export class Resolver {
}
const object = (this.user
? await this.apRequestService.signedGet(value, this.user, allowSoftfail) as IUnsanitizedObject
: await this.httpRequestService.getActivityJson(value, undefined, allowSoftfail)) as IUnsanitizedObject;
? await this.apRequestService.signedGet(value, this.user, allowSoftfail) as IObject
: await this.httpRequestService.getActivityJson(value, undefined, allowSoftfail)) as IObject;
if (
Array.isArray(object['@context']) ?
@ -127,18 +123,6 @@ export class Resolver {
return object;
}
@bindThis
public async resolve(value: string | IObject, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
try {
const object = await this.resolveNotNormalized(value, allowSoftfail);
return yumeNormalizeObject(object);
} catch (e) {
this.logger.error(`Failed to resolve ${value}: ${e}`);
throw e;
}
}
@bindThis
private resolveLocal(url: string): Promise<IObject> {
const parsed = this.apDbResolverService.parseUri(url);

View file

@ -11,7 +11,6 @@ import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
import type { JsonLdDocument } from 'jsonld';
import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
import { ActorKeySignerService } from '../ActorKeySignerService.js';
// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
@ -22,7 +21,6 @@ class JsonLd {
constructor(
private httpRequestService: HttpRequestService,
private actorKeySignerService: ActorKeySignerService,
) {
}
@ -47,13 +45,17 @@ class JsonLd {
const toBeSigned = await this.createVerifyData(data, options);
const signature = await this.actorKeySignerService.sign(creator, privateKey, toBeSigned);
const signer = crypto.createSign('sha256');
signer.update(toBeSigned);
signer.end();
const signature = signer.sign(privateKey);
return {
...data,
signature: {
...options,
signatureValue: signature,
signatureValue: signature.toString('base64'),
},
};
}
@ -167,12 +169,11 @@ class JsonLd {
export class JsonLdService {
constructor(
private httpRequestService: HttpRequestService,
private actorKeySignerService: ActorKeySignerService,
) {
}
@bindThis
public use(): JsonLd {
return new JsonLd(this.httpRequestService, this.actorKeySignerService);
return new JsonLd(this.httpRequestService);
}
}

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