Compare commits

..

156 Commits

Author SHA1 Message Date
Take-John
bf818a6656 feature(mahjong): 搶槓/ドラ以外の麻雀の役を実装 (#14346)
* ビルドによる自動的なソース更新

* 麻雀関連のキーバリューペアを追加

* 役の定義をまとめてエクスポート

* タイポ修正

* Revert "麻雀関連のキーバリューペアを追加"

This reverts commit c349cdf70c.

* misskey-jsのビルドによる自動更新

* 型エラーに対処

* riichiがtrueの場合に門前であるかを確認

* EnvForCalcYakuのhouseプロパティを廃止

* 風牌の役の共通部分をクラスで定義

* タイポ修正

* 役牌をクラスで共通化

* 一盃口と二盃口のテストを通す

* 一盃口・二盃口判定関数の調整

* 一気通貫の判定にチーによる順子も考慮する

* 混全帯幺九の実装

* 純全帯幺九の実装

* 七対子の実装とテストの修正

* tsumoTileまたはronTileを必須に

* 待ちを確認して平和の判定を可能に

* 三暗刻と四暗刻、四暗刻単騎の実装

* 四暗刻であるために通常の役を判定できない牌姿のテストを修正

* 混老頭と清老頭を実装

* 三槓子と四槓子を実装

* 平和の実装とテストを修正

* 小三元のテストを修正

* 国士無双に対子の確認を追加

* 国士無双十三面待ちを実装し、テストを修正

* 一部の役の七対子形を認め、テストを追加

* 手牌の数を確認

* 役の定義をカプセル化して型エラーの対処

* ツモ・ロンの判定を修正

* calcYakusの引数のhandTilesを修正

* calcYakusに渡す風をseatWindに修正

* 嶺上開花の実装

* 海底摸月の実装

* FourMentsuOneJyantouWithWait型の作成

* 河底撈魚の実装

* ダブル立直の実装

* 天和・地和の実装

* エンジンのテストを作成

* エンジンによる地和のテストを追加

* 嶺上開花のテスト

* ライセンスの記述を追加

* ダブル立直一発ツモのテスト

* ダブル立直海底ツモのテスト

* ダブル立直河底のテスト

* 役満も処理できるように

* 点数のテスト

* 打牌時にrinshanFlags[house]をfalseに

* 七対子形の字一色を認める

* typo
2024-08-15 12:29:31 +09:00
syuilo
f32b11ba12 Merge branch 'develop' into mahjong 2024-07-28 15:18:08 +09:00
anatawa12
865b3039cc fix: deck uiの通知音が重なる問題 (#14029)
* fix: deck uiの通知音が重なる

* docs: Fix: deck uiの通知音が重なる問題

* unexport internal function

* fix

Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>

* chore: improve condition

* docs: move js dco comment

---------

Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-07-28 14:44:15 +09:00
github-actions[bot]
efa80f9ad4 Bump version to 2024.7.0-beta.3 2024-07-28 02:23:46 +00:00
Sayamame-beans
61f4a03e6c Fix(frontend): 下書き/削除して編集で保持されない項目があった問題を修正 (#14285)
* chore(frontend): reorder assignments

* fix(frontend): visibleUserIds is not kept when deleteAndEdit

* fix(frontend): quoteId is not kept on draft

* fix(frontend): reactionAcceptance is not kept for draft/deleteAndEdit

* docs(changelog): update changelog
2024-07-28 11:19:32 +09:00
syuilo
085b3abf26 update deps (#14312) 2024-07-28 11:14:31 +09:00
syuilo
5df85b8be1 enhance(frontend): add withCloseButton option for MkModalWindow 2024-07-28 10:39:31 +09:00
syuilo
02ecd1b371 refactor 2024-07-28 10:36:12 +09:00
かっこかり
22c4e9d7ec fix(frontend): modalが正しく閉じられていないのを修正 (#14307)
* fix(frontend): modalが正しく閉じられていないのを修正

* Update packages/frontend/src/components/MkSystemWebhookEditor.vue

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-07-27 18:09:57 +09:00
かっこかり
46d96c7412 fix(build): autogen生成時にbackendを2度buildしているのを修正 (#14309)
* fix(build): autogen生成時にbackendを2度buildしているのを修正

* fix

* fix
2024-07-27 18:09:15 +09:00
zyoshoka
0d76842abe docs: format CONTRIBUTING.md (#14302)
* fix: correct typos

* chore: convert indentation to tabs

* fix: missing lang

* chore: trim unnecessary whitespaces and newlines

* chore: use local path

* chore: use GFM alerts

* fix: missing use GFM alerts
2024-07-26 10:16:27 +09:00
github-actions[bot]
908d3ecb5c Bump version to 2024.7.0-beta.2 2024-07-25 12:02:12 +00:00
Kisaragi
8959ff89d0 chore: reflect actual policy about Committers' rights (#14267)
* Update CONTRIBUTING.md

* member -> commiter

* apply suggestions

Co-authored-by: Marie <robloxfilmcam@gmail.com>

* Update CONTRIBUTING.md

---------

Co-authored-by: Marie <robloxfilmcam@gmail.com>
2024-07-25 17:12:06 +09:00
Take-John
6c9f6e8057 fix(mahjong): 麻雀をプレイできない問題を修正 (#14268)
* ビルドによる自動的なソース更新

* 麻雀関連のキーバリューペアを追加

* 役の定義をまとめてエクスポート

* タイポ修正

* Revert "麻雀関連のキーバリューペアを追加"

This reverts commit c349cdf70c.

* misskey-jsのビルドによる自動更新
2024-07-25 17:09:07 +09:00
かっこかり
ee2f0f3a21 fix(frontend): いくつかのnumber inputに最小値を設定 (#14284) 2024-07-25 17:03:55 +09:00
かっこかり
ed6dc84c5f fix(frontend): リアクションしたユーザー一覧のユーザー名がはみ出る問題を修正 (#14294)
* pnpm dev で絵文字が表示されない問題を解決

(cherry picked from commit 22fcafbf55830922efe75d129f48b4d8c11724e6)

* リアクションしたユーザー一覧のユーザーネームがはみ出る問題を解決

(cherry picked from commit 46458b190e2b4ccfc8b50b6857ee9a5a6fd09fe9)

* Update Changelog

---------

Co-authored-by: 6wFh3kVo <yukikum57@gmail.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-07-25 17:03:00 +09:00
zyoshoka
7c67d3a5aa fix(frontend): emoji picker not opening on /share page (#14295)
* fix(frontend): emoji picker not opening on `/share` page

* Update CHANGELOG.md
2024-07-25 16:44:38 +09:00
Sayamame-beans
3d8eda14a2 [Re] refactor(misskey-js): 警告をすべて解決 (#14277)
* chore(misskey-js): Unchanged files with check annotationsで紛らわしい部分の警告を抑制 ロジック面は後で直す

* dummy change to see if the feature do not report them (to be reverted after the check)

* refactor: 型合わせ

* refactor: fix warnings from c22dd6358b

* lint

* 型合わせ

* キャスト

* pnpm build-misskey-js-with-types

* Revert "dummy change to see if the feature do not report them (to be reverted after the check)"

This reverts commit 67072e3ca6.

* eliminate reversiGame any

* move reversiGame types

* lint

* Update packages/misskey-js/src/streaming.ts

Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>

* Update acct.ts

* run api extractor

* re-run api extractor

---------

Co-authored-by: Kisaragi Marine <kisaragi.effective@gmail.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2024-07-25 16:40:14 +09:00
zyoshoka
befa8e4a7f fix(backend): avoid caching remote user's HTL when receiving Note (#13772)
* fix(backend): avoid caching remote user's HTL when receiving Note

* test(backend): add test for FFT

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-07-25 16:37:46 +09:00
zyoshoka
fc71bcc98e fix(backend): avoid notifying to remote users on local (#13774)
* fix(backend): avoid notifying to remote users on local

* Update CHANGELOG.md

* refactor: check before calling method

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-07-25 16:32:07 +09:00
かっこかり
aa3ea2b57a fix(frontend): 初期化時とroute変更時でkeyの決定方法が違うのを修正 (#14283) 2024-07-25 16:30:29 +09:00
syuilo
32651aba67 Update about-misskey.vue 2024-07-23 18:39:59 +09:00
syuilo
337b42bcb1 revert 5f88d56d96
バグがある(かつすぐに修正できそうにない) & まだレビュー途中で意図せずマージされたため
2024-07-20 21:33:20 +09:00
zyoshoka
efb04293bb docs(misskey-js): fix broken i-want-you image link in README.md (#14265) 2024-07-19 16:47:12 +09:00
かっこかり
56a43dc01d fix(frontend): blurhashが無い場合に何も出力されないのを修正 (#14250)
* fix(frontend): blurhashが無い場合に何も出力されないのを修正

* Update Changelog

* Update packages/frontend/src/components/MkImgWithBlurhash.vue

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

* attempt to fix test

* Update packages/frontend/src/components/MkImgWithBlurhash.vue

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

* attempt to ignore test

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2024-07-19 10:11:44 +09:00
woxtu
6920f0fa7e Disable ESLint for migration files (#14262) 2024-07-19 10:05:34 +09:00
かっこかり
1f24a8cb5a enhance(frontend): センシティブなメディアを開く際に確認ダイアログを出せるように (#14115)
* enhance(frontend): センシティブなメディアを開く際に確認ダイアログを出せるように

* Update Changelog
2024-07-19 09:57:01 +09:00
taichan
54d0a46378 fix(frontend): 個人宛てダイアログお知らせが即時表示されない問題 (#14260)
* fix(frontend): 個人向けお知らせが即時ダイアログで出ない問題

* Update CHANGELOG
2024-07-19 09:53:49 +09:00
Kisaragi
615e60f25c chore: modernize issue template (#14263) 2024-07-19 09:52:39 +09:00
anatawa12
10ce7bf3c4 kill any from streaming API Implementation (#14251)
* chore: add JsonValue type

* refactor: kill any from Connection.ts

* refactor: fix StreamEventEmitter contains undefined instead of null

* refactor: kill any from channels

* docs(changelog): Fix: Steaming APIが不正なデータを受けた場合の動作が不安定である問題

* fix license header

* fix lints
2024-07-18 20:04:23 +09:00
かっこかり
ec1c392f1e fix(frontend): 子メニューの最大長調整が行われていない問題を修正 (#14003)
* fix(frontend): 子メニューの最大長調整が行われていない問題を修正

* Update Changelog

* fix

* changelog

* Revert "fix"

This reverts commit 39fb326d49.

* Revert "fix(frontend): 子メニューの最大長調整が行われていない問題を修正"

This reverts commit ea58bf7a53.

* use css

* maxHeightをchildから定義するように

* use css min
2024-07-18 15:44:18 +09:00
かっこかり
4f85b6aa91 fix(frontend): Twitchの埋め込みが開けない問題を修正 (#14247)
* fix(frontend): twitchの埋め込みが開けない問題を修正

* Update Changelog

* fix test
2024-07-18 15:41:32 +09:00
anatawa12
ee3b132f05 Merge branch 'master' into develop 2024-07-18 14:25:09 +09:00
anatawa12
cd95a6e9c9 fix: remove unreleased section (#14246) 2024-07-18 14:23:47 +09:00
Kisaragi
e716c201c6 docs: 開発環境のセットアップ手順を詳細にする (#14235)
* docs: mentioning Devcontainer

fix #13753

* revise

* revise 2

* Apply suggestions from code review
per https://github.com/misskey-dev/misskey/pull/14235#discussion_r1680883942

Co-authored-by: anatawa12 <anatawa12@icloud.com>

* 下の方にあったDevcontainerのセクションをマージ

* revise 3

* Update CONTRIBUTING.md

https://github.com/misskey-dev/misskey/pull/14235#discussion_r1680928026

Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>

* mention Meilisearch

* Update CONTRIBUTING.md

---------

Co-authored-by: anatawa12 <anatawa12@icloud.com>
Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
2024-07-18 08:57:02 +09:00
かっこかり
de166a8ed4 fix(backend): リノートミュートがキャッシュが切れるまで効かない問題を修正 (#14242)
* Fix: RenoteMuteがキャッシュが切れるまで効かない問題を修正

(cherry picked from commit e9601029b5)

* update changelog

* 🎨

* remove unused import

* 消したときもキャッシュを飛ばすように

* lint

---------

Co-authored-by: mattyatea <mattyacocacora0@gmail.com>
2024-07-18 08:55:36 +09:00
tamaina
c3a6da19d7 chore: CHANGELOGにジョブキュー設定について追記 (follow-up of #13464) 2024-07-18 08:53:45 +09:00
github-actions[bot]
73a42ea2ee Bump version to 2024.7.0-beta.1 2024-07-17 17:23:58 +00:00
anatawa12
cfdad45092 fix: ソーシャルタイムラインにローカルタイムラインに表示される自分へのリプライが表示されない問題を修正 (#13978)
Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>
2024-07-18 02:06:31 +09:00
tamaina
dd4cb5f44a Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-07-18 02:05:44 +09:00
tamaina
a54d043923 chore: ignore misskey-assets (follow-up of #12818 ) 2024-07-18 02:05:40 +09:00
かっこかり
ec91e18899 fix(frontend): add missing import (follow-up of #12265) 2024-07-18 01:56:02 +09:00
かっこかり
25b65925f7 refactor: misskey-assetsサブモジュールを削除 (#12818)
* (change) misskey-assetsサブモジュールを削除

* なんか残ってた
2024-07-18 01:47:11 +09:00
tamaina
5f88d56d96 perf(federation): Ed25519署名に対応する (#13464)
* 1. ed25519キーペアを発行・Personとして公開鍵を送受信

* validate additionalPublicKeys

* getAuthUserFromApIdはmainを選ぶ

* ✌️

* fix

* signatureAlgorithm

* set publicKeyCache lifetime

* refresh

* httpMessageSignatureAcceptable

* ED25519_SIGNED_ALGORITHM

* ED25519_PUBLIC_KEY_SIGNATURE_ALGORITHM

* remove sign additionalPublicKeys signature requirements

* httpMessageSignaturesSupported

* httpMessageSignaturesImplementationLevel

* httpMessageSignaturesImplementationLevel: '01'

* perf(federation): Use hint for getAuthUserFromApId (#13470)

* Hint for getAuthUserFromApId

* とどのつまりこれでいいのか?

* use @misskey-dev/node-http-message-signatures

* fix

* signedPost, signedGet

* ap-request.tsを復活させる

* remove digest prerender

* fix test?

* fix test

* add httpMessageSignaturesImplementationLevel to FederationInstance

* ManyToOne

* fetchPersonWithRenewal

* exactKey

* ✌️

* use const

* use gen-key-pair fn. from  '@misskey-dev/node-http-message-signatures'

* update node-http-message-signatures

* fix

* @misskey-dev/node-http-message-signatures@0.0.0-alpha.11

* getAuthUserFromApIdでupdatePersonの頻度を増やす

* cacheRaw.date

* use requiredInputs
https://github.com/misskey-dev/misskey/pull/13464#discussion_r1509964359

* update @misskey-dev/node-http-message-signatures

* clean up

* err msg

* fix(backend): fetchInstanceMetadataのLockが永遠に解除されない問題を修正

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>

* fix httpMessageSignaturesImplementationLevel validation

* fix test

* fix

* comment

* comment

* improve test

* fix

* use Promise.all in genRSAAndEd25519KeyPair

* refreshAndprepareEd25519KeyPair

* refreshAndfindKey

* commetn

* refactor public keys add

* digestプリレンダを復活させる

RFC実装時にどうするか考える

* fix, async

* fix

* !== true

* use save

* Deliver update person when new key generated (not tested)
https://github.com/misskey-dev/misskey/pull/13464#issuecomment-1977049061

* 循環参照で落ちるのを解消?

* fix?

* Revert "fix?"

This reverts commit 0082f6f8e8.

* a

* logger

* log

* change logger

* 秘密鍵の変更は、フラグではなく鍵を引き回すようにする

* addAllKnowingSharedInboxRecipe

* nanka meccha kaeta

* delivre

* キャッシュ有効チェックはロック取得前に行う

* @misskey-dev/node-http-message-signatures@0.0.3

* PrivateKeyPem

* getLocalUserPrivateKey

* fix test

* if

* fix ap-request

* update node-http-message-signatures

* fix type error

* update package

* fix type

* update package

* retry no key

* @misskey-dev/node-http-message-signatures@0.0.8

* fix type error

* log keyid

* logger

* db-resolver

* JSON.stringify

* HTTP Signatureがなかったり使えなかったりしそうな場合にLD Signatureを活用するように

* inbox-delayed use actor if no signature

* ユーザーとキーの同一性チェックはhostの一致にする

* log signature parse err

* save array

* とりあえずtryで囲っておく

* fetchPersonWithRenewalでエラーが起きたら古いデータを返す

* use transactionalEntityManager

* fix spdx

* @misskey-dev/node-http-message-signatures@0.0.10

* add comment

* fix

* publicKeyに配列が入ってもいいようにする
https://github.com/misskey-dev/misskey/pull/13950

* define additionalPublicKeys

* fix

* merge fix

* refreshAndprepareEd25519KeyPair → refreshAndPrepareEd25519KeyPair

* remove gen-key-pair.ts

* defaultMaxListeners = 512

* Revert "defaultMaxListeners = 512"

This reverts commit f2c412c180.

* genRSAAndEd25519KeyPairではキーを直列に生成する?

* maxConcurrency: 8

* maxConcurrency: 16

* maxConcurrency: 8

* Revert "genRSAAndEd25519KeyPairではキーを直列に生成する?"

This reverts commit d0aada55c1.

* maxWorkers: '90%'

* Revert "maxWorkers: '90%'"

This reverts commit 9e0a93f110.

* e2e/timelines.tsで個々のテストに対するtimeoutを削除, maxConcurrency: 32

* better error handling of this.userPublickeysRepository.delete

* better comment

* set result to keypairEntityCache

* deliverJobConcurrency: 16, deliverJobPerSec: 1024, inboxJobConcurrency: 4

* inboxJobPerSec: 64

* delete request.headers['host'];

* fix

* // node-fetch will generate this for us. if we keep 'Host', it won't change with redirects!

* move delete host

* modify comment

* modify comment

* fix correct → collect

* refreshAndfindKey → refreshAndFindKey

* modify comment

* modify attachLdSignature

* getApId, InboxProcessorService

* TODO

* [skip ci] add CHANGELOG

---------

Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2024-07-18 01:28:17 +09:00
かっこかり
3331f3972a fix(frontend): 「アニメーション画像を再生しない」がオンのときにバナー画像・サーバー背景画像がアニメーションしないように (#14243)
* fix: stop animating banner and backgrounds when stop showing animated images is enabled

(cherry picked from commit 8fe2596316e9688509745706ea424f0b4bfd4136)

* chore: nest ternary

(cherry picked from commit 2783fe5f5bd7c0647db9f9b6fb5e000e4f411092)

* chore: flip ternary

(cherry picked from commit b9d66f824cff373cc53bfa846a56c16f456a6d5b)

* update changelog

---------

Co-authored-by: Marie <marie@kaifa.ch>
2024-07-18 01:22:24 +09:00
woxtu
6942a920c8 refactor(frontend): Improve typing (#14240)
* Improve typing

* Remove redundant promise

* Refactor

* Update packages/frontend/src/scripts/mfm-function-picker.ts

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

* Update packages/frontend/src/scripts/mfm-function-picker.ts

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
2024-07-18 00:31:52 +09:00
Chocolate Pie
68bcd91d57 chore: Use clipboard API directly (#14227)
* chore: Use clipboard API directly

* fix: Fix lint
2024-07-17 21:52:05 +09:00
かっこかり
8b4933cc48 fix changelog (wrong category) 2024-07-17 19:08:39 +09:00
tamaina
bda1de8a67 use pnpm@9.5.0 2024-07-17 18:19:06 +09:00
syuilo
6b16b85203 Merge branch 'develop' into mahjong 2024-07-08 13:08:57 +09:00
syuilo
4597d5db91 wip 2024-07-02 10:10:18 +09:00
syuilo
9c79f5d135 Merge branch 'develop' into mahjong 2024-07-02 10:10:01 +09:00
かっこかり
fce66b85b6 Merge pull request #13917 from misskey-dev/develop
Release 2024.5.0 (master)
2024-06-01 11:27:03 +09:00
syuilo
78ff90f2cc Merge pull request #13493 from misskey-dev/develop
Release: 2024.3.1
2024-03-02 17:06:46 +09:00
syuilo
7e706ea669 Merge pull request #13447 from misskey-dev/develop
Release: 2024.3.0
2024-03-01 21:17:01 +09:00
im_tan
0e27fa59d4 test: [Mi麻雀] 一部を除く各種役のテスト (#13397)
* fix typo

* add letter-tiles tests

* add ippatsu test

* add tanyao and pinfu test

* fix ippatsu test

* add wind tests

* add iipeko and chitoitsu test

* add sanshoku-doujunn sanshoku-doo ittsu junchan tests

* add toitoi sananko test

* add ryanpeko test

* add honroto sankantsu honitsu chintisu tests

* add shosangen test
2024-02-19 16:51:56 +09:00
syuilo
96c7c85ad0 Merge pull request #13045 from misskey-dev/develop
* perf(drop-and-fusion): remove root Transition component for improve performance

* refactor(drop-and-fusion): some refactors

* clean up

* enhance(drop-and-fusion): some tweaks

* Feat(frontend): リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように (#12984)

* リアクション・ノート内絵文字・/about#emojisで絵文字詳細が見られるように

* update CHANGELOG.md

* fix locale & type errors

* fix locale etc

* fix

* fix type

* lint fixes

* lint fixes(2)

* tweak

* fix(backend): 虚無ノートを投稿できる問題の修正と `api.json` の OpenAPI Specification 3.1.0 への対応 (#12969)

* fix(backend): `text: null`だけのノートは投稿できないように

* add test

* Update CHANGELOG.md

* chore: bump OpenAPI Specification from 3.0.0 to 3.1.0

* chore: テストがすでにコメントで記述されていたのでそっちを使うことにする

* fix test

* fix(backend): prohibit posting whitespace-only notes

* Update CHANGELOG.md

* fix(backend): `renoteId`または`fileIds`(`mediaIds`)または`poll`が`null`でない場合に、`text  が空白文字のみで構成されたリクエストになることを許可して、結果は`text: null`を返すように

* test(backend): 引用renoteで空白文字のみで構成されたtextにするとレスポンスが`text: null`になることをチェックするテストを追加

* fix(frontend): `text`が`null`であって`renoteId`と`replyId`が`null`でないようなノートは引用リノートとして表示するように

* fix(misskey-js): OpenAPI 3.1に対応

* fix(misskey-js): 型生成をOpenAPI Specification 3.1.0に対応

* fix(ci): `validate-api.json`をOpenAPI Specification 3.1.0に対応

* fix(ci): スキーマ書き換えの際のミスを修正

* Revert "fix(frontend): `text`が`null`であって`renoteId`と`replyId`が`null`でないようなノートは引用リノートとして表示するように"

This reverts commit a9ca55343d.

* fix(misskey-js): `build-misskey-js-with-types`時は`api.json`のGETをスキップするように

* Revert "fix(misskey-js): `build-misskey-js-with-types`時は`api.json`のGETをスキップするように"

This reverts commit 865458989f.

* fix(misskey-js): `openapi-parser`で`validate`のかわりに`parse`を用いるように

* Update CHANGELOG.md

* fix type

* enhance(drop-and-fusion): refactor and new mode(wip)

* feat: 枠線をつけるMFMを追加 (#12981)

* Update MkMisskeyFlavoredMarkdown.ts

* Update const.ts

* Update MkMisskeyFlavoredMarkdown.ts

* Update MkMisskeyFlavoredMarkdown.ts

* Update CHANGELOG.md

* feat(CI): CHANGELOG.mdの追記個所をチェックするCIを追加 (#12963)

* feat(CI): CHANGELOG.mdの追記個所をチェックするCIを追加

* fix

* remove strategy

* fix

* fix

* enhance(drop-and-fusion): sweets mode

* 完成 (#12980)

* enhance(frontend): Playの説明欄にMFMを使えるように (#12899)

* (enhance) Playの説明欄にMFMを使えるように

* Update Changelog

* use class for mfm component

* Update packages/frontend/src/pages/flash/flash-edit.vue

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* Update flash.vue

* Update CHANGELOG.md

---------

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* fix: isPrivateIpで検証時にipバージョンが一致するかを確認するように (#12988)

* fix: isPrivateIpで検証時にipバージョンが一致するかを確認するように

* Update CHANGELOG.md

* Update CHANGELOG.md

* enhance(frontend) 日本語の拡張絵文字辞書を追加 (#12855)

* Create ja-JP.json

* Update general.vue

* Update ja-JP.json

* Update ja-JP.json

* Update ja-JP.json

* fix

* fix design

* (Add) ひらがな [wip]

* fix lint

* Apply suggestions from code review

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* (add) ja-JP_hira

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* (enhance) 言語名をちゃんと表示するように

---------

Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>

* refactor: noteテーブルのインデックス整理と配列カラムへのクエリでインデックスを使うように (#12993)

* Optimize note model index

* enhance(backend): ANY()をやめる (MisskeyIO#239)

* add small e2e test drive endpoint

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>

* enhance(frontend): dedicated games page

* enhance: 動画・音声周りのUIと動作改良 (#12925)

* wip

* (fix) `/files` をバイトレンジリクエストに対応させる

* video

* audio

* fix

* fix

* spdx

* fix (rangeRequest)

* fix

* Update CHANGELOG.md

* (add) ボリュームを保存できるように

* (fix) ミュート復帰時に音量が固定される

* named export

* tweak design

* Add sensitive class for audio component

* Refactor seekbar styles

* Refactor hms

* Revert "(add) ボリュームを保存できるように"

This reverts commit 6271f9493b.

* Revert "(fix) ミュート復帰時に音量が固定される"

This reverts commit a65002b56e.

* revert revert changes

---------

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

* (style) sticky系フッターのデザイン調整 (#13005)

* enhance(frontend): ページ遷移時にPlayerを閉じるように (#13013)

* なんかできた

* update changelog.md

* onDeactivatedを使うように

* Enhance(frontend): MkCustomEmojiDetailedDialogを調整 (#13015)

* MkEmojiDetailedDialogを調整

* 絵文字ライセンスでMFMを使えるように

* <a> -> <MkLink>

* 入力ボックスでmfmのオートコンプリートを効かせる

* enhance(frontend): チャンネルノートの場合はその前後を見れるように (#13019)

* チャンネルノートの場合はその前後を見れるように

* Update Changelog

* $[border ...]にクリッピング機能を追加 (#13002)

* Update MkMisskeyFlavoredMarkdown.ts

* Update MkMisskeyFlavoredMarkdown.ts

* Update CHANGELOG.md

* Set clipping as default

* Fix: properly handle cc followers (#13009)

* Fix: properly handle cc followers

Fix #13001

* Update CHANGELOG.md

* Fix syntax error

* enhance(drop-and-fusion): ゲームバランスの調整など

* MkCodeにコピーボタンを追加 (#12999)

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update MkCode.vue

* Update CHANGELOG.md

---------

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

* chore(drop-and-fusion): bump version

* refactor: MkCodeをブロックとインラインで別コンポーネント化する (#13026)

* Create MkCodeInline.vue

* Update MkCode.vue

* Update MkMisskeyFlavoredMarkdown.ts

* Update flash.vue

* Update MkCodeInline.vue

* fix(frontend/MediaVideo): 再生シークバーの当たり判定を調整 (#13027)

* fix(frontend/MediaVideo): 再生シークバーの当たり判定を調整

* fix

* feat(frontend): 横スワイプでタブを切り替える機能 (#13011)

* (add) 横スワイプでタブを切り替える機能

* Change Changelog

* y方向の移動が一定量を超えたらスワイプを中断するように

* Update swipe distance thresholds

* Remove console.log

* adjust threshold

* rename, use v-model

* fix

* Update MkHorizontalSwipe.vue

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

* use css module

---------

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

* refactor: fully typed locales (#13033)

* refactor: fully typed locales

* refactor: hide parameterized locale strings from type data in ts access

* refactor: missing assertions

* docs: annotation

* refactor: style

* fix(frontend/HorizontalSwipe): ページの要素がはみ出る問題を修正 (#13036)

* 「外部サイトからインストール」のパスを /install-extensions に変更 (#12991)

* /install-extensionsに変更

* CHANGELOG.mdに追記

* 旧パスも利用できるように

* fix: Some fixes for #12850 (#12862)

- refinement the error message when trueMail validation fails
- the settings of trueMail are not displayed after saving
- changing how `Active Email Validation` is saved

* Enhance(frontend): MFMの属性にオートコンプリートが利用できるように (#12803)

* MFMのパラメータでオートコンプリートできるように

* tweak conditions & refactor

* ファイル末尾の改行忘れ

* remove console.log & refactor

* 型付けに敗北

* fix

* update CHANGELOG.md

* tweak conditions

* CHANGELOGの様式ミス

* CHANGELOGを書く場所を間違えていたので修正

* move changelog

* move changelog

* typeof MFM_TAGS[number]

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

* $[border.noclip ]対応

* Update const.ts

---------

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

* feat: reversi

Resolve #12962

* refactor: deprecate i18n.t (#13039)

* refactor: deprecate i18n.t

* revert: deprecate i18n.t

This reverts commit 7dbf873a2f.

* chore: reimpl

* refactor: extract bubble-game engine as independent package

* lint fix

* lint fixes

* tweak reversi map

* fix lint

* fix(dev): fix workspace settings

* fix(dev): fix pnpm dev

* enhance(reversi): tweak reversi

* refactor: migrate to ESM

* fix api-extractor

* add missing ext

* enhance(reversi): tweak reversi

* 🎨

* Fix(frontend): 日本語のUnicode絵文字追加辞書をインストールすると絵文字ピッカーでUnicode絵文字を検索できなくなるのを修正 (#13046)

* 絵文字辞書のサロゲートペアを修正

* update CHANGELOG.md

* Revert "update CHANGELOG.md"

This reverts commit 7c24fa611a.

* enhance(reversi): tweak reversi

Resolve #13048

* Update Dockerfile

* enhance(reversi): tweak reversi

* enhance(frontend): ノート作成画面の添付メニューから直接ファイルを消せるように (#12858)

* (enhance) 添付画面から直接ファイルを消せるように

* Update Changelog

---------

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

* enhance(reversi): tweak reversi

* enhance(sw): オフライン表示のデザインを改善 (#13052)

* enhance(sw): オフライン表示のデザインを改善

* Update Changelog

* fix

* fix

* fix

* 言語が取得できなかった場合のフォールバックを追加

* (change) translation key

* enhance(reversi): tweak reversi

* enhance(reversi): tweak reversi

* fix(frontend): MkHorizontalSwipeでメニューを閉じるのに2回クリックが必要になる問題を修正

#13055

* return a `Vary: Accept` header for all dual-format endpoints #365 (#13044)

`/users/:user`, `/@:user`, `/notes/:note` return different responses
depending on the request's `Accept:` header. If we don't consistently
return a `Vary: Accept` header, browsers and caching proxies will get
confused, and return AP representations when HTML was requested, or
vice versa.

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

* enhance(frontend): タイムラインフィルターの設定を保持+センシティブなノートを隠せるように (#12848)

* (enhance) タイムラインフィルターの状態を記憶するように

* fix

* (enhance) センシティブな投稿をミュート形式で表示する(TLのみ)

* fix

* Update Changelog

* Fix changelog

* Lintエラーを潰す

* Update locales/ja-JP.yml

* hideSensitive -> withSensitive

* Update CHANGELOG.md

* Update ja-JP.yml

---------

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

* Enhance(frontend): 絵文字編集ダイアログをウィンドウにする (#13047)

* 絵文字編集ダイアログをウィンドウにする

* update CHANGELOG.md

* update deps

* New Crowdin updates (#12845)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Lao)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Lao)

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

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

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

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

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

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

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Lao)

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

* New translations ja-jp.yml (Romanian)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Dutch)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Swedish)

* New translations ja-jp.yml (Ukrainian)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Uzbek)

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

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Catalan)

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

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Greek)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Ukrainian)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Uzbek)

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

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

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

* New translations ja-jp.yml (Italian)

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

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

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

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

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

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

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

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Ukrainian)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (English)

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

* New translations ja-jp.yml (German)

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

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

* New translations ja-jp.yml (Thai)

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

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

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

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Romanian)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Greek)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Ukrainian)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Uzbek)

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

* New translations ja-jp.yml (Korean (Gyeongsang))

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

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Indonesian)

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

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

* chore(deps-dev): bump vite in /scripts/changelog-checker (#13040)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.11 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* enhance(frontend): 季節に応じた画面の演出を南半球に対応させる (#12838)

* (enhance) 季節に応じた画面の演出を南半球に対応させる

* Update Changelog

* (add) 半球の簡易自動判定

---------

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

* enhance(frontend): リファクタリングなど

* perf(reversi): improve performance of reversi backend

* 2024.2.0-beta.1

* fix(frontend/pizzax): デフォルト値が適用できないことがあるのを修正 (#13057)

* fix(frontend/pizzax): デフォルト値が適用できないことがあるのを修正

* fix

* いらんプロパティをけす

* refactor(reversi): refactoring of reversi backend

* New Crowdin updates (#13056)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Lao)

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

* New translations ja-jp.yml (Romanian)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (Danish)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Greek)

* New translations ja-jp.yml (Hungarian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Dutch)

* New translations ja-jp.yml (Norwegian)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Swedish)

* New translations ja-jp.yml (Turkish)

* New translations ja-jp.yml (Ukrainian)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Croatian)

* New translations ja-jp.yml (Uyghur)

* New translations ja-jp.yml (Lojban)

* New translations ja-jp.yml (Sinhala)

* New translations ja-jp.yml (Uzbek)

* New translations ja-jp.yml (Kannada)

* New translations ja-jp.yml (Haitian Creole)

* New translations ja-jp.yml (Kabyle)

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

* New translations ja-jp.yml (Korean (Gyeongsang))

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

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

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

* 2024.2.0-beta.2

* enhance(reversi): some tweaks

* perf(reversi): improve performance of reversi backend

* fix lint

* enhance(reversi): render ogp

* fix lint

* fix: 2024-01-22 10:50時点のdevelopにてCIがコケている (#13060)

* fix: バブルゲームのビルド失敗修正

* fix: api.jsonの定義誤りを修正

* fix: lint.yml(typecheck)

* fix: fix eslint error

* fix: frontend vitest version

* fix: frontend vitest version

* fix:

* fix: cypress

* fix: misskey-js test

* fix: misskey-js tsd(tsdはpakcage.jsonのexportsをサポートしない?)

* fix: conflict

* fix: 間違えて上書きしたところを修正

* fix: 再

* fix: api.json

* fix: api.json

* fix: タイムアウト延長

* Update packages/misskey-js/jest.config.cjs

Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>

* 🎨

* fix lint

* 2024.2.0-beta.3

* chore: publish misskey-js automatically (#13014)

* chore: publish @misskey-dev/misskey-js

* remove @misskey-dev/

* ??

* correct version

* version

* fix of #13014 (misskey-js publish)

* 修正できたかも (#13066)

* perf: (productionの)dependenciesから@typesを削除、reversi/bubble-gameをesbuildにする (#13067)

* perf: (productionの)dependenciesから@typesを削除、reversi/bubble-gameをesbuildにする

* fix

* fix

* fix(build): スクリプトの名前の変更漏れ (#13068)

* fix(build): スクリプトの名前の変更漏れ

* 漏れの漏れ

* 🎨

* enhance(reversi): improve desync handling

* New Crowdin updates (#13061)

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

* New translations ja-jp.yml (Korean)

* 2024.2.0-beta.4

* fix(frontend/HorizontalSwipe): スワイプ・UIアニメーションが無効の際はトランジションを行わないように (#13076)

* fix(frontend/HorizontalSwipe): アニメーションを減らすが考慮されるように

* fix

* fix

* revert unused change

* fix

* 🎨

* enhance(reversi): 準備中の自分の対局も一覧に表示するように

* enhance(reversi): more robust matching process

* fix of 65557d5f27

* enhance(reversi): 開始時に対局をシェアできるように

* enhance(reversi): improve stability

* New translations ja-jp.yml (Japanese, Kansai) (#13074)

* enhance(reversi): improve game setting flow

* enhance(reversi): tweak MATCHING_TIMEOUT_MS

* perf(reversi): set expire matchSpecific and matchAny

* fix(reversi): wait redis operation to improve stability

* 2024.2.0-beta.5

* fix(frontend/pizzax): オブジェクトにnullがある場合に正しくマージされないのを修正 (#13073)

* fix(frontend/pizzax): オブジェクトにnullがある場合に正しくマージされない

* fix types

* マージを内製

* fix(frontend/reversi): fix game preview

* enhance(reversi): improve matching system

* New translations ja-jp.yml (Japanese, Kansai) (#13077)

* 2024.2.0-beta.6

* enhance(reversi): 変則なしマッチングを可能に

* fix(reversi/backend): refactor and fixes

* Create deploy-test-environment.yml (#13079)

* test

* Revert "Create deploy-test-environment.yml (#13079)"

This reverts commit 4de14fb5cf.

* New Crowdin updates (#13080)

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

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

* 2024.2.0-beta.7

* New Crowdin updates (#13082)

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

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

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

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

* fix(dev): pnpm devで依存関係更新が一部反映されない (#13091)

* fix misskey-js version

* refactor(frontend/MediaPlayer): cssの重複を削除 (#13094)

* Update MkMediaAudio.vue

* Update MkMediaVideo.vue

* enhance(frontend): リモートのユーザーはメニューから直接リモートで表示できるように (#13087)

* enhance(frontend): リモートのユーザーはメニューから直接リモートで表示できるように

* change changelog

* Apply suggestions from code review

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

---------

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

* fix(backend): Fix typos in job configurations (#13086)

* Fix typos

* Update CHANGELOG

* Update CHANGELOG.md

* feat(frontend/nirax): リダイレクトを設定できるように (#13030)

* feat(frontend/nirax): リダイレクトを設定できるように

* revert demonstrative changes

* fix

* revert unrelated changes

* リダイレクトの際にパスが変わらない問題を修正

* リダイレクトが必要なrouteを設定

* fix lint

* router向けe2eテストの追加

* fix

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: samunohito <46447427+samunohito@users.noreply.github.com>

* fix(i18n): ストック情報とフロー情報の文言をわかりやすく変更 (#13085)

* fix(i18n): ストック情報とフロー情報をわかりやすく書き直す

* Update ja-JP.yml

* Update ja-JP.yml

* test(frontend): load default config to start vite (#12867)

Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>

* iOSで大きな画像を変換してアップロードできない問題を修正 (#13109)

Fix https://github.com/misskey-dev/misskey/issues/12026

* refactor: frontendのcomponentsの型エラーを改善 (#12926)

* add: safeFloatParserを追加

* fix: 欠けていた型を追加

* refactor: pageBlockTypesをjson-schemaに移植

* refactor: components/global内の型エラーが出ている箇所を修正

* lint: fix null check style

* refactor: fix type error

* refactor: fix some type errors

* fix: 翻訳が抜けていた箇所を修正

* refactor: getJsonSchemaで正しいスキーマが返されるように修正

* fix: MkChartの型エラーとbytesオプションが機能していない問題を修正

* fix(misskey-js): `drive`->`folderUpdated`のpayloadの型が間違っていたのを修正

* refactor: fix some type errors

* change: Captcha読み込み中の文言をLoadingに変更

* refactor(backend/misskey-js): MainEventの型を改善

* refactor: chartjs-plugin-gradientが二重でpluginに登録されていたのを修正

* update: misskey-js.api.md

* refactor: fix some type errors

* fix: backendのtypecheckが落ちていたのを修正

* update: misskey-js.api.md

* add: json-schemaのnoteにpollの型定義を追加

* refactor: noteのjson-schemaの型を改善

* refactor: MkPoll

* refactor: fix some type errors

* change: UserLiteにisLockedを持たせるように

* fix: notificationスキーマにroleが含まれていないのを修正

* Revert "change: UserLiteにisLockedを持たせるように"

This reverts commit 1bb0c8e7a9.

* fix: フォロー通知から鍵垢へのフォローを行うと処理中のまま止まってしまう問題を修正

* refactor: noteスキーマのvisibilityにenumを追加

* change: deepCloneのCloneableTypeにundefinedを追加

* refactor: fix some type errors

* refactor: `allowEmpty: false`を使用していた箇所を`minLength: 1`に置き換え

* enhance: API 'retension' のresponseの型を追加

* fix: Chart関連のtooltipが正しい位置に表示されない問題を修正

* refactor: fix some type errors

* fix: 型情報が不足していたのを修正

* enhance: announcementスキーマにenumを追加

* enhance: ロールポリシーの型定義をRoleServiceからjson-schemaに移植

* refactor: policiesを`ref: RolePolicies`に統一

* fix: API `meta` のレスポンスの型にpoliciesが含まれていないのを修正

* refactor: fix some type errors

* fix: backendのlintが落ちているのを修正

* fix: MkFoldableSectionの開閉時のanimationが適用されていない問題を修正

* fix: backendのtypecheckが落ちているのを修正

* update: run build-misskey-js-with-types

* fix: MkDialogのmount時に文字数制限の判定が行われない問題を修正

* update: CHANGELOG.md

* refactor: MkUserSelectDialogの型を改善

* fix: deepCloneでundefinedはcloneしないように (#9207)

* change: frontendのcloneをbackend側にも反映

* update: CHANGELOG.md

* fix: RoleServiceからPackを通して型RolePoliciesに依存させないように

* Update packages/frontend/src/scripts/get-note-summary.ts

* revert RoleService.ts changes

* change:  optional chaining -> non-null assertion

* remove: unused import

* fix: propsで渡されたuserがUserLiteの場合に意図しない動作になってしまうのを修正

* change: fix null check style

* refactor: fix type error

* change: fix null check style

* Update packages/frontend/src/components/MkDrive.vue

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

* refactor: css moduleでglobalを使わないように

* refactor: roleのiconUrlは必ず存在するものとして扱うように

* enhance: MenuButtonのactiveにcomputedを受け付けられるように

* Update packages/frontend/src/components/MkNotePreview.vue

* Update MkWindow.vue

* refactor: notification.noteは必ず存在するものとして扱うように

* Update packages/frontend/src/components/MkNotification.vue

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

* fix: MkSignupDialogでdoneのemit時にresを含んでいなかったのを修正

* Update packages/frontend/src/scripts/clone.ts

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

* refactor: 不要な返り値の型を削除

* refactor: 不要なnullチェックを削除

* update: misskey-js-autogen

* update: clone.ts

* refactor

* Update MkNotification.vue

* Update MkNotification.vue

* ✌️

* Update MkNotification.vue

* Update MkNotification.vue

* Update MkNotification.vue

* Update MkNotifications.vue

* Update MkUserSetupDialog.Profile.vue

* Update MkUserCardMini.vue

* ✌️

* Update MkMenu.vue

---------

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

* fix/refactor(reversi): 既存のバグを修正・型定義を強化 (#13105)

* 既存のバグを修正

* fix types

* fix misskey-js autogen

* Update index.d.ts

---------

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

* update deps

* 2024.2.0-beta.8

* Revert "Revert "Create deploy-test-environment.yml (#13079)""

This reverts commit 4553d6426b.

* refactor(frontend): global/router -> router

* refactor(backend): User関連のスキーマ/型の指定を強くする (#12808)

* refactor(backend): User関連のスキーマ/型の指定を強くする

* refactor(backend): `pack()`の引数にスキーマを指定するように

* chore: fix ci

* fix: 変更漏れ

* fix ci

---------

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

* fix(frontend): styleの指定方法を変更 (#13120)

* fix(ci): `misskey-js` のバージョンチェックをトリガーする条件の修正 (#13116)

* fix(misskey-js): バージョンチェックのトリガー条件を修正

* chore(misskey-js): 2024.2.0-beta.8

* Fix(frontend): リバーシで自分自信を招待できるのを修正 & os.selectUser()のincludeSelfが機能していないのを修正 (#13117)

* リバーシで自分自信を招待できるのを修正 & os.selectUser()のincludeSelfが機能していないのを修正

* lint fix

* enhance(frontend): 🌸

* chore(deps): bump codecov/codecov-action from 3 to 4 (#13125)

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: Hide reactions of all remote users / feat: moderators can see reactions of all users (#13128)

* fix: Hide reactions of all remote users
https://github.com/misskey-dev/misskey/issues/12964

* feat: Moderators can see reactions of all users
https://github.com/misskey-dev/misskey/issues/13127

* modify CHANGELOG.md

* fix iAmModerator

* chore(deps): bump peter-evans/slash-command-dispatch from 3 to 4 (#13124)

Bumps [peter-evans/slash-command-dispatch](https://github.com/peter-evans/slash-command-dispatch) from 3 to 4.
- [Release notes](https://github.com/peter-evans/slash-command-dispatch/releases)
- [Commits](https://github.com/peter-evans/slash-command-dispatch/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/slash-command-dispatch
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* 「見たことのあるリノートを省略して表示」が効いていない問題を修正  (#13133)

* fix: 「見たことのあるリノートを省略して表示」が効いていない問題を修正
fix #13131

* add a comment

* fix(backend): "誰でも新規登録できるようにする"の初期値をOFFにする (#13130)

* fix(backend): "誰でも新規登録できるようにする"の初期値をOFFにする

* fix CHANGELOG.md

* fix

* Update deploy-test-environment.yml (#13136)

* fix: api-docが開けない問題を修正 (#13132)

* refactor: 自己参照を使用している箇所に`selfRef`を持たせるように

* feat: スキーマ生成時に自己参照を含むかどうかを指定できるように

* fix: api.jsonにselfRefが含まれているのを修正

* refactor: 他の箇所と同様にselfRefの除去を行うように

* remove: 不要なimportを削除

* refactor(frontend): `os.popup()`の`props`の型チェックを有効化 (#13140)

* refactor(frontend): `os.popup()`の`props`の型チェックを有効化

* refactor: `ComponentProps`に書き換え

* refacor: `import type`

* enhance(frontend): shiki v1に移行 (#13138)

* enhance(frontend): shiki v1に移行

* optimize chunks, エラーを握りつぶす

* wasmを分離

* バンドルサイズの警告の最小値を650kBに引き上げ

* optimize

* fix(frontend): アバターデコレーションのアニメーションが止まらない (#13139)

* fix(frontend): アバターデコレーションのアニメーションが止まらない

* Update Changelog

* i -> index

* key

* revert lint fixes

* fix(frontend): selectUserのパラメータを調整 (#13142)

* fix(frontend): selectUserのパラメータを調整

* ついでに軽微なスタイルの修正

* fix(frontend): チャートのlegendがクリックに反応しない問題を修正

これにより発生 https://github.com/misskey-dev/misskey/pull/12926

* New Crowdin updates (#13090)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean (Gyeongsang))

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

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

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (French)

* 2024.2.0-beta.9

* fix(backend): メール配信機能が無効ならばメールを送ることのないように (#13152)

Do not send email if email delivery is disabled

* ignore `instance.actor` when checking if there are local users (#13146)

* ignore `instance.actor` when checking if there are local users

We've seen this happen a few times:

* there was some AP software at $some_domain
* it gets replaced by Misskey
* before the first user can be created, an AP activity comes in
* Misskey resolves the activity
* to do this, it creates the `instance.actor` to sign its request
* now there *is* a local user, so the `meta` endpoint returns
  `requireSetup:false`
* the admin is very confused

This commit factors out the check, and doesn't count the
`instance.actor` as a real user.

* autogen bits

* keep cached avatar&banner when refresh fails to get new values (#13145)

* keep cached avatar&banner when refresh fails to get new values

when the remote explicitly tells us a user image is gone, we remove
our cached value, but if we fail to get the image, we keep whatever
value we already have

this should minimise the problem of avatars randomly disappearing

* autogen bits

* pnpm run build-misskey-js-with-types

---------

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

* update patrons

* New Crowdin updates (#13156)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* Fix(frontend): クロップ後の解像度が異様に低くなる問題の修正&クロップに失敗する問題&コメントにnullという文字列が入る問題の修正 (#13162)

* Fix(frontend): Fix resolution of cropped image (misskey-dev#11489)

* CHANGELOG

* Fix(frontend): クロップの際、folderIdがnullだと文字列のnullが送られ検索できない問題

* Fix: キャプションが存在しないときにクロップすると'null'がキャプションに入ってしまう問題 (misskey-dev#11813)

* Update CHANGELOG

* refactor(frontend): `os.popup()`の`events`の型チェックを有効化 (#13165)

* 2024.2.0-beta.10

* enhance(frontend): シンタックスハイライトにテーマを適用できるように (#13175)

* enhance(frontend): シンタックスハイライトにテーマを適用できるように

* Update Changelog

* こっちも

* テーマの値がディープマージされるように

* 常にテーマ設定に準じるように

* テーマ更新時に新しいshikiテーマを読み込むように

* enhance(frontend): KeepAliveのページキャッシュを削除できるように (#13180)

* enhance(frontend): 内部のページキャッシュを削除できるように

* Update Changelog

* Enhance(frontend): フロント側でもリアクション権限のチェックをするように (#13134)

* フロント側でもリアクション権限のチェックをするように

* update CHANGELOG.md

* lint fixes

* remove unrelated diffs

* deny -> reject
denyは「(信用しないことを理由に)拒否する」という意味らしい

* allow -> accept

* EmojiSimpleにlocalOnlyを含めるように

* リアクション権限のない絵文字は打てないように(ダイアログを出すのではなく)

* regenerate type definitions

* lint fix

* remove unused locales

* remove unnecessary async

* fix(frontend): エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正 (#13172)

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

* enhance(frontend): リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように (#13178)

* enhance(frontend): リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように

* Update Changelog

---------

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

* AP Key の JSON-LD 表現を修正 (#13170)

* CHANGELOGを修正 (#13181)

* chore(frontend): reword possible typo (#13182)

* fix(bubble-game): 共有用画像のコメントにnullが入る問題を修正 (#13183)

* fix(misskey-js): 自動生成物の冒頭からバージョンと日付を削除 (#13185)

* Enhance: 連合向けのノート配信を軽量化 (#13192)

* AP HTML表現をシンプルに

* a

* CHANGELOG

* リンク

* Fix(frontend): MkCodeEditorで行がずれていくのを修正 (#13188)

* MkCodeEditorで行がずれていくのを修正

* update CHANGELOG.md

* 正しい 2024.2.0-beta.10 改版手順? (#13173)

* 正しい 2024.2.0-beta.10 改版手順?

* run build-misskey-js-with-types

* enhance(frontend/HorizontalSwipe): 操作性の改善 (#13038)

* Update swipe thresholds and touch-action

* スワイプ中にPullToRefreshが反応しないように

* 横スワイプに関与する可能性のある要素がある場合はスワイプを発火しないように

* update threshold

* isSwipingを外部化

* rename

---------

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

* typo

* Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 (#13196)

* Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正

* CHANGELOG

* test(frontend): migrate MSW in Storybook to v2 (#13195)

* fix(frontend) misskey-js type (#13202)

* refactor(backend): exist -> exists (#13203)

* refactor(backend): exist -> exists

* fix

* fix(frontend): aiscriptのコードブロックでのハイライト指定を修正 (#13208)

* chore: use vite@5.1.0 / pnpm@8.15.1

* fix: 特定文字列を含むノートを投稿できないようにする管理画面用設定項目を追加 (#13210)

* fix: 特定文字列を含むノートを投稿できないようにする管理画面用設定項目を追加

* Serviceでチェックするように変更

* perf(frontend): splash screenのdomが消えない場合があるのを修正

https://github.com/misskey-dev/misskey/issues/10805

* chore(deps): bump pnpm/action-setup from 2 to 3 (#13215)

Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 3.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2...v3)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* New Crowdin updates (#13179)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Russian)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Thai)

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

* New translations ja-jp.yml (Catalan)

* update deps

* 2024.2.0-beta.11

* fix misskey-js version

* dev: Update misskey-tga deploy-test-environment.yml (#13221)

* fix: misskey-jsの型定義生成時にバックエンドの依存パッケージもビルドするように (#13249)

* fix(frontend): vue v3.4.16でタイムラインが正常に表示できない問題を修正

* type

* fix: misskey-jsの型定義生成時にバックエンドの依存パッケージもビルドするように

* Revert "type"

This reverts commit bac0951bd1.

* Revert "fix(frontend): vue v3.4.16でタイムラインが正常に表示できない問題を修正"

This reverts commit 92b2165828.

* Update about-misskey.vue

* New Crowdin updates (#13216)

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

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* feat: provide tarball (#13260)

* feat: provide tarball

* build: pack on build-assets

* chore: use ignore-walk

* chore: debug

* build: dependencies

* New translations ja-jp.yml (Spanish) (#13261)

* update SPDX-FileCopyrightText

* refactor(msjs): avoid any (part 1) (#13247)

* refactor(msjs): avoid any

* run api extractor

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <daisho7308+f@gmail.com>

* ci(test-frontend): Cypressのテストの失敗時、永遠に止まらない問題を回避 (MisskeyIO#434) (#13274)

失敗しないようタイムアウトの延長・15分で止まるように

* chore: 以前の開発環境(backendにアクセスする方式)を立ち上げられるように (#13220)

* chore: 以前の開発環境(backendにアクセスする方式)を立ち上げられるように

* Update scripts/dev.mjs

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>

* fix: downgrade vue to 3.4.15

* enhance: 禁止ワードはリモートノートも対象に (#13280)

Resolve #13279

* Update CHANGELOG.md (#13282)

#13281 に対応していることを強調

* perf: omit search for immutable static requests (#13265)

* perf: omit search for immutable static requests

* perf: also applies to /files

* fix: exclude /proxy

* /files/:key/*を301 redirectに

---------

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

* Revert "update SPDX-FileCopyrightText"

This reverts commit 9b5aeb76d8.

* (re)  update SPDX-FileCopyrightText
Fix  #13290

* fix(frontend): エラーページのトラブルシューティングがリンク切れしている問題 (#176) (#13288)

* fix: TypeAssertionExpression breaks Storybook builds

* build: upgrade Storybook to 8 beta (#13297)

* chore: upgrade Storybook to 8

* ci: restore Storybook workflow

* build: createRequire

* ci: TurboSnap life extension

* dev: Update misskey-tga (#13223)

* Update deploy-test-environment.yml

* Update .github/workflows/deploy-test-environment.yml

Co-authored-by: anatawa12 <anatawa12@icloud.com>

* Update deploy-test-environment.yml

* Update deploy-test-environment.yml

---------

Co-authored-by: anatawa12 <anatawa12@icloud.com>

* fix(ci): publish docker image fails (#13325)

* fix(ci): publish docker image fails

* fix: `docker.yml`

* refactor: remove inaccurate name

* fix: match version

* feat(backend): likeOnlyなどでハートにフォールバックする際異体字セレクタがない方に揃える (#13299)

* feat(backend): likeOnlyなどでハートにフォールバックする際異体字セレクタがない方に揃える

close #13298

* Update ReactionService.ts

* chore(backend): prefer single quote for string literal

* fix(backend): add missing schemas and fix incorrect schemas (#13295)

* fix(backend): add missing schemas and fix incorrect schemas

* fix: ci

* fix: ci (本命)

* fix: run `pnpm build-misskey-js-with-types`

* fix: typos

* fix: role-condition-formula-value contains `id`

* fix: incorrect schema

* リモートユーザーが復活してもキャッシュにより該当ユーザーのActivityが受け入れられないのを修正 Fix #13273 (#13275)

* リモートユーザーが復活してもキャッシュにより該当ユーザーのActivityが受け入れられないのを修正 Fix #13273

* CHAGELOG

* Use Redis event

---------

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

* refactor(backend): misc/cacheをシンプルな実装に戻した

* fix

* fix type

* Update CHANGELOG.md

* New Crowdin updates (#13267)

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

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (English)

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

* New translations ja-jp.yml (Thai)

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

* fix(frontend): vue v3.4.16以降でタイムラインが正常に表示できない問題を修正 (#13248)

* fix(frontend): vue v3.4.16でタイムラインが正常に表示できない問題を修正

* type

* Revert "fix: downgrade vue to 3.4.15"

This reverts commit e12369ac13.

* Update pnpm-lock.yaml

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* 2024.2.0-beta.12

* fix(ci): publish docker image fails (3) (#13327)

* fix(ci): publish docker image fails (3)

* fix: set `tags`

* fix(frontend/pageMetadata): ページタイトルが更新されない問題 (#13289)

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

* chore(deps): bump actions/github-script from 6.4.0 to 7.0.1 (#13311)

Bumps [actions/github-script](https://github.com/actions/github-script) from 6.4.0 to 7.0.1.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6.4.0...v7.0.1)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump pnpm/action-setup from 2 to 3 (#13310)

Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 3.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2.0.0...v3)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/checkout from 3.6.0 to 4.1.1 (#13309)

Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.1.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.6.0...v4.1.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/upload-artifact from 3 to 4 (#13308)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/setup-node from 3.8.1 to 4.0.2 (#13307)

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.8.1 to 4.0.2.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3.8.1...v4.0.2)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build: docker buildのpnpm i実行時にNODE_ENV=productionが指定されるようにする (#13329)

* fix of  #13330 (#13330)

* build: docker buildのpnpm i実行時にNODE_ENV=productionが指定されるようにする

* build: 消す行間違ってたのを修正

* fix(dev): devコマンドの実装を修正 (#13336)

* fix misskey-js version

* refactor(backend): remove/replace deprecated type deps (#13252)

* Update CHANGELOG.md

* 2024.2.0-beta.13

* Merge pull request from GHSA-qqrm-9grj-6v32

* maybe ok

* fix

* test wip

* ✌️

* fix

* if (res.ok)

* validateContentTypeSetAsJsonLD

* 条件を考慮し直す

* その他の+json接尾辞が付いているメディアタイプも受け容れる

* https://github.com/misskey-dev/misskey-ghsa-qqrm-9grj-6v32/pull/1#discussion_r1490999009

* add `; profile="https://www.w3.org/ns/activitystreams"`

* application/ld+json;

* feat: add link to local note in initial comment of abuse note (#13347)

* feat: add link to local note in initial comment of abuse note

* docs(changelog): ノートの通報時にリモートのノートであっても自インスタンスにおけるノートのリンクを含むように

* feat: license violation protection (#13285)

* spec(frontend): aboutページにリポジトリ・フィードバックのURLを表示させる

Cherry-picked from MisskeyIO#441
Cherry-picked from MisskeyIO#438

* feat: license violation protection

* build: fix typo

* build: fix typo

* fix: farewell to the static type land

* fix: key typo

* fix: import typo

* fix: properly interpret `prominently`

* docs: add disclaimer

* docs: update CHANGELOG

* chore: add gap

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: 1Step621 <86859447+1STEP621@users.noreply.github.com>
Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Co-authored-by: FineArchs <133759614+FineArchs@users.noreply.github.com>
Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
Co-authored-by: ikasoba <57828948+ikasoba@users.noreply.github.com>
Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Co-authored-by: GrapeApple0 <84321396+GrapeApple0@users.noreply.github.com>
Co-authored-by: YS <47836716+yszkst@users.noreply.github.com>
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
Co-authored-by: a <a@trwnh.com>
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: Korange <hi@korange.work>
Co-authored-by: AsukaMari <2037177696@qq.com>
Co-authored-by: dakkar <dakkar@thenautilus.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: Srgr0 <66754887+Srgr0@users.noreply.github.com>
Co-authored-by: woxtu <woxtup@gmail.com>
Co-authored-by: Kagami Sascha Rosylight <saschanaz@outlook.com>
Co-authored-by: yukineko <27853966+hideki0403@users.noreply.github.com>
Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com>
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Co-authored-by: Soli <personal@str08.net>
Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>
Co-authored-by: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <daisho7308+f@gmail.com>
Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com>
Co-authored-by: anatawa12 <anatawa12@icloud.com>
2024-02-17 15:05:47 +09:00
syuilo
4ae591a2c7 Merge branch 'develop' into mahjong 2024-02-14 21:03:58 +09:00
im_tan
af9ebf7034 test: [Mi麻雀] 役満(天和、地和を除く)のテスト (#13263)
* add daisangen test

* add suanko test

* add suanko-tanki test

* add tsuiso test

* fix typo

* add test-shosushi

* add test-daisushi

* add ryuiso-test

* add chinroto-test

* add sukantsu-test

* add kokushi-13-test
2024-02-12 18:38:55 +09:00
syuilo
b04a0c99a4 wip 2024-02-12 11:50:46 +09:00
syuilo
10a2c16a6d Merge branch 'develop' into mahjong 2024-02-12 11:50:03 +09:00
im_tan
622fc44645 add kokushi test (#13262) 2024-02-12 09:06:00 +09:00
syuilo
3f810a856c wip 2024-02-11 14:23:37 +09:00
syuilo
c47203b888 wip 2024-02-11 12:45:50 +09:00
syuilo
c99d55e0cb Merge branch 'develop' into mahjong 2024-02-09 19:34:18 +09:00
syuilo
bb042b46ac wip 2024-02-09 15:42:33 +09:00
syuilo
084e9449dc Merge branch 'develop' into mahjong 2024-02-09 10:20:01 +09:00
syuilo
2af3710757 Merge branch 'develop' into mahjong 2024-02-08 16:59:43 +09:00
syuilo
894f65f754 wip 2024-02-07 20:52:11 +09:00
syuilo
166aeb631e wip 2024-02-06 18:21:58 +09:00
syuilo
2d6f9b083f wip 2024-02-06 15:41:57 +09:00
syuilo
7a9434414d Merge branch 'develop' into mahjong 2024-02-05 21:08:21 +09:00
syuilo
b302796e70 wip 2024-02-05 21:07:44 +09:00
syuilo
76cdb48a3e Merge branch 'develop' into mahjong 2024-02-05 15:02:55 +09:00
syuilo
b32022c20c wip 2024-02-05 15:02:43 +09:00
syuilo
b785793e41 Merge branch 'develop' into mahjong 2024-02-05 11:04:04 +09:00
syuilo
bfb6e2f461 wip 2024-02-05 10:58:56 +09:00
syuilo
38e3d248fb wip 2024-02-04 17:11:42 +09:00
syuilo
be3b2558d1 wip 2024-02-04 13:26:40 +09:00
syuilo
d57f20dc84 wip 2024-02-03 22:14:44 +09:00
syuilo
00bf57d243 wip 2024-02-03 18:02:00 +09:00
syuilo
586a458c7a wip 2024-02-03 17:00:45 +09:00
syuilo
2dd886e285 wip 2024-02-03 13:52:13 +09:00
syuilo
7cdaa10d46 wip 2024-02-02 20:56:39 +09:00
syuilo
9ea29fe84c Merge branch 'develop' into mahjong 2024-02-02 20:41:13 +09:00
syuilo
054a48c184 Update logo.png 2024-02-01 21:01:53 +09:00
syuilo
c964c49c58 wip 2024-02-01 20:34:05 +09:00
syuilo
10a112489d Merge branch 'develop' into mahjong 2024-02-01 16:42:32 +09:00
syuilo
859cf75ad3 wip 2024-01-31 18:49:42 +09:00
syuilo
3c97164cf2 wip 2024-01-31 18:31:02 +09:00
syuilo
8121f8f40f wip 2024-01-31 10:54:38 +09:00
syuilo
072928b147 Merge branch 'develop' into mahjong 2024-01-31 10:54:34 +09:00
syuilo
5af8b5d547 wip 2024-01-30 20:13:38 +09:00
syuilo
5e3a805671 Update index.d.ts 2024-01-30 19:56:16 +09:00
syuilo
ef14a56a5c Merge branch 'develop' into mahjong 2024-01-30 19:56:08 +09:00
syuilo
2f0924c85b wip 2024-01-30 17:11:16 +09:00
syuilo
4183fec4ab wip 2024-01-30 11:34:57 +09:00
syuilo
ce65e9dd69 refactor 2024-01-30 11:30:58 +09:00
syuilo
d7337e5f81 wip 2024-01-30 11:27:08 +09:00
syuilo
547b74c9b2 wip 2024-01-29 20:35:25 +09:00
syuilo
d427d24ca4 wip 2024-01-29 17:15:09 +09:00
syuilo
668bf9a226 wip 2024-01-29 14:14:00 +09:00
syuilo
11404e545e Update index.ts 2024-01-29 12:30:09 +09:00
syuilo
5f48109230 wip 2024-01-29 11:47:01 +09:00
syuilo
dad8430040 wip 2024-01-29 10:46:23 +09:00
syuilo
0111b8736a Merge branch 'develop' into mahjong 2024-01-29 07:50:16 +09:00
syuilo
1ea098f4b4 wip 2024-01-28 20:34:45 +09:00
syuilo
366fade8d3 wip 2024-01-28 20:20:18 +09:00
syuilo
db7bd0e94e wip 2024-01-28 17:31:32 +09:00
syuilo
606c88aa6b Merge branch 'develop' into mahjong 2024-01-28 15:19:24 +09:00
syuilo
55629f2b39 wip 2024-01-28 13:49:56 +09:00
syuilo
ab404d491d wip 2024-01-27 17:50:41 +09:00
syuilo
0f2991cbaf Merge branch 'develop' into mahjong 2024-01-27 12:03:08 +09:00
syuilo
34ed9cb187 Merge branch 'develop' into mahjong 2024-01-27 12:02:55 +09:00
syuilo
67e6184a75 wip 2024-01-26 14:25:00 +09:00
syuilo
2133d0552c Merge branch 'develop' into mahjong 2024-01-25 10:42:24 +09:00
syuilo
314c31db34 wip 2024-01-17 20:14:35 +09:00
syuilo
339acd2644 Merge pull request #12828 from misskey-dev/develop
Release: 2023.12.2
2023-12-28 08:31:51 +09:00
syuilo
53898c5006 Merge pull request #12771 from misskey-dev/develop
Release: 2023.12.1
2023-12-27 21:28:38 +09:00
syuilo
0b5228f3cd Merge pull request #12564 from misskey-dev/develop
Release: 2023.12.0
2023-12-23 20:00:20 +09:00
syuilo
9784d10c62 Merge pull request #12330 from misskey-dev/develop
Release: 2023.11.1
2023-11-17 18:32:42 +09:00
syuilo
0c2dd33593 Merge pull request #12177 from misskey-dev/develop
Release: 2023.11.0
2023-11-05 18:18:35 +09:00
syuilo
3043b5256d Merge pull request #12060 from misskey-dev/develop
Release: 2023.10.2
2023-10-21 14:18:53 +09:00
syuilo
7e7138c0eb Merge pull request #12011 from misskey-dev/develop
Release: 2023.10.1
2023-10-12 09:21:04 +09:00
syuilo
f964ef163b Merge pull request #11963 from misskey-dev/develop
Release: 2023.10.0
2023-10-10 20:40:13 +09:00
syuilo
0e6cd577cc Merge pull request #11926 from misskey-dev/develop
* fix(backend): Redisに古いMisskeyバージョンのキャッシュが残っている場合の問題を修正

* Update CHANGELOG.md

* enhance(front)end: improve moderation log

* enhance: ノートの翻訳機能の利用可否をロールで設定可能に

Resolve #11923

* 2023.9.3

* 後方互換性の強化

* Update CHANGELOG.md

* fix test

* [ci skip] New Crowdin updates (#11922)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Thai)

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

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

* feat: AiScriptでホストのアドレスを参照できる定数 (#11924)

* add HOST_URL

* Update CHANGELOG.md

* tweak

---------

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

---------

Co-authored-by: FineArchs <133759614+FineArchs@users.noreply.github.com>
2023-09-30 09:40:00 +09:00
syuilo
7adc8fcaf5 Merge pull request #11920 from misskey-dev/develop
Release: 2023.9.2
2023-09-29 18:11:30 +09:00
syuilo
e57b536767 Merge pull request #11898 from misskey-dev/develop
2023.9.1
2023-09-25 17:12:28 +09:00
syuilo
f32915b515 Merge pull request #11874 from misskey-dev/develop
Release: 2023.9.0
2023-09-24 18:21:31 +09:00
syuilo
a8d45d4b0d Merge pull request #11384 from misskey-dev/develop
Release: 13.14.2
2023-07-27 13:00:14 +09:00
syuilo
4e24aff408 Merge pull request #11338 from misskey-dev/develop
Release: 13.14.1
2023-07-21 20:40:03 +09:00
syuilo
e64a81aa1d Merge pull request #11301 from misskey-dev/develop
Release: 13.14.0
2023-07-21 20:36:07 +09:00
syuilo
7093662ce5 Merge pull request #10990 from misskey-dev/develop
Release: 13.13.2
2023-06-13 16:46:01 +09:00
syuilo
32c741154d Merge pull request #10961 from misskey-dev/develop
Release: 13.13.1
2023-06-06 11:34:36 +09:00
syuilo
407a965c1d Merge pull request #10932 from misskey-dev/develop
Release: 13.13.0
2023-06-05 19:47:08 +09:00
syuilo
de6348e8a0 Merge pull request #10833 from misskey-dev/develop
* refactor(frontend): use css modules

* feat: 投稿したコンテンツのAIによる学習を軽減するオプションを追加

Resolve #10819

* enhance(backend): publicReactionsをデフォルトtrueに

* 念のためnoimageaiもつける

* add X-Robots-Tag: noai

* Update ja-JP.yml

* fix(frontend): ブラーエフェクトを有効にしている状態で高負荷になる問題を修正

* enhance(backend): graceful shutdown for job queue and refactor

* fix(backend): テスト時は一部のサービスを停止

* fix test

* New Crowdin updates (#10815)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

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

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

* refactor

* bump

* refactor(frontend): use css module

* refactor(frontend): use css module

* delete unused component

* センシティブワードを正規表現、CWにも適用するように (#10688)

* cwにセンシティブが効いてない

* CWが無いときにTextを見るように

* 比較演算子間違えた

* とりあえずチェック

* 正規表現対応

* /test/giにも対応

* matchでしなくてもいいのでは感

* レビュー修正

* Update packages/backend/src/core/NoteCreateService.ts

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

* Update packages/backend/src/core/NoteCreateService.ts

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

* 修正

* wipかも

* wordsでスペース区切りのものできたかも

* なんか動いたかも

* test作成

* 文言の修正

* 修正

* note参照

---------

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

* Update CHANGELOG.md

* New Crowdin updates (#10823)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* ci: fix typo

* fix(frontend): より明確な説明にしたのとtypo修正

* fix typo

* fix(frontend): カラーバーがリプライには表示されないのを修正

* fix(frontend): チャンネル内の検索ボックスが挙動不審な問題を修正

Fix #10793

* enhance(backend): ノートのハッシュタグもMeilisearchに突っ込むように

今後ハッシュタグ検索とか実装するときのため

* feat(frontend): ユーザー指定ノート検索

* fix(frontend): fix retention chart rendering

* Update about-misskey.vue

* meta: Remove @rinsuki from reviewer-lottery (#10830)

* New Crowdin updates (#10824)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

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

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

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

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

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Russian)

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

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Thai)

* enhance(frontend): アカウント初期設定ウィザードにプライバシー設定を追加

* Update CHANGELOG.md

* fix(backend): ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正

* fix MkUserSetupDialog.Privacy.vue

* ci: skip non-Japanese locale on TurboSnap

* ci: notify on changes for push events

* ci: fix missing branch

* Update basic.cy.js

* [ci skip] New Crowdin updates (#10834)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (German)

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

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

* New translations ja-JP.yml (Arabic)

* 🎨

* 🎨

* enhance(frontend): add retention line chart

* update deps

* refactor

* fix(frontend): Pageにおいて画像ブロックに画像を設定できない問題を修正

Fix #10837

---------

Co-authored-by: nenohi <kimutipartylove@gmail.com>
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: rinsuki <428rinsuki+git@gmail.com>
2023-05-12 12:41:53 +09:00
syuilo
9ad57324db Merge pull request #10814 from misskey-dev/develop
Release: 13.12.1
2023-05-09 15:38:17 +09:00
syuilo
94690c835e Merge pull request #10774 from misskey-dev/develop
Release: 13.12.0
2023-05-09 09:17:34 +09:00
syuilo
c5d2dba28d Merge pull request #10608 from misskey-dev/develop
Release: 13.11.3
2023-04-13 12:18:07 +09:00
syuilo
272e0c874f Merge pull request #10606 from misskey-dev/EbiseLutica-patch-1
Update CHANGELOG.md
2023-04-13 08:35:14 +09:00
Ebise Lutica
d429f810a9 Update CHANGELOG.md 2023-04-13 00:31:22 +09:00
syuilo
75b28d6782 Merge pull request #10578 from misskey-dev/develop
Release: 13.11.2
2023-04-11 15:51:07 +09:00
syuilo
8b1362ab03 Merge pull request #10543 from misskey-dev/develop
Release: 13.11.1
2023-04-09 10:29:36 +09:00
syuilo
a096f621cf Merge pull request #10506 from misskey-dev/develop
13.11.0
2023-04-08 21:27:21 +09:00
syuilo
f54a9542bb Merge pull request #10402 from misskey-dev/develop
Release: 13.10.3
2023-03-25 08:36:41 +09:00
syuilo
a52bbc7c8d Merge pull request #10388 from misskey-dev/develop
Release: 13.10.2
2023-03-22 18:47:10 +09:00
syuilo
59768bdf3f Merge pull request #10383 from misskey-dev/develop
Release: 13.10.1
2023-03-22 16:30:36 +09:00
syuilo
1e67e9c661 Merge pull request #10342 from misskey-dev/develop
Release: 13.10.0
2023-03-22 09:55:38 +09:00
syuilo
ae517a99a7 Merge pull request #10218 from misskey-dev/develop
Release: 13.9.2
2023-03-06 11:54:12 +09:00
syuilo
b23a9b1a88 Merge pull request #10181 from misskey-dev/develop
Release: 13.9.1
2023-03-03 20:56:50 +09:00
syuilo
5bd68aa3e0 Merge pull request #10177 from misskey-dev/develop
Release: 13.9.0
2023-03-03 15:35:40 +09:00
syuilo
647ce174b3 Merge pull request #10112 from misskey-dev/develop
Release: 13.8.1
2023-02-26 20:57:13 +09:00
syuilo
02c8fd9de5 Merge pull request #10108 from misskey-dev/develop
* Add dialog to remove follower (#9718)

* update PULL_REQUEST_TEMPLATE

* 起動時にRedisの疎通確認を行う (#9832)

* 起動時にRedisの疎通確認を行う

* check:connectをstart内に移動

---------

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

* Pass `--detectOpenHandles` to Jest (#9895)

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

* enhance(client): MkUrlPreviewの閉じるボタンを見やすく (#9913)

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

* test(backend): restore ap-request tests (#9997)

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

* fix/refaftor(client): MkTime.vueの変更 (#10061)

* fix(client): MkTime.timeにstringでもDateでない値が入った場合、?を表示

* fix(client): MkTimeを改良

* numberを許容

* falsyな値もとる

* 不明

* ありません

* fix

* fix(server): notes/createで、fileIdsと見つかったファイルの数が異なる場合はエラーにする (#9911)

* fix(server): notes/createで、fileIdsと見つかったファイルの数が異なる場合はエラーにする

* NO_SUCH_FILE

* Update codecov.yml

* Update apple-touch-icon.png

* デプロイされているプレビュー環境がない場合はプレビュー環境を削除しないようにする (#10062)

* デプロイされているプレビュー環境がない場合はDestroy preview environmentを実行しないようにする

* CIがない場合の処理追加

* enhance(client): improve clip menu ux

* 未知のユーザーが deleteActor されたら処理をスキップする (#10067)

* fix(client): Android ChromeでPWAとしてインストールできない問題を修正 (#10069)

* fix(client): Android ChromeでPWAとしてインストールできない問題を修正

* 順番関係ある?

* Windows環境でswcを使うと正常にビルドができない問題の修正 (#10074)

* Update @swc/core to v1.3.36

* Update CHANGELOG.md

* Update CHANGELOG.md

* バックグラウンドで一定時間経過したらページネーションのアイテム更新をしない (#10053)

* 🎨

* feat: 2つの検索画面の統合 (#9949) (#10038)

* feat: 検索画面の UI を統一

* fix: エラーの修正

* add: changelog

---------

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

* enhance(client): ノートメニューからユーザーメニューを開けるように

Resolve #10019

* enhance(client): renoteした際の表示を改善

Resolve #10078

* Update CHANGELOG.md

* enhance(client): tweak contextmenu position calculation

* 🎨

* 🎨

* feat: in-channel featured note

Resolve #9938

* refactor(frontend): fix eslint error (#10084)

* Simplify search.vue (remove dead code) (#10088)

* Simplify search.vue

This is already handled by the code above it, no need to handle it twice

* Remove unused imports

* Update about-misskey.vue

* test(server): add validation test of api:notes/create (#10090)

* fix(server): notes/createのバリデーションが効いていない
Fix #10079

Co-Authored-By: mei23 <m@m544.net>

* anyOf内にバリデーションを書いても最初の一つしかチェックされない

* ✌️

* wip

* wip

* ✌️

* RequiredProp

* Revert "RequiredProp"

This reverts commit 7469390011.

* add api:notes/create

* fix lint

* text

* ✌️

* improve readability

---------

Co-authored-by: mei23 <m@m544.net>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* New Crowdin updates (#10059)

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

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Ukrainian)

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

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Thai)

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

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

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

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Ukrainian)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

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

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

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* enhance(client): improve user menu ux

* enhance(client): photoswipe 表示時に戻る操作をしても前の画面に戻らないように (#10098)

* enhance(client): photoswipe 表示時に戻る操作をしても前の画面に戻らないように

* add: changelog

---------

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

* enhance(client): メニューの「もっと」からインスタンス情報を見れるように

* [Fix] fixed an typo in error message (#10102)

* Update codecov.yml

* Update CHANGELOG.md

* fix(server): エラーのスタックトレースは返さないように

Fix #10064

* [chore]Editorconfig: ymlに加えてyamlファイルに対しても同じ規約を適用する (#10081)

* Added yaml file in addition to yml file, in editorconfig

* Applied editorconfig for pnpm-workspace.yaml

---------

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

* update deps

* ホームタイムラインの読み込みでクエリタイムアウトになるのを修正する (#10106)

* refactor

* New translations ja-JP.yml (French) (#10103)

* Update CHANGELOG.md

* 13.8.0

---------

Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com>
Co-authored-by: Masaya Suzuki <15100604+massongit@users.noreply.github.com>
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: Kagami Sascha Rosylight <saschanaz@outlook.com>
Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com>
Co-authored-by: xianon <xianon@hotmail.co.jp>
Co-authored-by: kabo2468 <28654659+kabo2468@users.noreply.github.com>
Co-authored-by: YS <47836716+yszkst@users.noreply.github.com>
Co-authored-by: Khsmty <me@khsmty.com>
Co-authored-by: Soni L <EnderMoneyMod@gmail.com>
Co-authored-by: mei23 <m@m544.net>
Co-authored-by: daima3629 <52790780+daima3629@users.noreply.github.com>
Co-authored-by: Windymelt <1113940+windymelt@users.noreply.github.com>
2023-02-26 20:21:54 +09:00
syuilo
1ba49b614d Merge pull request #10058 from misskey-dev/develop
Release: 13.7.5
2023-02-24 13:06:55 +09:00
tamaina
40de14415c Release: 13.7.4
Merge pull request #10050 from misskey-dev/develop
2023-02-23 23:11:25 +09:00
tamaina
7c9330a02f Release: 13.7.3
Merge pull request #10048 from misskey-dev/develop
2023-02-23 22:15:56 +09:00
227 changed files with 13346 additions and 3411 deletions

View File

@@ -12,7 +12,6 @@ node_modules/
packages/*/node_modules
redis/
files/
misskey-assets/
fluent-emojis/
.pnp.*

View File

@@ -53,8 +53,8 @@ body:
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.io
* Misskey: 13.x.x
* Server URL: misskey.example.com
* Misskey: 2024.x.x
value: |
* Model and OS of the device(s):
* Browser:
@@ -74,11 +74,11 @@ body:
Examples:
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
* Misskey: 13.x.x
* Misskey: 2024.x.x
* Node: 20.x.x
* PostgreSQL: 15.x.x
* Redis: 7.x.x
* OS and Architecture: Ubuntu 22.04.2 LTS aarch64
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
value: |
* Installation Method or Hosting Service:
* Misskey:

1
.gitignore vendored
View File

@@ -59,6 +59,7 @@ ormconfig.json
temp
/packages/frontend/src/**/*.stories.ts
tsdoc-metadata.json
misskey-assets
# blender backups
*.blend1

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "misskey-assets"]
path = misskey-assets
url = https://github.com/misskey-dev/assets.git
[submodule "fluent-emojis"]
path = fluent-emojis
url = https://github.com/misskey-dev/emojis.git

View File

@@ -2,6 +2,7 @@
### Note
- デッキUIの新着ートをサウンドで通知する機能の追加v2024.5.0)に伴い、以前から動作しなくなっていたクライアント設定内の「アンテナ受信」「チャンネル通知」サウンドを削除しました。
- Streaming APIにて入力が不正な場合にはそのメッセージを無視するようになりました。 #14251
### General
- Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705
@@ -10,7 +11,6 @@
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
- Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
### Client
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
@@ -21,6 +21,8 @@
- Enhance: サーバー情報ページ・お問い合わせページを改善
(Cherry-picked from https://github.com/taiyme/misskey/pull/238)
- Enhance: AiScriptを0.19.0にアップデート
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
- Enhance: センシティブなメディアを開く際に確認ダイアログを出せるように
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
@@ -30,6 +32,19 @@
- Fix: ショートカットキーが連打できる問題を修正
(Cherry-picked from https://github.com/taiyme/misskey/pull/234)
- Fix: MkSignin.vueのcredentialRequestからReactivityを削除ProxyがPasskey認証処理に渡ることを避けるため
- Fix: 「アニメーション画像を再生しない」がオンのときでもサーバーのバナー画像・背景画像がアニメーションしてしまう問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/574)
- Fix: Twitchの埋め込みが開けない問題を修正
- Fix: 子メニューの高さがウィンドウからはみ出ることがある問題を修正
- Fix: 個人宛てのダイアログ形式のお知らせが即時表示されない問題を修正
- Fix: 一部の画像がセンシティブ指定されているときに画面に何も表示されないことがあるのを修正
- Fix: リアクションしたユーザー一覧のユーザー名がはみ出る問題を修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/672)
- Fix: `/share`ページにおいて絵文字ピッカーを開くことができない問題を修正
- Fix: deck uiの通知音が重なる問題 (#14029)
- Fix: ダイレクト投稿の"削除して編集"において、宛先が保持されていなかった問題を修正
- Fix: 投稿フォームへのURL貼り付けによる引用が下書きに保存されていなかった問題を修正
- Fix: "削除して編集"や下書きにおいて、リアクションの受け入れ設定が保持/保存されていなかった問題を修正
### Server
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
@@ -60,7 +75,13 @@
- Fix: 一般ユーザーから見たユーザーのバッジの一覧に公開されていないものが含まれることがある問題を修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/652)
- Fix: ユーザーのリアクション一覧でミュート/ブロックが機能していなかった問題を修正
- Fix: FTT有効時にリモートユーザーのートがHTLにキャッシュされる問題を修正
- Fix: 一部の通知がローカル上のリモートユーザーに対して行われていた問題を修正
- Fix: エラーメッセージの誤字を修正 (#14213)
- Fix: ソーシャルタイムラインにローカルタイムラインに表示される自分へのリプライが表示されない問題を修正
- Fix: リノートのミュートが適用されるまでに時間がかかることがある問題を修正
(Cherry-picked from https://github.com/Type4ny-Project/Type4ny/commit/e9601029b52e0ad43d9131b555b614e56c84ebc1)
- Fix: Steaming APIが不正なデータを受けた場合の動作が不安定である問題 #14251
### Misskey.js
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)

View File

@@ -1,7 +1,7 @@
# Contribution guide
We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project.
> **Note**
> [!NOTE]
> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
@@ -17,16 +17,31 @@ Before creating an issue, please check the following:
- Issues should only be used to feature requests, suggestions, and bug tracking.
- Please ask questions or troubleshooting in [GitHub Discussions](https://github.com/misskey-dev/misskey/discussions) or [Discord](https://discord.gg/Wp8gVStHW3).
> **Warning**
> [!WARNING]
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
## Before implementation
### Recommended discussing before implementation
We welcome your proposal.
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them.
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask Committer to assign you).
By expressing your intention to work on the Issue, you can prevent conflicts in the work.
To the Committers: you should not assign someone on it before the Final Decision.
### How issues are triaged
The Committers may:
* close an issue that is not reproducible on latest stable release,
* merge an issue into another issue,
* split an issue into multiple issues,
* or re-open that has been closed for some reason which is not applicable anymore.
@syuilo reserves the Final Decision rights including whether the project will implement feature and how to implement, these rights are not always exercised.
## Well-known branches
- **`master`** branch is tracking the latest release and used for production purposes.
@@ -37,14 +52,14 @@ Also, when you start implementation, assign yourself to the Issue (if you cannot
## Creating a PR
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to [`CHANGELOG.md`](/CHANGELOG.md). However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
- You can run it with `pnpm test` and `pnpm lint`. [See more info](#testing)
- You can run it with `pnpm test` and `pnpm lint`. [See more info](#testing)
- If this PR includes UI changes, please attach a screenshot in the text.
Thanks for your cooperation 🤗
@@ -54,8 +69,8 @@ Be willing to comment on the good points and not just the things you want fixed
### Review perspective
- Scope
- Are the goals of the PR clear?
- Is the granularity of the PR appropriate?
- Are the goals of the PR clear?
- Is the granularity of the PR appropriate?
- Security
- Does merging this PR create a vulnerability?
- Performance
@@ -77,7 +92,7 @@ An actual domain will be assigned so you can test the federation.
## Release
### Release Instructions
1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
1. Commit version changes in the `develop` branch ([package.json](package.json))
2. Create a release PR.
- Into `master` from `develop` branch.
- The title must be in the format `Release: x.y.z`.
@@ -88,7 +103,7 @@ An actual domain will be assigned so you can test the federation.
- The target branch must be `master`
- The tag name must be the version
> **Note**
> [!NOTE]
> Why this instruction is necessary:
> - To perform final QA checks
> - To distribute responsibility
@@ -106,12 +121,42 @@ If your language is not listed in Crowdin, please open an issue.
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
## Development
During development, it is useful to use the
### Setup
Before developing, you have to set up environment. Misskey requires Redis, PostgreSQL, and FFmpeg.
You would want to install Meilisearch to experiment related features. Technically, meilisearch is not strict requirement, but some features and tests require it.
There are a few ways to proceed.
#### Use system-wide software
You could install them in system-wide (such as from package manager).
#### Use `docker compose`
You could obtain middleware container by typing `docker compose -f $PROJECT_ROOT/compose.local-db.yml up -d`.
#### Use Devcontainer
Devcontainer also has necessary setting. This method can be done by connecting from VSCode.
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
To use Dev Container, open the project directory on VSCode with Dev Containers installed.
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
It will run the following command automatically inside the container.
``` bash
git submodule update --init
pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml
pnpm build
pnpm migrate
```
After finishing the migration, you can proceed.
### Start developing
During development, it is useful to use the
```
pnpm dev
```
command.
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
@@ -135,26 +180,6 @@ MK_DEV_PREFER=backend pnpm dev
- To change the port of Vite, specify with `VITE_PORT` environment variable.
- HMR may not work in some environments such as Windows.
### Dev Container
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
To use Dev Container, open the project directory on VSCode with Dev Containers installed.
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
It will run the following command automatically inside the container.
``` bash
git submodule update --init
pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml
pnpm build
pnpm migrate
```
After finishing the migration, run the `pnpm dev` command to start the development server.
``` bash
pnpm dev
```
## Testing
- Test codes are located in [`/packages/backend/test`](/packages/backend/test).
@@ -204,7 +229,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
### ルート定義
ルート定義は、以下の形式のオブジェクトの配列です。
``` ts
```ts
{
name?: string;
path: string;
@@ -217,7 +242,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
}
```
> **Warning**
> [!WARNING]
> 現状、ルートは定義された順に評価されます。
> たとえば、`/foo/:id`ルート定義の次に`/foo/bar`ルート定義がされていた場合、後者がマッチすることはありません。
@@ -279,7 +304,7 @@ export const Default = {
parameters: {
layout: 'centered',
},
} satisfies StoryObj<typeof MkAvatar>;
} satisfies StoryObj<typeof MyComponent>;
```
If you want to opt-out from the automatic generation, create a `MyComponent.stories.impl.ts` file and add the following line to the file.
@@ -390,7 +415,7 @@ describe('test', () => {
})
.useMocker(...
.compile();
fooService = app.get<FooService>(FooService);
barService = app.get<BarService>(BarService) as jest.Mocked<BarService>;
@@ -511,13 +536,13 @@ pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name>
- 作成されたスクリプトは不必要な変更を含むため除去してください
### JSON SchemaのobjectでanyOfを使うとき
JSON Schemaで、objectに対してanyOfを使う場合、anyOfの中でpropertiesを定義しないこと。
バリデーションが効かないため。SchemaTypeもそのように作られており、objectのanyOf内のpropertiesは捨てられます
JSON Schemaで、objectに対してanyOfを使う場合、anyOfの中でpropertiesを定義しないこと。
バリデーションが効かないため。SchemaTypeもそのように作られており、objectのanyOf内のpropertiesは捨てられます
https://github.com/misskey-dev/misskey/pull/10082
テキストhogeおよびfugaについて、片方を必須としつつ両方の指定もありうる場合:
```
```ts
export const paramDef = {
type: 'object',
properties: {

View File

@@ -26,6 +26,7 @@ COPY --link ["packages/sw/package.json", "./packages/sw/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"]
COPY --link ["packages/misskey-mahjong/package.json", "./packages/misskey-mahjong/"]
ARG NODE_ENV=production
@@ -56,6 +57,7 @@ COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"]
COPY --link ["packages/misskey-mahjong/package.json", "./packages/misskey-mahjong/"]
ARG NODE_ENV=production
@@ -91,10 +93,12 @@ COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/nod
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-reversi/node_modules ./packages/misskey-reversi/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-bubble-game/node_modules ./packages/misskey-bubble-game/node_modules
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-mahjong/node_modules ./packages/misskey-mahjong/node_modules
COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/built ./packages/misskey-js/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-mahjong/built ./packages/misskey-mahjong/built
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
COPY --chown=misskey:misskey . ./

290
locales/index.d.ts vendored
View File

@@ -5008,6 +5008,14 @@ export interface Locale extends ILocale {
* もう一度お試しください。
*/
"tryAgain": string;
/**
* センシティブなメディアを表示するとき確認する
*/
"confirmWhenRevealingSensitiveMedia": string;
/**
* センシティブなメディアです。表示しますか?
*/
"sensitiveMediaRevealConfirm": string;
"_delivery": {
/**
* 配信状態
@@ -10008,6 +10016,288 @@ export interface Locale extends ILocale {
*/
"useAvatarAsStone": string;
};
"_mahjong": {
/**
* 麻雀
*/
"mahjong": string;
/**
* ルームに参加
*/
"joinRoom": string;
/**
* ルームを作成
*/
"createRoom": string;
/**
* 準備完了
*/
"ready": string;
/**
* 準備を再開
*/
"cancelReady": string;
/**
* 退室
*/
"leave": string;
/**
* CPUを追加
*/
"addCpu": string;
/**
* 東
*/
"east": string;
/**
* 南
*/
"south": string;
/**
* 西
*/
"west": string;
/**
* 北
*/
"north": string;
/**
* ドラ
*/
"dora": string;
/**
* 赤ドラ
*/
"redDora": string;
/**
* 飜
*/
"fan": string;
"_fanNames": {
/**
* 満貫
*/
"mangan": string;
/**
* 跳満
*/
"haneman": string;
/**
* 倍満
*/
"baiman": string;
/**
* 三倍満
*/
"sanbaiman": string;
/**
* 役満
*/
"yakuman": string;
/**
* 数え役満
*/
"kazoeyakuman": string;
};
"_yakus": {
/**
* 立直
*/
"riichi": string;
/**
* 一発
*/
"ippatsu": string;
/**
* 門前清自摸和
*/
"tsumo": string;
/**
* 断么
*/
"tanyao": string;
/**
* 平和
*/
"pinfu": string;
/**
* 一盃口
*/
"iipeko": string;
/**
* 東
*/
"field-wind-e": string;
/**
* 南
*/
"field-wind-s": string;
/**
* 東
*/
"seat-wind-e": string;
/**
* 南
*/
"seat-wind-s": string;
/**
* 西
*/
"seat-wind-w": string;
/**
* 北
*/
"seat-wind-n": string;
/**
* 白
*/
"white": string;
/**
* 發
*/
"green": string;
/**
* 中
*/
"red": string;
/**
* 嶺上開花
*/
"rinshan": string;
/**
* 搶槓
*/
"chankan": string;
/**
* 海底摸月
*/
"haitei": string;
/**
* 河底撈魚
*/
"hotei": string;
/**
* 三色同順
*/
"sanshoku-dojun": string;
/**
* 三色同刻
*/
"sanshoku-doko": string;
/**
* 一気通貫
*/
"ittsu": string;
/**
* 混全帯么九
*/
"chanta": string;
/**
* 七対子
*/
"chitoitsu": string;
/**
* 対々
*/
"toitoi": string;
/**
* 三暗刻
*/
"sananko": string;
/**
* 混老頭
*/
"honroto": string;
/**
* 三槓子
*/
"sankantsu": string;
/**
* 小三元
*/
"shosangen": string;
/**
* ダブル立直
*/
"double-riichi": string;
/**
* 混一色
*/
"honitsu": string;
/**
* 清全帯么九
*/
"junchan": string;
/**
* ニ盃口
*/
"ryampeko": string;
/**
* 清一色
*/
"chinitsu": string;
/**
* 国士無双
*/
"kokushi": string;
/**
* 国士無双十三面待
*/
"kokushi-13": string;
/**
* 四暗刻
*/
"suanko": string;
/**
* 四暗刻単騎待
*/
"suanko-tanki": string;
/**
* 大三元
*/
"daisangen": string;
/**
* 字一色
*/
"tsuiso": string;
/**
* 小四喜
*/
"shosushi": string;
/**
* 大四喜
*/
"daisushi": string;
/**
* 緑一色
*/
"ryuiso": string;
/**
* 清老頭
*/
"chinroto": string;
/**
* 四槓子
*/
"sukantsu": string;
/**
* 九蓮宝燈
*/
"churen": string;
/**
* 九連宝灯九面待
*/
"churen-9": string;
/**
* 天和
*/
"tenho": string;
/**
* 地和
*/
"chiho": string;
};
};
"_offlineScreen": {
/**
* オフライン - サーバーに接続できません

View File

@@ -1248,6 +1248,8 @@ noDescription: "説明文はありません"
alwaysConfirmFollow: "フォローの際常に確認する"
inquiry: "お問い合わせ"
tryAgain: "もう一度お試しください。"
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
_delivery:
status: "配信状態"
@@ -2666,6 +2668,79 @@ _reversi:
showBoardLabels: "盤面に行・列番号を表示"
useAvatarAsStone: "石をアイコンにする"
_mahjong:
mahjong: "麻雀"
joinRoom: "ルームに参加"
createRoom: "ルームを作成"
ready: "準備完了"
cancelReady: "準備を再開"
leave: "退室"
addCpu: "CPUを追加"
east: "東"
south: "南"
west: "西"
north: "北"
dora: "ドラ"
redDora: "赤ドラ"
fan: "飜"
_fanNames:
mangan: "満貫"
haneman: "跳満"
baiman: "倍満"
sanbaiman: "三倍満"
yakuman: "役満"
kazoeyakuman: "数え役満"
_yakus:
"riichi": "立直"
"ippatsu": "一発"
"tsumo": "門前清自摸和"
"tanyao": "断么"
"pinfu": "平和"
"iipeko": "一盃口"
"field-wind-e": "東"
"field-wind-s": "南"
"seat-wind-e": "東"
"seat-wind-s": "南"
"seat-wind-w": "西"
"seat-wind-n": "北"
"white": "白"
"green": "發"
"red": "中"
"rinshan": "嶺上開花"
"chankan": "搶槓"
"haitei": "海底摸月"
"hotei": "河底撈魚"
"sanshoku-dojun": "三色同順"
"sanshoku-doko": "三色同刻"
"ittsu": "一気通貫"
"chanta": "混全帯么九"
"chitoitsu": "七対子"
"toitoi": "対々"
"sananko": "三暗刻"
"honroto": "混老頭"
"sankantsu": "三槓子"
"shosangen": "小三元"
"double-riichi": "ダブル立直"
"honitsu": "混一色"
"junchan": "清全帯么九"
"ryampeko": "ニ盃口"
"chinitsu": "清一色"
"kokushi": "国士無双"
"kokushi-13": "国士無双十三面待"
"suanko": "四暗刻"
"suanko-tanki": "四暗刻単騎待"
"daisangen": "大三元"
"tsuiso": "字一色"
"shosushi": "小四喜"
"daisushi": "大四喜"
"ryuiso": "緑一色"
"chinroto": "清老頭"
"sukantsu": "四槓子"
"churen": "九蓮宝燈"
"churen-9": "九連宝灯九面待"
"tenho": "天和"
"chiho": "地和"
_offlineScreen:
title: "オフライン - サーバーに接続できません"
header: "サーバーに接続できません"

Submodule misskey-assets deleted from 0179793ec8

View File

@@ -1,19 +1,20 @@
{
"name": "misskey",
"version": "2024.7.0-beta.0",
"version": "2024.7.0-beta.3",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@9.0.6",
"packageManager": "pnpm@9.6.0",
"workspaces": [
"packages/frontend",
"packages/backend",
"packages/sw",
"packages/misskey-js",
"packages/misskey-reversi",
"packages/misskey-bubble-game"
"packages/misskey-bubble-game",
"packages/misskey-mahjong"
],
"private": true,
"scripts": {
@@ -21,7 +22,7 @@
"build-assets": "node ./scripts/build-assets.mjs",
"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 && 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",
"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 && node ./built/boot/entry.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
"init": "pnpm migrate",
@@ -51,24 +52,24 @@
"cssnano": "6.1.2",
"execa": "8.0.1",
"fast-glob": "3.3.2",
"ignore-walk": "6.0.4",
"ignore-walk": "6.0.5",
"js-yaml": "4.1.0",
"postcss": "8.4.38",
"postcss": "8.4.40",
"tar": "6.2.1",
"terser": "5.31.1",
"typescript": "5.5.3",
"esbuild": "0.22.0",
"glob": "10.3.12"
"terser": "5.31.3",
"typescript": "5.5.4",
"esbuild": "0.23.0",
"glob": "11.0.0"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "2.0.2",
"@types/node": "20.14.9",
"@typescript-eslint/eslint-plugin": "7.15.0",
"@typescript-eslint/parser": "7.15.0",
"@types/node": "20.14.12",
"@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0",
"cross-env": "7.0.3",
"cypress": "13.13.0",
"eslint": "9.6.0",
"globals": "15.7.0",
"cypress": "13.13.1",
"eslint": "9.8.0",
"globals": "15.8.0",
"ncp": "2.0.0",
"start-server-and-test": "2.0.4"
},

View File

@@ -4,7 +4,7 @@ import sharedConfig from '../shared/eslint.config.js';
export default [
...sharedConfig,
{
ignores: ['**/node_modules', 'built', '@types/**/*'],
ignores: ['**/node_modules', 'built', '@types/**/*', 'migration'],
},
{
files: ['**/*.ts', '**/*.tsx'],

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Mahjong1706234054207 {
name = 'Mahjong1706234054207'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "mahjong_game" ("id" character varying(32) NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "endedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32), "user2Id" character varying(32), "user3Id" character varying(32), "user4Id" character varying(32), "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "timeLimitForEachTurn" smallint NOT NULL DEFAULT '90', "logs" jsonb NOT NULL DEFAULT '[]', CONSTRAINT "PK_77db54c0a9785d387e3fbbdd2f0" PRIMARY KEY ("id"))`);
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_b98c78761a845b69e6540401264" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_f17b0ba519ae28f188a7915ad6f" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_64314ffd3cb59475b0d06330058" FOREIGN KEY ("user3Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_58a75f1ea2a810ae3986f72a0e3" FOREIGN KEY ("user4Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_58a75f1ea2a810ae3986f72a0e3"`);
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_64314ffd3cb59475b0d06330058"`);
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_f17b0ba519ae28f188a7915ad6f"`);
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_b98c78761a845b69e6540401264"`);
await queryRunner.query(`DROP TABLE "mahjong_game"`);
}
}

View File

@@ -31,7 +31,7 @@
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"test-and-coverage": "pnpm jest-and-coverage",
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
"generate-api-json": "node ./scripts/generate_api_json.js"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
@@ -65,11 +65,11 @@
"utf-8-validate": "6.0.3"
},
"dependencies": {
"@aws-sdk/client-s3": "3.600.0",
"@aws-sdk/lib-storage": "3.600.0",
"@bull-board/api": "5.20.5",
"@bull-board/fastify": "5.20.5",
"@bull-board/ui": "5.20.5",
"@aws-sdk/client-s3": "3.620.0",
"@aws-sdk/lib-storage": "3.620.0",
"@bull-board/api": "5.21.1",
"@bull-board/fastify": "5.21.1",
"@bull-board/ui": "5.21.1",
"@discordapp/twemoji": "15.0.3",
"@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.3.1",
@@ -86,22 +86,22 @@
"@nestjs/core": "10.3.10",
"@nestjs/testing": "10.3.10",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "8.13.0",
"@sentry/profiling-node": "8.13.0",
"@simplewebauthn/server": "10.0.0",
"@sentry/node": "8.20.0",
"@sentry/profiling-node": "8.20.0",
"@simplewebauthn/server": "10.0.1",
"@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.5.0",
"@swc/cli": "0.3.12",
"@swc/core": "1.6.6",
"@twemoji/parser": "15.1.1",
"accepts": "1.3.8",
"ajv": "8.16.0",
"ajv": "8.17.1",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
"bullmq": "5.8.3",
"bullmq": "5.10.4",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.2",
"chalk": "5.3.0",
@@ -115,10 +115,10 @@
"fastify": "4.28.1",
"fastify-raw-body": "4.3.0",
"feed": "4.2.2",
"file-type": "19.0.0",
"file-type": "19.3.0",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.0",
"got": "14.4.1",
"got": "14.4.2",
"happy-dom": "10.0.3",
"hpagent": "1.2.0",
"htmlescape": "1.1.1",
@@ -128,7 +128,7 @@
"ipaddr.js": "2.2.0",
"is-svg": "5.0.1",
"js-yaml": "4.1.0",
"jsdom": "24.1.0",
"jsdom": "24.1.1",
"json5": "2.2.3",
"jsonld": "8.3.2",
"jsrsasign": "11.1.0",
@@ -138,6 +138,7 @@
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"misskey-mahjong": "workspace:*",
"ms": "3.0.0-canary.1",
"nanoid": "5.0.7",
"nested-property": "4.0.0",
@@ -177,11 +178,11 @@
"tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0",
"typeorm": "0.3.20",
"typescript": "5.5.3",
"typescript": "5.5.4",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.7",
"ws": "8.17.1",
"ws": "8.18.0",
"xev": "3.0.2"
},
"devDependencies": {
@@ -201,11 +202,11 @@
"@types/jest": "29.5.12",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7",
"@types/jsonld": "1.5.14",
"@types/jsonld": "1.5.15",
"@types/jsrsasign": "10.5.14",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
"@types/node": "20.14.9",
"@types/node": "20.14.12",
"@types/nodemailer": "6.4.15",
"@types/oauth": "0.9.5",
"@types/oauth2orize": "1.11.5",
@@ -225,18 +226,18 @@
"@types/tmp": "0.2.6",
"@types/vary": "1.1.3",
"@types/web-push": "3.6.3",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "7.15.0",
"@typescript-eslint/parser": "7.15.0",
"@types/ws": "8.5.11",
"@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0",
"aws-sdk-client-mock": "4.0.1",
"cross-env": "7.0.3",
"eslint-plugin-import": "2.29.1",
"execa": "9.2.0",
"execa": "9.3.0",
"fkill": "9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",
"nodemon": "3.1.4",
"pid-port": "1.0.0",
"simple-oauth2": "5.0.1"
"simple-oauth2": "5.1.0"
}
}

View File

@@ -3,11 +3,34 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { loadConfig } from '../built/config.js'
import { genOpenapiSpec } from '../built/server/api/openapi/gen-spec.js'
import { writeFileSync } from "node:fs";
import { execa } from 'execa';
import { writeFileSync, existsSync } from "node:fs";
const config = loadConfig();
const spec = genOpenapiSpec(config, true);
async function main() {
if (!process.argv.includes('--no-build')) {
await execa('pnpm', ['run', 'build'], {
stdout: process.stdout,
stderr: process.stderr,
});
}
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
if (!existsSync('./built')) {
throw new Error('`built` directory does not exist.');
}
/** @type {import('../src/config.js')} */
const { loadConfig } = await import('../built/config.js');
/** @type {import('../src/server/api/openapi/gen-spec.js')} */
const { genOpenapiSpec } = await import('../built/server/api/openapi/gen-spec.js');
const config = loadConfig();
const spec = genOpenapiSpec(config, true);
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
}
main().catch(e => {
console.error(e);
process.exit(1);
});

View File

@@ -61,6 +61,7 @@ import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairService } from './UserKeypairService.js';
import { UserListService } from './UserListService.js';
import { UserMutingService } from './UserMutingService.js';
import { UserRenoteMutingService } from './UserRenoteMutingService.js';
import { UserSuspendService } from './UserSuspendService.js';
import { UserAuthService } from './UserAuthService.js';
import { VideoProcessingService } from './VideoProcessingService.js';
@@ -75,6 +76,7 @@ import { FanoutTimelineService } from './FanoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
import { RegistryApiService } from './RegistryApiService.js';
import { ReversiService } from './ReversiService.js';
import { MahjongService } from './MahjongService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
@@ -203,6 +205,7 @@ const $UserFollowingService: Provider = { provide: 'UserFollowingService', useEx
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService', useExisting: UserRenoteMutingService };
const $UserSearchService: Provider = { provide: 'UserSearchService', useExisting: UserSearchService };
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
@@ -219,6 +222,7 @@ const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpo
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
const $MahjongService: Provider = { provide: 'MahjongService', useExisting: MahjongService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -350,6 +354,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserKeypairService,
UserListService,
UserMutingService,
UserRenoteMutingService,
UserSearchService,
UserSuspendService,
UserAuthService,
@@ -366,6 +371,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ChannelFollowingService,
RegistryApiService,
ReversiService,
MahjongService,
ChartLoggerService,
FederationChart,
@@ -493,6 +499,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserKeypairService,
$UserListService,
$UserMutingService,
$UserRenoteMutingService,
$UserSearchService,
$UserSuspendService,
$UserAuthService,
@@ -509,6 +516,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ChannelFollowingService,
$RegistryApiService,
$ReversiService,
$MahjongService,
$ChartLoggerService,
$FederationChart,
@@ -637,6 +645,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserKeypairService,
UserListService,
UserMutingService,
UserRenoteMutingService,
UserSearchService,
UserSuspendService,
UserAuthService,
@@ -653,6 +662,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ChannelFollowingService,
RegistryApiService,
ReversiService,
MahjongService,
FederationChart,
NotesChart,
@@ -779,6 +789,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserKeypairService,
$UserListService,
$UserMutingService,
$UserRenoteMutingService,
$UserSearchService,
$UserSuspendService,
$UserAuthService,
@@ -795,6 +806,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ChannelFollowingService,
$RegistryApiService,
$ReversiService,
$MahjongService,
$FederationChart,
$NotesChart,

View File

@@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import * as Reversi from 'misskey-reversi';
import * as Mmj from 'misskey-mahjong';
import type { MiChannel } from '@/models/Channel.js';
import type { MiUser } from '@/models/User.js';
import type { MiUserProfile } from '@/models/UserProfile.js';
@@ -194,6 +195,52 @@ export interface ReversiGameEventTypes {
userId: MiUser['id'];
};
}
export interface MahjongRoomEventTypes {
joined: {
index: number;
user: Packed<'UserLite'>;
};
changeReadyStates: {
user1: boolean;
user2: boolean;
user3: boolean;
user4: boolean;
};
started: {
room: Packed<'MahjongRoomDetailed'>;
};
tsumo: {
house: Mmj.House;
tile: Mmj.Tile;
};
dahai: {
house: Mmj.House;
tile: Mmj.Tile;
riichi: boolean;
};
dahaiAndTsumo: {
dahaiHouse: Mmj.House;
dahaiTile: Mmj.Tile;
tsumoTile: Mmj.Tile;
riichi: boolean;
};
ponned: {
caller: Mmj.House;
callee: Mmj.House;
tile: Mmj.Tile;
};
kanned: {
caller: Mmj.House;
callee: Mmj.House;
tile: Mmj.Tile;
rinsyan: Mmj.Tile;
};
ronned: {
};
tsumoHora: {
};
}
//#endregion
// 辞書(interface or type)から{ type, body }ユニオンを定義
@@ -209,6 +256,10 @@ type SerializedAll<T> = {
[K in keyof T]: Serialized<T[K]>;
};
type UndefinedAsNullAll<T> = {
[K in keyof T]: T[K] extends undefined ? null : T[K];
}
export interface InternalEventTypes {
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
@@ -247,43 +298,45 @@ export interface InternalEventTypes {
userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
}
type EventTypesToEventPayload<T> = EventUnionFromDictionary<UndefinedAsNullAll<SerializedAll<T>>>;
// name/messages(spec) pairs dictionary
export type GlobalEvents = {
internal: {
name: 'internal';
payload: EventUnionFromDictionary<SerializedAll<InternalEventTypes>>;
payload: EventTypesToEventPayload<InternalEventTypes>;
};
broadcast: {
name: 'broadcast';
payload: EventUnionFromDictionary<SerializedAll<BroadcastTypes>>;
payload: EventTypesToEventPayload<BroadcastTypes>;
};
main: {
name: `mainStream:${MiUser['id']}`;
payload: EventUnionFromDictionary<SerializedAll<MainEventTypes>>;
payload: EventTypesToEventPayload<MainEventTypes>;
};
drive: {
name: `driveStream:${MiUser['id']}`;
payload: EventUnionFromDictionary<SerializedAll<DriveEventTypes>>;
payload: EventTypesToEventPayload<DriveEventTypes>;
};
note: {
name: `noteStream:${MiNote['id']}`;
payload: EventUnionFromDictionary<SerializedAll<NoteStreamEventTypes>>;
payload: EventTypesToEventPayload<NoteStreamEventTypes>;
};
userList: {
name: `userListStream:${MiUserList['id']}`;
payload: EventUnionFromDictionary<SerializedAll<UserListEventTypes>>;
payload: EventTypesToEventPayload<UserListEventTypes>;
};
roleTimeline: {
name: `roleTimelineStream:${MiRole['id']}`;
payload: EventUnionFromDictionary<SerializedAll<RoleTimelineEventTypes>>;
payload: EventTypesToEventPayload<RoleTimelineEventTypes>;
};
antenna: {
name: `antennaStream:${MiAntenna['id']}`;
payload: EventUnionFromDictionary<SerializedAll<AntennaEventTypes>>;
payload: EventTypesToEventPayload<AntennaEventTypes>;
};
admin: {
name: `adminStream:${MiUser['id']}`;
payload: EventUnionFromDictionary<SerializedAll<AdminEventTypes>>;
payload: EventTypesToEventPayload<AdminEventTypes>;
};
notes: {
name: 'notesStream';
@@ -291,11 +344,15 @@ export type GlobalEvents = {
};
reversi: {
name: `reversiStream:${MiUser['id']}`;
payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
payload: EventTypesToEventPayload<ReversiEventTypes>;
};
reversiGame: {
name: `reversiGameStream:${MiReversiGame['id']}`;
payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
payload: EventTypesToEventPayload<ReversiGameEventTypes>;
};
mahjongRoom: {
name: `mahjongRoomStream:${string}`;
payload: EventUnionFromDictionary<SerializedAll<MahjongRoomEventTypes>>;
};
};
@@ -396,4 +453,9 @@ export class GlobalEventService {
public publishReversiGameStream<K extends keyof ReversiGameEventTypes>(gameId: MiReversiGame['id'], type: K, value?: ReversiGameEventTypes[K]): void {
this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishMahjongRoomStream<K extends keyof MahjongRoomEventTypes>(roomId: string, type: K, value?: MahjongRoomEventTypes[K]): void {
this.publish(`mahjongRoomStream:${roomId}`, type, typeof value === 'undefined' ? null : value);
}
}

View File

@@ -0,0 +1,734 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { ModuleRef } from '@nestjs/core';
import { IsNull, LessThan, MoreThan } from 'typeorm';
import * as Mmj from 'misskey-mahjong';
import type {
MiMahjongGame,
MahjongGamesRepository,
} from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { Serialized } from '@/types.js';
import { Packed } from '@/misc/json-schema.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
const CALL_AND_RON_ASKING_TIMEOUT_MS = 1000 * 10; // 10sec
const TURN_TIMEOUT_MS = 1000 * 30; // 30sec
const NEXT_KYOKU_CONFIRMATION_TIMEOUT_MS = 1000 * 15; // 15sec
type Room = {
id: string;
user1Id: MiUser['id'];
user2Id: MiUser['id'] | null;
user3Id: MiUser['id'] | null;
user4Id: MiUser['id'] | null;
user1: Packed<'UserLite'> | null;
user2: Packed<'UserLite'> | null;
user3: Packed<'UserLite'> | null;
user4: Packed<'UserLite'> | null;
user1Ai?: boolean;
user2Ai?: boolean;
user3Ai?: boolean;
user4Ai?: boolean;
user1Ready: boolean;
user2Ready: boolean;
user3Ready: boolean;
user4Ready: boolean;
user1Offline?: boolean;
user2Offline?: boolean;
user3Offline?: boolean;
user4Offline?: boolean;
isStarted?: boolean;
timeLimitForEachTurn: number;
gameState?: Mmj.MasterState;
};
type CallingAnswers = {
pon: null | boolean;
cii: null | false | 'x__' | '_x_' | '__x';
kan: null | boolean;
ron: {
e: null | boolean;
s: null | boolean;
w: null | boolean;
n: null | boolean;
};
};
type NextKyokuConfirmation = {
user1: boolean;
user2: boolean;
user3: boolean;
user4: boolean;
};
function getUserIdOfHouse(room: Room, mj: Mmj.MasterGameEngine, house: Mmj.House): MiUser['id'] {
return mj.user1House === house ? room.user1Id : mj.user2House === house ? room.user2Id : mj.user3House === house ? room.user3Id : room.user4Id;
}
function getHouseOfUserId(room: Room, mj: Mmj.MasterGameEngine, userId: MiUser['id']): Mmj.House {
return userId === room.user1Id ? mj.user1House : userId === room.user2Id ? mj.user2House : userId === room.user3Id ? mj.user3House : mj.user4House;
}
@Injectable()
export class MahjongService implements OnApplicationShutdown, OnModuleInit {
private notificationService: NotificationService;
constructor(
private moduleRef: ModuleRef,
@Inject(DI.redis)
private redisClient: Redis.Redis,
//@Inject(DI.mahjongGamesRepository)
//private mahjongGamesRepository: MahjongGamesRepository,
private cacheService: CacheService,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService,
private reversiGameEntityService: ReversiGameEntityService,
private idService: IdService,
) {
}
async onModuleInit() {
this.notificationService = this.moduleRef.get(NotificationService.name);
}
@bindThis
private async saveRoom(room: Room) {
await this.redisClient.set(`mahjong:room:${room.id}`, JSON.stringify(room), 'EX', 60 * 30);
}
@bindThis
public async createRoom(user: MiUser): Promise<Room> {
const room: Room = {
id: this.idService.gen(),
user1Id: user.id,
user2Id: null,
user3Id: null,
user4Id: null,
user1: await this.userEntityService.pack(user),
user1Ready: false,
user2Ready: false,
user3Ready: false,
user4Ready: false,
timeLimitForEachTurn: 30,
};
await this.saveRoom(room);
return room;
}
@bindThis
public async getRoom(id: Room['id']): Promise<Room | null> {
const room = await this.redisClient.get(`mahjong:room:${id}`);
if (!room) return null;
const parsed = JSON.parse(room);
return {
...parsed,
};
}
@bindThis
public async joinRoom(roomId: Room['id'], user: MiUser): Promise<Room | null> {
const room = await this.getRoom(roomId);
if (!room) return null;
if (room.user1Id === user.id) return room;
if (room.user2Id === user.id) return room;
if (room.user3Id === user.id) return room;
if (room.user4Id === user.id) return room;
if (room.user2Id === null) {
room.user2Id = user.id;
room.user2 = await this.userEntityService.pack(user);
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 2, user: room.user2 });
return room;
}
if (room.user3Id === null) {
room.user3Id = user.id;
room.user3 = await this.userEntityService.pack(user);
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 3, user: room.user3 });
return room;
}
if (room.user4Id === null) {
room.user4Id = user.id;
room.user4 = await this.userEntityService.pack(user);
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 4, user: room.user4 });
return room;
}
return null;
}
@bindThis
public async addAi(roomId: Room['id'], user: MiUser): Promise<Room | null> {
const room = await this.getRoom(roomId);
if (!room) return null;
if (room.user1Id !== user.id) throw new Error('access denied');
if (room.user2Id == null && !room.user2Ai) {
room.user2Ai = true;
room.user2Ready = true;
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 2, user: null });
return room;
}
if (room.user3Id == null && !room.user3Ai) {
room.user3Ai = true;
room.user3Ready = true;
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 3, user: null });
return room;
}
if (room.user4Id == null && !room.user4Ai) {
room.user4Ai = true;
room.user4Ready = true;
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 4, user: null });
return room;
}
return null;
}
@bindThis
public async leaveRoom(roomId: Room['id'], user: MiUser): Promise<Room | null> {
const room = await this.getRoom(roomId);
if (!room) return null;
if (room.user1Id === user.id) {
room.user1Id = null;
room.user1 = null;
await this.saveRoom(room);
return room;
}
if (room.user2Id === user.id) {
room.user2Id = null;
room.user2 = null;
await this.saveRoom(room);
return room;
}
if (room.user3Id === user.id) {
room.user3Id = null;
room.user3 = null;
await this.saveRoom(room);
return room;
}
if (room.user4Id === user.id) {
room.user4Id = null;
room.user4 = null;
await this.saveRoom(room);
return room;
}
return null;
}
@bindThis
public async changeReadyState(roomId: Room['id'], user: MiUser, ready: boolean): Promise<void> {
const room = await this.getRoom(roomId);
if (!room) return;
if (room.user1Id === user.id) {
room.user1Ready = ready;
await this.saveRoom(room);
}
if (room.user2Id === user.id) {
room.user2Ready = ready;
await this.saveRoom(room);
}
if (room.user3Id === user.id) {
room.user3Ready = ready;
await this.saveRoom(room);
}
if (room.user4Id === user.id) {
room.user4Ready = ready;
await this.saveRoom(room);
}
this.globalEventService.publishMahjongRoomStream(room.id, 'changeReadyStates', {
user1: room.user1Ready,
user2: room.user2Ready,
user3: room.user3Ready,
user4: room.user4Ready,
});
if (room.user1Ready && room.user2Ready && room.user3Ready && room.user4Ready) {
await this.startGame(room);
}
}
@bindThis
public async startGame(room: Room) {
if (!room.user1Ready || !room.user2Ready || !room.user3Ready || !room.user4Ready) {
throw new Error('Not ready');
}
room.gameState = Mmj.MasterGameEngine.createInitialState();
room.isStarted = true;
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'started', { room: room });
this.kyokuStarted(room);
}
@bindThis
private kyokuStarted(room: Room) {
const mj = new Mmj.MasterGameEngine(room.gameState);
this.waitForTurn(room, mj.turn, mj);
}
@bindThis
private async answer(room: Room, mj: Mmj.MasterGameEngine, answers: CallingAnswers) {
const res = mj.commit_resolveCallingInterruption({
pon: answers.pon ?? false,
cii: answers.cii ?? false,
kan: answers.kan ?? false,
ron: [...(answers.ron.e ? ['e'] : []), ...(answers.ron.s ? ['s'] : []), ...(answers.ron.w ? ['w'] : []), ...(answers.ron.n ? ['n'] : [])] as Mmj.House[],
});
room.gameState = mj.getState();
await this.saveRoom(room);
switch (res.type) {
case 'tsumo':
this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile });
this.waitForTurn(room, res.turn, mj);
break;
case 'ponned':
this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { caller: res.caller, callee: res.callee, tiles: res.tiles });
this.waitForTurn(room, res.turn, mj);
break;
case 'kanned':
this.globalEventService.publishMahjongRoomStream(room.id, 'kanned', { caller: res.caller, callee: res.callee, tiles: res.tiles, rinsyan: res.rinsyan });
this.waitForTurn(room, res.turn, mj);
break;
case 'ciied':
this.globalEventService.publishMahjongRoomStream(room.id, 'ciied', { caller: res.caller, callee: res.callee, tiles: res.tiles });
this.waitForTurn(room, res.turn, mj);
break;
case 'ronned':
this.globalEventService.publishMahjongRoomStream(room.id, 'ronned', {
callers: res.callers,
callee: res.callee,
handTiles: {
e: mj.handTiles.e,
s: mj.handTiles.s,
w: mj.handTiles.w,
n: mj.handTiles.n,
},
});
this.endKyoku(room, mj);
break;
case 'ryuukyoku':
this.globalEventService.publishMahjongRoomStream(room.id, 'ryuukyoku', {
});
this.endKyoku(room, mj);
break;
}
}
@bindThis
private async endKyoku(room: Room, mj: Mmj.MasterGameEngine) {
const confirmation: NextKyokuConfirmation = {
user1: false,
user2: false,
user3: false,
user4: false,
};
this.redisClient.set(`mahjong:gameNextKyokuConfirmation:${room.id}`, JSON.stringify(confirmation));
const waitingStartedAt = Date.now();
const interval = setInterval(async () => {
const confirmationRaw = await this.redisClient.get(`mahjong:gameNextKyokuConfirmation:${room.id}`);
if (confirmationRaw == null) {
clearInterval(interval);
return;
}
const confirmation = JSON.parse(confirmationRaw) as NextKyokuConfirmation;
const allConfirmed = confirmation.user1 && confirmation.user2 && confirmation.user3 && confirmation.user4;
if (allConfirmed || (Date.now() - waitingStartedAt > NEXT_KYOKU_CONFIRMATION_TIMEOUT_MS)) {
await this.redisClient.del(`mahjong:gameNextKyokuConfirmation:${room.id}`);
clearInterval(interval);
this.nextKyoku(room, mj);
}
}, 2000);
}
@bindThis
private async nextKyoku(room: Room, mj: Mmj.MasterGameEngine) {
const res = mj.commit_nextKyoku();
room.gameState = mj.getState();
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'nextKyoku', {
room: room,
});
this.kyokuStarted(room);
}
@bindThis
private async dahai(room: Room, mj: Mmj.MasterGameEngine, house: Mmj.House, tile: Mmj.TileId, riichi = false) {
const res = mj.commit_dahai(house, tile, riichi);
room.gameState = mj.getState();
await this.saveRoom(room);
const aiHouses = [[1, room.user1Ai], [2, room.user2Ai], [3, room.user3Ai], [4, room.user4Ai]].filter(([id, ai]) => ai).map(([id, ai]) => mj.getHouse(id));
if (res.ryuukyoku) {
this.endKyoku(room, mj);
this.globalEventService.publishMahjongRoomStream(room.id, 'ryuukyoku', {
});
} else if (res.asking) {
const answers: CallingAnswers = {
pon: null,
cii: null,
kan: null,
ron: {
e: null,
s: null,
w: null,
n: null,
},
};
// リーチ中はポン、チー、カンできない
if (res.canPonHouse != null && mj.riichis[res.canPonHouse]) {
answers.pon = false;
}
if (res.canCiiHouse != null && mj.riichis[res.canCiiHouse]) {
answers.cii = false;
}
if (res.canKanHouse != null && mj.riichis[res.canKanHouse]) {
answers.kan = false;
}
if (aiHouses.includes(res.canPonHouse)) {
// TODO: ちゃんと思考するようにする
answers.pon = Math.random() < 0.25;
}
if (aiHouses.includes(res.canCiiHouse)) {
// TODO: ちゃんと思考するようにする
//answers.cii = Math.random() < 0.25;
answers.cii = false;
}
if (aiHouses.includes(res.canKanHouse)) {
// TODO: ちゃんと思考するようにする
answers.kan = Math.random() < 0.25;
}
for (const h of res.canRonHouses) {
if (aiHouses.includes(h)) {
// TODO: ちゃんと思考するようにする
}
}
this.redisClient.set(`mahjong:gameCallingAsking:${room.id}`, JSON.stringify(answers));
const waitingStartedAt = Date.now();
const interval = setInterval(async () => {
const current = await this.redisClient.get(`mahjong:gameCallingAsking:${room.id}`);
if (current == null) throw new Error('arienai (gameCallingAsking)');
const currentAnswers = JSON.parse(current) as CallingAnswers;
const allAnswered = !(
(res.canPonHouse != null && currentAnswers.pon == null) ||
(res.canCiiHouse != null && currentAnswers.cii == null) ||
(res.canKanHouse != null && currentAnswers.kan == null) ||
(res.canRonHouses.includes('e') && currentAnswers.ron.e == null) ||
(res.canRonHouses.includes('s') && currentAnswers.ron.s == null) ||
(res.canRonHouses.includes('w') && currentAnswers.ron.w == null) ||
(res.canRonHouses.includes('n') && currentAnswers.ron.n == null)
);
if (allAnswered || (Date.now() - waitingStartedAt > CALL_AND_RON_ASKING_TIMEOUT_MS)) {
console.log(allAnswered ? 'ask all answerd' : 'ask timeout');
await this.redisClient.del(`mahjong:gameCallingAsking:${room.id}`);
clearInterval(interval);
this.answer(room, mj, currentAnswers);
return;
}
}, 1000);
this.globalEventService.publishMahjongRoomStream(room.id, 'dahai', { house: house, tile, riichi });
} else {
this.globalEventService.publishMahjongRoomStream(room.id, 'dahaiAndTsumo', { dahaiHouse: house, dahaiTile: tile, tsumoTile: res.tsumoTile, riichi });
this.waitForTurn(room, res.next, mj);
}
}
@bindThis
public async confirmNextKyoku(roomId: Room['id'], user: MiUser) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
// TODO: この辺の処理はアトミックに行いたいけどJSONサポートはRedis Stackが必要
const confirmationRaw = await this.redisClient.get(`mahjong:gameNextKyokuConfirmation:${room.id}`);
if (confirmationRaw == null) return;
const confirmation = JSON.parse(confirmationRaw) as NextKyokuConfirmation;
if (user.id === room.user1Id) confirmation.user1 = true;
if (user.id === room.user2Id) confirmation.user2 = true;
if (user.id === room.user3Id) confirmation.user3 = true;
if (user.id === room.user4Id) confirmation.user4 = true;
await this.redisClient.set(`mahjong:gameNextKyokuConfirmation:${room.id}`, JSON.stringify(confirmation));
}
@bindThis
public async commit_dahai(roomId: MiMahjongGame['id'], user: MiUser, tile: Mmj.TileId, riichi = false) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
const mj = new Mmj.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, mj, user.id);
await this.clearTurnWaitingTimer(room.id);
await this.dahai(room, mj, myHouse, tile, riichi);
}
@bindThis
public async commit_ankan(roomId: MiMahjongGame['id'], user: MiUser, tile: Mmj.TileId) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
const mj = new Mmj.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, mj, user.id);
await this.clearTurnWaitingTimer(room.id);
const res = mj.commit_ankan(myHouse, tile);
room.gameState = mj.getState();
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'ankanned', { house: myHouse, tiles: res.tiles, rinsyan: res.rinsyan });
this.waitForTurn(room, myHouse, mj);
}
@bindThis
public async commit_kakan(roomId: MiMahjongGame['id'], user: MiUser, tile: Mmj.TileId) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
const mj = new Mmj.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, mj, user.id);
await this.clearTurnWaitingTimer(room.id);
const res = mj.commit_kakan(myHouse, tile);
room.gameState = mj.getState();
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'kakanned', { house: myHouse, tiles: res.tiles, rinsyan: res.rinsyan, from: res.from });
}
@bindThis
public async commit_tsumoHora(roomId: MiMahjongGame['id'], user: MiUser) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
const mj = new Mmj.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, mj, user.id);
await this.clearTurnWaitingTimer(room.id);
const res = mj.commit_tsumoHora(myHouse);
room.gameState = mj.getState();
await this.saveRoom(room);
this.globalEventService.publishMahjongRoomStream(room.id, 'tsumoHora', { house: myHouse, handTiles: res.handTiles, tsumoTile: res.tsumoTile });
this.endKyoku(room, mj);
}
@bindThis
public async commit_ronHora(roomId: MiMahjongGame['id'], user: MiUser) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
const mj = new Mmj.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, mj, user.id);
// TODO: 自分に回答する権利がある状態かバリデーション
// TODO: この辺の処理はアトミックに行いたいけどJSONサポートはRedis Stackが必要
const current = await this.redisClient.get(`mahjong:gameCallingAsking:${room.id}`);
if (current == null) throw new Error('no asking found');
const currentAnswers = JSON.parse(current) as CallingAnswers;
currentAnswers.ron[myHouse] = true;
await this.redisClient.set(`mahjong:gameCallingAsking:${room.id}`, JSON.stringify(currentAnswers));
}
@bindThis
public async commit_pon(roomId: MiMahjongGame['id'], user: MiUser) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
// TODO: 自分に回答する権利がある状態かバリデーション
// TODO: この辺の処理はアトミックに行いたいけどJSONサポートはRedis Stackが必要
const current = await this.redisClient.get(`mahjong:gameCallingAsking:${room.id}`);
if (current == null) throw new Error('no asking found');
const currentAnswers = JSON.parse(current) as CallingAnswers;
currentAnswers.pon = true;
await this.redisClient.set(`mahjong:gameCallingAsking:${room.id}`, JSON.stringify(currentAnswers));
}
@bindThis
public async commit_kan(roomId: MiMahjongGame['id'], user: MiUser) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
// TODO: 自分に回答する権利がある状態かバリデーション
// TODO: この辺の処理はアトミックに行いたいけどJSONサポートはRedis Stackが必要
const current = await this.redisClient.get(`mahjong:gameCallingAsking:${room.id}`);
if (current == null) throw new Error('no asking found');
const currentAnswers = JSON.parse(current) as CallingAnswers;
currentAnswers.kan = true;
await this.redisClient.set(`mahjong:gameCallingAsking:${room.id}`, JSON.stringify(currentAnswers));
}
@bindThis
public async commit_cii(roomId: MiMahjongGame['id'], user: MiUser, pattern: 'x__' | '_x_' | '__x') {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
// TODO: 自分に回答する権利がある状態かバリデーション
// TODO: この辺の処理はアトミックに行いたいけどJSONサポートはRedis Stackが必要
const current = await this.redisClient.get(`mahjong:gameCallingAsking:${room.id}`);
if (current == null) throw new Error('no asking found');
const currentAnswers = JSON.parse(current) as CallingAnswers;
currentAnswers.cii = pattern;
await this.redisClient.set(`mahjong:gameCallingAsking:${room.id}`, JSON.stringify(currentAnswers));
}
@bindThis
public async commit_nop(roomId: MiMahjongGame['id'], user: MiUser) {
const room = await this.getRoom(roomId);
if (room == null) return;
if (room.gameState == null) return;
const mj = new Mmj.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, mj, user.id);
// TODO: この辺の処理はアトミックに行いたいけどJSONサポートはRedis Stackが必要
const current = await this.redisClient.get(`mahjong:gameCallingAsking:${room.id}`);
if (current == null) throw new Error('no asking found');
const currentAnswers = JSON.parse(current) as CallingAnswers;
if (mj.askings.pon?.caller === myHouse) currentAnswers.pon = false;
if (mj.askings.cii?.caller === myHouse) currentAnswers.cii = false;
if (mj.askings.kan?.caller === myHouse) currentAnswers.kan = false;
if (mj.askings.ron != null && mj.askings.ron.callers.includes(myHouse)) currentAnswers.ron[myHouse] = false;
await this.redisClient.set(`mahjong:gameCallingAsking:${room.id}`, JSON.stringify(currentAnswers));
}
/**
* プレイヤーの行動(打牌、加槓、暗槓、ツモ和了)を待つ
* 制限時間が過ぎたらツモ切り
* NOTE: 時間切れチェックが行われたときにタイミングによっては次のwaitingが始まっている場合があることを考慮し、Setに一意のIDを格納する構造としている
* @param room
* @param house
* @param mj
*/
@bindThis
private async waitForTurn(room: Room, house: Mmj.House, mj: Mmj.MasterGameEngine) {
const aiHouses = [[1, room.user1Ai], [2, room.user2Ai], [3, room.user3Ai], [4, room.user4Ai]].filter(([id, ai]) => ai).map(([id, ai]) => mj.getHouse(id));
if (mj.riichis[house]) {
// リーチ時はアガリ牌でない限りツモ切り
if (!Mmj.isAgarikei(mj.handTileTypes[house])) {
setTimeout(() => {
this.dahai(room, mj, house, mj.handTiles[house].at(-1));
}, 500);
return;
}
}
if (aiHouses.includes(house)) {
setTimeout(() => {
this.dahai(room, mj, house, mj.handTiles[house].at(-1));
}, 500);
return;
}
const id = Math.random().toString(36).slice(2);
console.log('waitForTurn', house, id);
this.redisClient.sadd(`mahjong:gameTurnWaiting:${room.id}`, id);
const waitingStartedAt = Date.now();
const interval = setInterval(async () => {
const waiting = await this.redisClient.sismember(`mahjong:gameTurnWaiting:${room.id}`, id);
if (waiting === 0) {
clearInterval(interval);
return;
}
if (Date.now() - waitingStartedAt > TURN_TIMEOUT_MS) {
await this.redisClient.srem(`mahjong:gameTurnWaiting:${room.id}`, id);
console.log('turn timeout', house, id);
clearInterval(interval);
const handTiles = mj.handTiles[house];
await this.dahai(room, mj, house, handTiles.at(-1));
return;
}
}, 2000);
}
/**
* プレイヤーが行動(打牌、加槓、暗槓、ツモ和了)したら呼ぶ
* @param roomId
*/
@bindThis
private async clearTurnWaitingTimer(roomId: Room['id']) {
await this.redisClient.del(`mahjong:gameTurnWaiting:${roomId}`);
}
@bindThis
public packState(room: Room, me: MiUser) {
const mj = new Mmj.MasterGameEngine(room.gameState);
const myIndex = room.user1Id === me.id ? 1 : room.user2Id === me.id ? 2 : room.user3Id === me.id ? 3 : 4;
return mj.createPlayerState(myIndex);
}
@bindThis
public async packRoom(room: Room, me: MiUser) {
if (room.gameState) {
return {
...room,
gameState: this.packState(room, me),
};
} else {
return {
...room,
};
}
}
@bindThis
public dispose(): void {
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View File

@@ -933,10 +933,13 @@ export class NoteCreateService implements OnApplicationShutdown {
}
}
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
// 自分自身のHTL
if (note.userHost == null) {
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
}

View File

@@ -505,14 +505,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
if (role.isPublic) {
const user = await this.usersRepository.findOneByOrFail({ id: userId });
if (role.isPublic && user.host === null) {
this.notificationService.createNotification(userId, 'roleAssigned', {
roleId: roleId,
});
}
if (moderator) {
const user = await this.usersRepository.findOneByOrFail({ id: userId });
this.moderationLogService.log(moderator, 'assignRole', {
roleId: roleId,
roleName: role.name,

View File

@@ -279,8 +279,10 @@ export class UserFollowingService implements OnModuleInit {
});
// 通知を作成
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
}, followee.id);
if (follower.host === null) {
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
}, followee.id);
}
}
if (alreadyFollowed) return;

View File

@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import type { RenoteMutingsRepository } from '@/models/_.js';
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
import { IdService } from '@/core/IdService.js';
import type { MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
@Injectable()
export class UserRenoteMutingService {
constructor(
@Inject(DI.renoteMutingsRepository)
private renoteMutingsRepository: RenoteMutingsRepository,
private idService: IdService,
private cacheService: CacheService,
) {
}
@bindThis
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
await this.renoteMutingsRepository.insert({
id: this.idService.gen(),
muterId: user.id,
muteeId: target.id,
});
await this.cacheService.renoteMutingsCache.refresh(user.id);
}
@bindThis
public async unmute(mutings: MiRenoteMuting[]): Promise<void> {
if (mutings.length === 0) return;
await this.renoteMutingsRepository.delete({
id: In(mutings.map(m => m.id)),
});
const muterIds = [...new Set(mutings.map(m => m.muterId))];
for (const muterId of muterIds) {
await this.cacheService.renoteMutingsCache.refresh(muterId);
}
}
}

View File

@@ -82,5 +82,6 @@ export const DI = {
userMemosRepository: Symbol('userMemosRepository'),
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
reversiGamesRepository: Symbol('reversiGamesRepository'),
mahjongGamesRepository: Symbol('mahjongGamesRepository'),
//#endregion
};

View File

@@ -59,6 +59,7 @@ import {
} from '@/models/json-schema/meta.js';
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
import { packedMahjongRoomDetailedSchema } from '@/models/json-schema/mahjong-room.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -115,6 +116,7 @@ export const refs = {
MetaDetailed: packedMetaDetailedSchema,
SystemWebhook: packedSystemWebhookSchema,
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
MahjongRoomDetailed: packedMahjongRoomDetailedSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;

View File

@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
export type JsonObject = {[K in string]?: JsonValue};
export type JsonArray = JsonValue[];

View File

@@ -0,0 +1,89 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@Entity('mahjong_game')
export class MiMahjongGame {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
nullable: true,
})
public startedAt: Date | null;
@Column('timestamp with time zone', {
nullable: true,
})
public endedAt: Date | null;
@Column({
...id(),
nullable: true,
})
public user1Id: MiUser['id'] | null;
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user1: MiUser | null;
@Column({
...id(),
nullable: true,
})
public user2Id: MiUser['id'] | null;
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user2: MiUser | null;
@Column({
...id(),
nullable: true,
})
public user3Id: MiUser['id'] | null;
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user3: MiUser | null;
@Column({
...id(),
nullable: true,
})
public user4Id: MiUser['id'] | null;
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user4: MiUser | null;
@Column('boolean', {
default: false,
})
public isEnded: boolean;
@Column({
...id(),
nullable: true,
})
public winnerId: MiUser['id'] | null;
// in sec
@Column('smallint', {
default: 90,
})
public timeLimitForEachTurn: number;
@Column('jsonb', {
default: [],
})
public logs: number[][];
}

View File

@@ -77,7 +77,8 @@ import {
MiUserProfile,
MiUserPublickey,
MiUserSecurityKey,
MiWebhook
MiWebhook,
MiMahjongGame,
} from './_.js';
import type { DataSource } from 'typeorm';
@@ -495,6 +496,12 @@ const $reversiGamesRepository: Provider = {
inject: [DI.db],
};
const $mahjongGamesRepository: Provider = {
provide: DI.mahjongGamesRepository,
useFactory: (db: DataSource) => db.getRepository(MiMahjongGame),
inject: [DI.db],
};
@Module({
imports: [],
providers: [
@@ -567,6 +574,7 @@ const $reversiGamesRepository: Provider = {
$userMemosRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
$mahjongGamesRepository,
],
exports: [
$usersRepository,
@@ -638,6 +646,7 @@ const $reversiGamesRepository: Provider = {
$userMemosRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
$mahjongGamesRepository,
],
})
export class RepositoryModule {

View File

@@ -79,6 +79,7 @@ import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { MiMahjongGame } from '@/models/MahjongGame.js';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
export interface MiRepository<T extends ObjectLiteral> {
@@ -194,6 +195,7 @@ export {
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
MiMahjongGame,
};
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport> & MiRepository<MiAbuseUserReport>;
@@ -265,3 +267,4 @@ export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlas
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
export type MahjongGamesRepository = Repository<MiMahjongGame> & MiRepository<MiMahjongGame>;

View File

@@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedMahjongRoomDetailedSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
startedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
endedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
isStarted: {
type: 'boolean',
optional: false, nullable: false,
},
isEnded: {
type: 'boolean',
optional: false, nullable: false,
},
user1Id: {
type: 'string',
optional: false, nullable: null,
format: 'id',
},
user2Id: {
type: 'string',
optional: false, nullable: null,
format: 'id',
},
user3Id: {
type: 'string',
optional: false, nullable: null,
format: 'id',
},
user4Id: {
type: 'string',
optional: false, nullable: null,
format: 'id',
},
user1: {
type: 'object',
optional: false, nullable: null,
ref: 'User',
},
user2: {
type: 'object',
optional: false, nullable: null,
ref: 'User',
},
user3: {
type: 'object',
optional: false, nullable: null,
ref: 'User',
},
user4: {
type: 'object',
optional: false, nullable: null,
ref: 'User',
},
user1Ai: {
type: 'boolean',
optional: false, nullable: false,
},
user2Ai: {
type: 'boolean',
optional: false, nullable: false,
},
user3Ai: {
type: 'boolean',
optional: false, nullable: false,
},
user4Ai: {
type: 'boolean',
optional: false, nullable: false,
},
user1Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user2Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user3Ready: {
type: 'boolean',
optional: false, nullable: false,
},
user4Ready: {
type: 'boolean',
optional: false, nullable: false,
},
timeLimitForEachTurn: {
type: 'number',
optional: false, nullable: false,
},
},
} as const;

View File

@@ -204,6 +204,7 @@ export const packedNoteSchema = {
reactionAcceptance: {
type: 'string',
optional: false, nullable: true,
enum: ['likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'],
},
reactionEmojis: {
type: 'object',

View File

@@ -78,6 +78,7 @@ import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { MiMahjongGame } from '@/models/MahjongGame.js';
import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js';
@@ -198,6 +199,7 @@ export const entities = [
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
MiMahjongGame,
...charts,
];

View File

@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { PollVotesRepository, NotesRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { CacheService } from '@/core/CacheService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
@@ -24,6 +25,7 @@ export class EndedPollNotificationProcessorService {
@Inject(DI.pollVotesRepository)
private pollVotesRepository: PollVotesRepository,
private cacheService: CacheService,
private notificationService: NotificationService,
private queueLoggerService: QueueLoggerService,
) {
@@ -47,9 +49,12 @@ export class EndedPollNotificationProcessorService {
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
for (const userId of userIds) {
this.notificationService.createNotification(userId, 'pollEnded', {
noteId: note.id,
});
const profile = await this.cacheService.userProfileCache.fetch(userId);
if (profile.userHost === null) {
this.notificationService.createNotification(userId, 'pollEnded', {
noteId: note.id,
});
}
}
}
}

View File

@@ -46,6 +46,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
import { ReversiChannelService } from './api/stream/channels/reversi.js';
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
import { MahjongRoomChannelService } from './api/stream/channels/mahjong-room.js';
@Module({
imports: [
@@ -84,6 +85,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js
RoleTimelineChannelService,
ReversiChannelService,
ReversiGameChannelService,
MahjongRoomChannelService,
HomeTimelineChannelService,
HybridTimelineChannelService,
LocalTimelineChannelService,

View File

@@ -385,6 +385,9 @@ import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
import * as ep___mahjong_createRoom from './endpoints/mahjong/create-room.js';
import * as ep___mahjong_joinRoom from './endpoints/mahjong/join-room.js';
import * as ep___mahjong_showRoom from './endpoints/mahjong/show-room.js';
import { GetterService } from './GetterService.js';
import { ApiLoggerService } from './ApiLoggerService.js';
import type { Provider } from '@nestjs/common';
@@ -768,6 +771,9 @@ const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useC
const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default };
const $mahjong_createRoom: Provider = { provide: 'ep:mahjong/create-room', useClass: ep___mahjong_createRoom.default };
const $mahjong_joinRoom: Provider = { provide: 'ep:mahjong/join-room', useClass: ep___mahjong_joinRoom.default };
const $mahjong_showRoom: Provider = { provide: 'ep:mahjong/show-room', useClass: ep___mahjong_showRoom.default };
@Module({
imports: [
@@ -1155,6 +1161,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$reversi_showGame,
$reversi_surrender,
$reversi_verify,
$mahjong_createRoom,
$mahjong_joinRoom,
$mahjong_showRoom,
],
exports: [
$admin_meta,
@@ -1534,6 +1543,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$reversi_showGame,
$reversi_surrender,
$reversi_verify,
$mahjong_createRoom,
$mahjong_joinRoom,
$mahjong_showRoom,
],
})
export class EndpointsModule {}

View File

@@ -391,6 +391,9 @@ import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
import * as ep___mahjong_createRoom from './endpoints/mahjong/create-room.js';
import * as ep___mahjong_joinRoom from './endpoints/mahjong/join-room.js';
import * as ep___mahjong_showRoom from './endpoints/mahjong/show-room.js';
const eps = [
['admin/meta', ep___admin_meta],
@@ -772,6 +775,9 @@ const eps = [
['reversi/show-game', ep___reversi_showGame],
['reversi/surrender', ep___reversi_surrender],
['reversi/verify', ep___reversi_verify],
['mahjong/create-room', ep___mahjong_createRoom],
['mahjong/join-room', ep___mahjong_joinRoom],
['mahjong/show-room', ep___mahjong_showRoom],
];
interface IEndpointMetaBase {

View File

@@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiService } from '@/core/ReversiService.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
errors: {
},
res: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id', nullable: true },
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private reversiService: ReversiService,
) {
super(meta, paramDef, async (ps, me) => {
if (ps.userId) {
await this.reversiService.matchSpecificUserCancel(me, ps.userId);
return;
} else {
await this.reversiService.matchAnyUserCancel(me);
}
});
}
}

View File

@@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { MahjongService } from '@/core/MahjongService.js';
import { ApiError } from '../../error.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
errors: {
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'MahjongRoomDetailed',
},
} as const;
export const paramDef = {
type: 'object',
properties: {
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private mahjongService: MahjongService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.mahjongService.createRoom(me);
return await this.mahjongService.packRoom(room, me);
});
}
}

View File

@@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { DI } from '@/di-symbols.js';
import type { ReversiGamesRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
export const meta = {
requireCredential: false,
res: {
type: 'array',
optional: false, nullable: false,
items: { ref: 'ReversiGameLite' },
},
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
my: { type: 'boolean', default: false },
},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
private reversiGameEntityService: ReversiGameEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
.innerJoinAndSelect('game.user1', 'user1')
.innerJoinAndSelect('game.user2', 'user2');
if (ps.my && me) {
query.andWhere(new Brackets(qb => {
qb
.where('game.user1Id = :userId', { userId: me.id })
.orWhere('game.user2Id = :userId', { userId: me.id });
}));
} else {
query.andWhere('game.isStarted = TRUE');
}
const games = await query.take(ps.limit).getMany();
return await this.reversiGameEntityService.packLiteMany(games);
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { MahjongService } from '@/core/MahjongService.js';
import { ApiError } from '../../error.js';
export const meta = {
requireCredential: true,
kind: 'write:account',
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: '370e42b0-2a67-4306-9328-51c5f568f110',
},
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'MahjongRoomDetailed',
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private mahjongService: MahjongService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.mahjongService.getRoom(ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
await this.mahjongService.joinRoom(room.id, me);
return await this.mahjongService.packRoom(room, me);
});
}
}

View File

@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { MahjongService } from '@/core/MahjongService.js';
import { ApiError } from '../../error.js';
export const meta = {
requireCredential: true,
kind: 'read:account',
errors: {
noSuchRoom: {
message: 'No such room.',
code: 'NO_SUCH_ROOM',
id: 'd77df68f-06f3-492b-9078-e6f72f4acf23',
},
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'MahjongRoomDetailed',
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roomId: { type: 'string', format: 'misskey:id' },
},
required: ['roomId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private mahjongService: MahjongService,
) {
super(meta, paramDef, async (ps, me) => {
const room = await this.mahjongService.getRoom(ps.roomId);
if (room == null) {
throw new ApiError(meta.errors.noSuchRoom);
}
return await this.mahjongService.packRoom(room, me);
});
}
}

View File

@@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ReversiService } from '@/core/ReversiService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { ApiError } from '../../error.js';
export const meta = {
errors: {
noSuchGame: {
message: 'No such game.',
code: 'NO_SUCH_GAME',
id: '8fb05624-b525-43dd-90f7-511852bdfeee',
},
},
res: {
type: 'object',
optional: false, nullable: false,
properties: {
desynced: { type: 'boolean' },
game: {
type: 'object',
optional: true, nullable: true,
ref: 'ReversiGameDetailed',
},
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
gameId: { type: 'string', format: 'misskey:id' },
crc32: { type: 'string' },
},
required: ['gameId', 'crc32'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private reversiService: ReversiService,
private reversiGameEntityService: ReversiGameEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const game = await this.reversiService.checkCrc(ps.gameId, ps.crc32);
if (game) {
return {
desynced: true,
game: await this.reversiGameEntityService.packDetail(game),
};
} else {
return {
desynced: false,
};
}
});
}
}

View File

@@ -139,6 +139,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
timelineConfig = [
`homeTimeline:${me.id}`,
'localTimeline',
`localTimelineWithReplyTo:${me.id}`,
];
}

View File

@@ -6,12 +6,11 @@
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { IdService } from '@/core/IdService.js';
import type { RenoteMutingsRepository } from '@/models/_.js';
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
import type { RenoteMutingsRepository } from '@/models/_.js';
export const meta = {
tags: ['account'],
@@ -62,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private renoteMutingsRepository: RenoteMutingsRepository,
private getterService: GetterService,
private idService: IdService,
private userRenoteMutingService: UserRenoteMutingService,
) {
super(meta, paramDef, async (ps, me) => {
const muter = me;
@@ -79,21 +78,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
});
// Check if already muting
const exist = await this.renoteMutingsRepository.findOneBy({
muterId: muter.id,
muteeId: mutee.id,
const exist = await this.renoteMutingsRepository.exists({
where: {
muterId: muter.id,
muteeId: mutee.id,
},
});
if (exist != null) {
if (exist === true) {
throw new ApiError(meta.errors.alreadyMuting);
}
// Create mute
await this.renoteMutingsRepository.insert({
id: this.idService.gen(),
muterId: muter.id,
muteeId: mutee.id,
} as MiRenoteMuting);
await this.userRenoteMutingService.mute(muter, mutee);
});
}
}

View File

@@ -5,10 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RenoteMutingsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
import type { RenoteMutingsRepository } from '@/models/_.js';
export const meta = {
tags: ['account'],
@@ -53,6 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private renoteMutingsRepository: RenoteMutingsRepository,
private getterService: GetterService,
private userRenoteMutingService: UserRenoteMutingService,
) {
super(meta, paramDef, async (ps, me) => {
const muter = me;
@@ -79,9 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// Delete mute
await this.renoteMutingsRepository.delete({
id: exist.id,
});
await this.userRenoteMutingService.unmute([exist]);
});
}
}

View File

@@ -21,6 +21,7 @@ import { HashtagChannelService } from './channels/hashtag.js';
import { RoleTimelineChannelService } from './channels/role-timeline.js';
import { ReversiChannelService } from './channels/reversi.js';
import { ReversiGameChannelService } from './channels/reversi-game.js';
import { MahjongRoomChannelService } from './channels/mahjong-room.js';
import { type MiChannelService } from './channel.js';
@Injectable()
@@ -42,6 +43,7 @@ export class ChannelsService {
private adminChannelService: AdminChannelService,
private reversiChannelService: ReversiChannelService,
private reversiGameChannelService: ReversiGameChannelService,
private mahjongRoomChannelService: MahjongRoomChannelService,
) {
}
@@ -64,6 +66,7 @@ export class ChannelsService {
case 'admin': return this.adminChannelService;
case 'reversi': return this.reversiChannelService;
case 'reversiGame': return this.reversiGameChannelService;
case 'mahjongRoom': return this.mahjongRoomChannelService;
default:
throw new Error(`no such channel: ${name}`);

View File

@@ -14,6 +14,7 @@ import { CacheService } from '@/core/CacheService.js';
import { MiFollowing, MiUserProfile } from '@/models/_.js';
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
import type { JsonObject } from '@/misc/json-value.js';
import type { ChannelsService } from './ChannelsService.js';
import type { EventEmitter } from 'events';
import type Channel from './channel.js';
@@ -28,7 +29,7 @@ export default class Connection {
private wsConnection: WebSocket.WebSocket;
public subscriber: StreamEventEmitter;
private channels: Channel[] = [];
private subscribingNotes: any = {};
private subscribingNotes: Partial<Record<string, number>> = {};
private cachedNotes: Packed<'Note'>[] = [];
public userProfile: MiUserProfile | null = null;
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
@@ -101,7 +102,7 @@ export default class Connection {
*/
@bindThis
private async onWsConnectionMessage(data: WebSocket.RawData) {
let obj: Record<string, any>;
let obj: JsonObject;
try {
obj = JSON.parse(data.toString());
@@ -111,6 +112,8 @@ export default class Connection {
const { type, body } = obj;
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
switch (type) {
case 'readNotification': this.onReadNotification(body); break;
case 'subNote': this.onSubscribeNote(body); break;
@@ -151,7 +154,7 @@ export default class Connection {
}
@bindThis
private readNote(body: any) {
private readNote(body: JsonObject) {
const id = body.id;
const note = this.cachedNotes.find(n => n.id === id);
@@ -163,7 +166,7 @@ export default class Connection {
}
@bindThis
private onReadNotification(payload: any) {
private onReadNotification(payload: JsonObject) {
this.notificationService.readAllNotification(this.user!.id);
}
@@ -171,16 +174,14 @@ export default class Connection {
* 投稿購読要求時
*/
@bindThis
private onSubscribeNote(payload: any) {
if (!payload.id) return;
private onSubscribeNote(payload: JsonObject) {
if (!payload.id || typeof payload.id !== 'string') return;
if (this.subscribingNotes[payload.id] == null) {
this.subscribingNotes[payload.id] = 0;
}
const current = this.subscribingNotes[payload.id] ?? 0;
const updated = current + 1;
this.subscribingNotes[payload.id] = updated;
this.subscribingNotes[payload.id]++;
if (this.subscribingNotes[payload.id] === 1) {
if (updated === 1) {
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
}
}
@@ -189,11 +190,14 @@ export default class Connection {
* 投稿購読解除要求時
*/
@bindThis
private onUnsubscribeNote(payload: any) {
if (!payload.id) return;
private onUnsubscribeNote(payload: JsonObject) {
if (!payload.id || typeof payload.id !== 'string') return;
this.subscribingNotes[payload.id]--;
if (this.subscribingNotes[payload.id] <= 0) {
const current = this.subscribingNotes[payload.id];
if (current == null) return;
const updated = current - 1;
this.subscribingNotes[payload.id] = updated;
if (updated <= 0) {
delete this.subscribingNotes[payload.id];
this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage);
}
@@ -212,17 +216,22 @@ export default class Connection {
* チャンネル接続要求時
*/
@bindThis
private onChannelConnectRequested(payload: any) {
private onChannelConnectRequested(payload: JsonObject) {
const { channel, id, params, pong } = payload;
this.connectChannel(id, params, channel, pong);
if (typeof id !== 'string') return;
if (typeof channel !== 'string') return;
if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return;
if (typeof params !== 'undefined' && (typeof params !== 'object' || params === null || Array.isArray(params))) return;
this.connectChannel(id, params, channel, pong ?? undefined);
}
/**
* チャンネル切断要求時
*/
@bindThis
private onChannelDisconnectRequested(payload: any) {
private onChannelDisconnectRequested(payload: JsonObject) {
const { id } = payload;
if (typeof id !== 'string') return;
this.disconnectChannel(id);
}
@@ -230,7 +239,7 @@ export default class Connection {
* クライアントにメッセージ送信
*/
@bindThis
public sendMessageToWs(type: string, payload: any) {
public sendMessageToWs(type: string, payload: JsonObject) {
this.wsConnection.send(JSON.stringify({
type: type,
body: payload,
@@ -241,7 +250,7 @@ export default class Connection {
* チャンネルに接続
*/
@bindThis
public connectChannel(id: string, params: any, channel: string, pong = false) {
public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) {
const channelService = this.channelsService.getChannelService(channel);
if (channelService.requireCredential && this.user == null) {
@@ -288,7 +297,11 @@ export default class Connection {
* @param data メッセージ
*/
@bindThis
private onChannelMessageRequested(data: any) {
private onChannelMessageRequested(data: JsonObject) {
if (typeof data.id !== 'string') return;
if (typeof data.type !== 'string') return;
if (typeof data.body === 'undefined') return;
const channel = this.channels.find(c => c.id === data.id);
if (channel != null && channel.onMessage != null) {
channel.onMessage(data.type, data.body);

View File

@@ -8,6 +8,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { Packed } from '@/misc/json-schema.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import type Connection from './Connection.js';
/**
@@ -81,10 +82,12 @@ export default abstract class Channel {
this.connection = connection;
}
public send(payload: { type: string, body: JsonValue }): void
public send(type: string, payload: JsonValue): void
@bindThis
public send(typeOrPayload: any, payload?: any) {
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
const body = payload === undefined ? typeOrPayload.body : payload;
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
const body = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).body : payload;
this.connection.sendMessageToWs('channel', {
id: this.id,
@@ -93,11 +96,11 @@ export default abstract class Channel {
});
}
public abstract init(params: any): void;
public abstract init(params: JsonObject): void;
public dispose?(): void;
public onMessage?(type: string, body: any): void;
public onMessage?(type: string, body: JsonValue): void;
}
export type MiChannelService<T extends boolean> = {

View File

@@ -5,6 +5,7 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class AdminChannel extends Channel {
@@ -14,7 +15,7 @@ class AdminChannel extends Channel {
public static kind = 'read:admin:stream';
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
// Subscribe admin stream
this.subscriber.on(`adminStream:${this.user!.id}`, data => {
this.send(data);

View File

@@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class AntennaChannel extends Channel {
@@ -27,8 +28,9 @@ class AntennaChannel extends Channel {
}
@bindThis
public async init(params: any) {
this.antennaId = params.antennaId as string;
public async init(params: JsonObject) {
if (typeof params.antennaId !== 'string') return;
this.antennaId = params.antennaId;
// Subscribe stream
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);

View File

@@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class ChannelChannel extends Channel {
@@ -27,8 +28,9 @@ class ChannelChannel extends Channel {
}
@bindThis
public async init(params: any) {
this.channelId = params.channelId as string;
public async init(params: JsonObject) {
if (typeof params.channelId !== 'string') return;
this.channelId = params.channelId;
// Subscribe stream
this.subscriber.on('notesStream', this.onNote);

View File

@@ -5,6 +5,7 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class DriveChannel extends Channel {
@@ -14,7 +15,7 @@ class DriveChannel extends Channel {
public static kind = 'read:account';
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
// Subscribe drive stream
this.subscriber.on(`driveStream:${this.user!.id}`, data => {
this.send(data);

View File

@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class GlobalTimelineChannel extends Channel {
@@ -32,12 +33,12 @@ class GlobalTimelineChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.gtlAvailable) return;
this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
this.withRenotes = !!(params.withRenotes ?? true);
this.withFiles = !!(params.withFiles ?? false);
// Subscribe events
this.subscriber.on('notesStream', this.onNote);

View File

@@ -9,6 +9,7 @@ import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class HashtagChannel extends Channel {
@@ -28,11 +29,11 @@ class HashtagChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
if (!Array.isArray(params.q)) return;
if (!params.q.every(x => Array.isArray(x) && x.every(y => typeof y === 'string'))) return;
this.q = params.q;
if (this.q == null) return;
// Subscribe stream
this.subscriber.on('notesStream', this.onNote);
}

View File

@@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class HomeTimelineChannel extends Channel {
@@ -29,9 +30,9 @@ class HomeTimelineChannel extends Channel {
}
@bindThis
public async init(params: any) {
this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
public async init(params: JsonObject) {
this.withRenotes = !!(params.withRenotes ?? true);
this.withFiles = !!(params.withFiles ?? false);
this.subscriber.on('notesStream', this.onNote);
}

View File

@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class HybridTimelineChannel extends Channel {
@@ -34,13 +35,13 @@ class HybridTimelineChannel extends Channel {
}
@bindThis
public async init(params: any): Promise<void> {
public async init(params: JsonObject): Promise<void> {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.ltlAvailable) return;
this.withRenotes = params.withRenotes ?? true;
this.withReplies = params.withReplies ?? false;
this.withFiles = params.withFiles ?? false;
this.withRenotes = !!(params.withRenotes ?? true);
this.withReplies = !!(params.withReplies ?? false);
this.withFiles = !!(params.withFiles ?? false);
// Subscribe events
this.subscriber.on('notesStream', this.onNote);

View File

@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class LocalTimelineChannel extends Channel {
@@ -33,13 +34,13 @@ class LocalTimelineChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.ltlAvailable) return;
this.withRenotes = params.withRenotes ?? true;
this.withReplies = params.withReplies ?? false;
this.withFiles = params.withFiles ?? false;
this.withRenotes = !!(params.withRenotes ?? true);
this.withReplies = !!(params.withReplies ?? false);
this.withFiles = !!(params.withFiles ?? false);
// Subscribe events
this.subscriber.on('notesStream', this.onNote);

View File

@@ -0,0 +1,197 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { MahjongService } from '@/core/MahjongService.js';
import { GlobalEvents } from '@/core/GlobalEventService.js';
import Channel, { type MiChannelService } from '../channel.js';
class MahjongRoomChannel extends Channel {
public readonly chName = 'mahjongRoom';
public static shouldShare = false;
public static requireCredential = true as const;
public static kind = 'read:account';
private roomId: string | null = null;
constructor(
private mahjongService: MahjongService,
id: string,
connection: Channel['connection'],
) {
super(id, connection);
}
@bindThis
public async init(params: any) {
this.roomId = params.roomId as string;
this.subscriber.on(`mahjongRoomStream:${this.roomId}`, this.onMahjongRoomStreamMessage);
}
@bindThis
private async onMahjongRoomStreamMessage(message: GlobalEvents['mahjongRoom']['payload']) {
if (message.type === 'started') {
const packed = await this.mahjongService.packRoom(message.body.room, this.user!);
this.send('started', {
room: packed,
});
} else if (message.type === 'nextKyoku') {
const packed = this.mahjongService.packState(message.body.room, this.user!);
this.send('nextKyoku', {
state: packed,
});
} else {
this.send(message.type, message.body);
}
}
@bindThis
public onMessage(type: string, body: any) {
switch (type) {
case 'ready': this.ready(body); break;
case 'updateSettings': this.updateSettings(body.key, body.value); break;
case 'addAi': this.addAi(); break;
case 'confirmNextKyoku': this.confirmNextKyoku(); break;
case 'dahai': this.dahai(body.tile, body.riichi); break;
case 'tsumoHora': this.tsumoHora(); break;
case 'ronHora': this.ronHora(); break;
case 'pon': this.pon(); break;
case 'cii': this.cii(body.pattern); break;
case 'kan': this.kan(); break;
case 'ankan': this.ankan(body.tile); break;
case 'kakan': this.kakan(body.tile); break;
case 'nop': this.nop(); break;
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
}
}
@bindThis
private async updateSettings(key: string, value: any) {
if (this.user == null) return;
this.mahjongService.updateSettings(this.roomId!, this.user, key, value);
}
@bindThis
private async ready(ready: boolean) {
if (this.user == null) return;
this.mahjongService.changeReadyState(this.roomId!, this.user, ready);
}
@bindThis
private async confirmNextKyoku() {
if (this.user == null) return;
this.mahjongService.confirmNextKyoku(this.roomId!, this.user);
}
@bindThis
private async addAi() {
if (this.user == null) return;
this.mahjongService.addAi(this.roomId!, this.user);
}
@bindThis
private async dahai(tile: number, riichi = false) {
if (this.user == null) return;
this.mahjongService.commit_dahai(this.roomId!, this.user, tile, riichi);
}
@bindThis
private async tsumoHora() {
if (this.user == null) return;
this.mahjongService.commit_tsumoHora(this.roomId!, this.user);
}
@bindThis
private async ronHora() {
if (this.user == null) return;
this.mahjongService.commit_ronHora(this.roomId!, this.user);
}
@bindThis
private async pon() {
if (this.user == null) return;
this.mahjongService.commit_pon(this.roomId!, this.user);
}
@bindThis
private async cii(pattern: string) {
if (this.user == null) return;
this.mahjongService.commit_cii(this.roomId!, this.user, pattern);
}
@bindThis
private async kan() {
if (this.user == null) return;
this.mahjongService.commit_kan(this.roomId!, this.user);
}
@bindThis
private async ankan(tile: number) {
if (this.user == null) return;
this.mahjongService.commit_ankan(this.roomId!, this.user, tile);
}
@bindThis
private async kakan(tile: number) {
if (this.user == null) return;
this.mahjongService.commit_kakan(this.roomId!, this.user, tile);
}
@bindThis
private async nop() {
if (this.user == null) return;
this.mahjongService.commit_nop(this.roomId!, this.user);
}
@bindThis
private async claimTimeIsUp() {
if (this.user == null) return;
this.mahjongService.checkTimeout(this.roomId!);
}
@bindThis
public dispose() {
// Unsubscribe events
this.subscriber.off(`mahjongRoomStream:${this.roomId}`, this.onMahjongRoomStreamMessage);
}
}
@Injectable()
export class MahjongRoomChannelService implements MiChannelService<true> {
public readonly shouldShare = MahjongRoomChannel.shouldShare;
public readonly requireCredential = MahjongRoomChannel.requireCredential;
public readonly kind = MahjongRoomChannel.kind;
constructor(
private mahjongService: MahjongService,
) {
}
@bindThis
public create(id: string, connection: Channel['connection']): MahjongRoomChannel {
return new MahjongRoomChannel(
this.mahjongService,
id,
connection,
);
}
}

View File

@@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class MainChannel extends Channel {
@@ -25,7 +26,7 @@ class MainChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
// Subscribe main stream channel
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
switch (data.type) {

View File

@@ -6,6 +6,7 @@
import Xev from 'xev';
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
const ev = new Xev();
@@ -22,19 +23,22 @@ class QueueStatsChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
ev.addListener('queueStats', this.onStats);
}
@bindThis
private onStats(stats: any) {
private onStats(stats: JsonObject) {
this.send('stats', stats);
}
@bindThis
public onMessage(type: string, body: any) {
public onMessage(type: string, body: JsonValue) {
switch (type) {
case 'requestLog':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
if (typeof body.id !== 'string') return;
if (typeof body.length !== 'number') return;
ev.once(`queueStatsLog:${body.id}`, statsLog => {
this.send('statsLog', statsLog);
});

View File

@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { ReversiService } from '@/core/ReversiService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class ReversiGameChannel extends Channel {
@@ -28,25 +29,41 @@ class ReversiGameChannel extends Channel {
}
@bindThis
public async init(params: any) {
this.gameId = params.gameId as string;
public async init(params: JsonObject) {
if (typeof params.gameId !== 'string') return;
this.gameId = params.gameId;
this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send);
}
@bindThis
public onMessage(type: string, body: any) {
public onMessage(type: string, body: JsonValue) {
switch (type) {
case 'ready': this.ready(body); break;
case 'updateSettings': this.updateSettings(body.key, body.value); break;
case 'cancel': this.cancelGame(); break;
case 'putStone': this.putStone(body.pos, body.id); break;
case 'ready':
if (typeof body !== 'boolean') return;
this.ready(body);
break;
case 'updateSettings':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
if (typeof body.key !== 'string') return;
if (typeof body.value !== 'object' || body.value === null || Array.isArray(body.value)) return;
this.updateSettings(body.key, body.value);
break;
case 'cancel':
this.cancelGame();
break;
case 'putStone':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
if (typeof body.pos !== 'number') return;
if (typeof body.id !== 'string') return;
this.putStone(body.pos, body.id);
break;
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
}
}
@bindThis
private async updateSettings(key: string, value: any) {
private async updateSettings(key: string, value: JsonObject) {
if (this.user == null) return;
this.reversiService.updateSettings(this.gameId!, this.user, key, value);

View File

@@ -5,6 +5,7 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class ReversiChannel extends Channel {
@@ -21,7 +22,7 @@ class ReversiChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
this.subscriber.on(`reversiStream:${this.user!.id}`, this.send);
}

View File

@@ -8,6 +8,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class RoleTimelineChannel extends Channel {
@@ -28,8 +29,9 @@ class RoleTimelineChannel extends Channel {
}
@bindThis
public async init(params: any) {
this.roleId = params.roleId as string;
public async init(params: JsonObject) {
if (typeof params.roleId !== 'string') return;
this.roleId = params.roleId;
this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
}

View File

@@ -6,6 +6,7 @@
import Xev from 'xev';
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
const ev = new Xev();
@@ -22,19 +23,20 @@ class ServerStatsChannel extends Channel {
}
@bindThis
public async init(params: any) {
public async init(params: JsonObject) {
ev.addListener('serverStats', this.onStats);
}
@bindThis
private onStats(stats: any) {
private onStats(stats: JsonObject) {
this.send('stats', stats);
}
@bindThis
public onMessage(type: string, body: any) {
public onMessage(type: string, body: JsonValue) {
switch (type) {
case 'requestLog':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
ev.once(`serverStatsLog:${body.id}`, statsLog => {
this.send('statsLog', statsLog);
});

View File

@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class UserListChannel extends Channel {
@@ -36,10 +37,11 @@ class UserListChannel extends Channel {
}
@bindThis
public async init(params: any) {
this.listId = params.listId as string;
this.withFiles = params.withFiles ?? false;
this.withRenotes = params.withRenotes ?? true;
public async init(params: JsonObject) {
if (typeof params.listId !== 'string') return;
this.listId = params.listId;
this.withFiles = !!(params.withFiles ?? false);
this.withRenotes = !!(params.withRenotes ?? true);
// Check existence and owner
const listExist = await this.userListsRepository.exists({

View File

@@ -9,8 +9,8 @@
import * as assert from 'assert';
import { setTimeout } from 'node:timers/promises';
import { Redis } from 'ioredis';
import { loadConfig } from '@/config.js';
import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js';
import { loadConfig } from '@/config.js';
function genHost() {
return randomString() + '.example.com';
@@ -492,6 +492,44 @@ describe('Timelines', () => {
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
});
test.concurrent('FTT: ローカルユーザーの HTL にはプッシュされる', async () => {
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
await api('following/create', {
userId: alice.id,
}, bob);
const aliceNote = await post(alice, { text: 'I\'m Alice.' });
const bobNote = await post(bob, { text: 'I\'m Bob.' });
const carolNote = await post(carol, { text: 'I\'m Carol.' });
await waitForPushToTl();
// NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1);
const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1);
assert.strictEqual(bobHTL.includes(aliceNote.id), true);
assert.strictEqual(bobHTL.includes(bobNote.id), true);
assert.strictEqual(bobHTL.includes(carolNote.id), false);
});
test.concurrent('FTT: リモートユーザーの HTL にはプッシュされない', async () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
await api('following/create', {
userId: alice.id,
}, bob);
await post(alice, { text: 'I\'m Alice.' });
await post(bob, { text: 'I\'m Bob.' });
await waitForPushToTl();
// NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0);
});
});
describe('Local TL', () => {

View File

@@ -53,7 +53,6 @@ await fs.readFile(
'../../assets/**',
'../../fluent-emojis/**',
'../../locales/ja-JP.yml',
'../../misskey-assets/**',
'assets/**',
'public/**',
'../../pnpm-lock.yaml',

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

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