Compare commits

...

195 Commits

Author SHA1 Message Date
syuilo
b72f9186b5 2023.12.0-beta.3 2023-12-09 13:15:30 +09:00
Yuriha
dd332b3515 Misskey Playのノート投稿画面で「内容を隠す」を設定できるようにする (#12576)
* Add the content warning option in AiScript UI postFormButton

* Fix initial CW in postFormButton

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-09 13:14:51 +09:00
おさむのひと
b7bdd45dba Fix/vue import error on intellij (#12612)
* Fix fix labeler config (#8)

* fix vue import error

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-09 13:13:31 +09:00
syuilo
319267e096 update deps 2023-12-09 13:02:14 +09:00
syuilo
fcf0f5f6b5 fix(frontend): disable Mk:apiExternal 2023-12-09 12:58:00 +09:00
zyoshoka
6c1f839cbe chore: labelerが治っていなかったのを修正 (#12610)
* fix ci

* fix

* fix labeler.yml

* Revert "fix labeler.yml"

This reverts commit 9b6a7d02cd.

---------

Co-authored-by: samunohito <46447427+samunohito@users.noreply.github.com>
2023-12-09 09:54:43 +09:00
かっこかり
2c6fc0ba63 fix(dev-frontend) 足りてないCSPを追加 (#12606)
* fix(dev-frontend) サーバーサイドのHTMLと噛み合わない部分を修正

* cspをなおした

* typo
2023-12-08 20:16:49 +09:00
zyoshoka
d10048edac chore: fix labeler's config (#12609) 2023-12-08 20:16:15 +09:00
まっちゃとーにゅ
ab5d2eca1f enhance(frontend): window.openやaタグにnoopenerオプションをつける (MisskeyIO#283) 2023-12-08 19:46:25 +09:00
かっこかり
c54d1cdde2 fix(dev-frontend) サーバーサイドのHTMLと噛み合わない部分を修正 (#12605) 2023-12-08 16:54:33 +09:00
おさむのひと
712e5447b8 Merge pull request #12604 from kakkokari-gtyih/fix-dev-0
fix(dev-frontend) 一部のアセットが読み込まれない問題を修正
2023-12-08 16:37:41 +09:00
kakkokari-gtyih
b760db13bc fix(dev) 一部のアセットが読み込まれない問題を修正 2023-12-08 16:32:24 +09:00
ikasoba
e38af60fd0 fix: secure: true なエンドポイントの型が misskey-js に含まれていない (#12603)
* 作った

* 修正

* 修正
2023-12-08 15:15:17 +09:00
かっこかり
ac4089f37d enhance(frontend): ウィジェットを非表示にできるPageMetaを追加 (#12456)
* (enhance) ウィジェットを非表示にできるPageMetaを追加

* fix lint

* rename

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-08 13:06:42 +09:00
dependabot[bot]
f80ae7f686 chore(deps): bump actions/labeler from 4 to 5 (#12584)
Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 5.
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](https://github.com/actions/labeler/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/labeler
  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>
2023-12-08 09:00:37 +09:00
おさむのひと
9059b837fa fix CONTRIBUTING.md (#12600) 2023-12-08 09:00:23 +09:00
おさむのひと
b0039f0946 chore: 開発モードでフロントエンドとバックエンドを独立して起動するようにする(再) (#12593)
* [wip]run standalone vite

* [wip]run standalone vite

* some fix (tabler icons, sw, streaming)

* fix theme

* fix run scripts

* favicon

* client-assets

* cssの読み込み順序とCSP設定の変更

* fix lang change

* fix clientManifest

* baseを相対パスにしてドメイン直下とサブディレクトリ配下両方に対応

* 色々修正

* 色々修正

* 色々修正

* fix

* Revert "client-assets"

This reverts commit 582601e90e.

# Conflicts:
#	packages/frontend/vite.config.ts

* 色々修正

* fix

* fix

* add url and proxy to server proxy

* Update packages/frontend/src/index.html

* wip

* Merge remote-tracking branch 'origin/develop' into feat/launch-standalone-frontend

# Conflicts:
#	packages/frontend/src/pages/welcome.entrance.a.vue

* Merge remote-tracking branch 'origin/develop' into feat/launch-standalone-frontend

# Conflicts:
#	packages/frontend/src/pages/welcome.entrance.a.vue

* fix tabler load

* Apply suggestions from code review

* Update packages/frontend/src/index.html

* fix

* fix vite.config.local-dev.ts

* fix CONTRIBUTING.md

---------

Co-authored-by: FruitRiin <nassii74@gmail.com>
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
Co-authored-by: 果物リン <fruitriin@riinswork.space>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: ozelot <contact@ozelot.dev>
2023-12-08 08:22:08 +09:00
KanariKanaru
e6d01e33e6 fix(backend): ブロックした相手から自分のノートが見えないように(/users/featured-notes, /users/notes) (#12511)
* fix: ブロックした相手から自分のノートが見えないように(ユーザー,チャンネル)

* Update CHANGELOG.md

* /users/featured-notesでもブロックを考慮するように

* cacheServiceを使うように

* /channels/timeline.tsで必要のないnoteFilterを持たないように

* Update CHANGELOG.md

* FanoutTimelineEndpointServiceへの対応

- ブロックされている場合は、/users/notesでノートが表示されない
- ミュートしている場合は、ノートが表示される
2023-12-07 18:15:38 +09:00
KanariKanaru
bcf6b7f5ee enhance: meilisearchを有効にしてもミュートやブロックを考慮するように (#12575)
* enhance: meilisearchを有効にしてもミュートやブロックを考慮するように

* Update CHANGELOG.md
2023-12-07 17:09:31 +09:00
anatawa12
1d3ef7b42f fix(backend): pagination with sinceId broken (#12586)
* fix(backend): pagination with sinceId broken

* fix(backend): pagination with sinceId broken for dbFallback
2023-12-07 17:07:06 +09:00
Ryan He
e926411812 chore: Add descriptions for "MeiliSearch" and "allowedPrivateNetworks" to example.yml (#12594)
* Update example.yml, add descriptions for some items

Add descriptions for "MeiliSearch" and "allowedPrivateNetworks"

* Update docker_example.yml

Add descriptions for "MeiliSearch" and "allowedPrivateNetworks"
2023-12-07 17:00:34 +09:00
zyoshoka
406b4bdbe7 refactor(frontend): 非推奨となったReactivity Transformを使わないように (#12539)
* refactor(frontend): 非推奨となったReactivity Transformを使わないように

* refactor: 不要な括弧を除去

* fix: 不要なアノテーションを除去

* fix: Refの配列をrefしている部分の対応

* refactor: 不要な括弧を除去

* fix: lint

* refactor: Ref、ShallowRef、ComputedRefの変数の宣言をletからconstに置換

* fix: type error

* chore: drop reactivity transform from eslint configuration

* refactor: remove unnecessary import

* fix: 対応漏れ
2023-12-07 14:42:09 +09:00
yupix
e42c91dee7 feat: Roleに関するSchemaを追加 (#12572)
* feat: Roleに関連するschemaを追加

* feat: 新しいRoleSchemaを使うように

* chore: misskey.jsのデータを更新

* chore: misskey-js.api.mdを更新
2023-12-06 15:47:57 +09:00
anatawa12
00b11b1f75 chore: hide thumbnail if website is sensitive (#12581) 2023-12-06 13:46:10 +09:00
Yuriha
ad60e43ae4 タイムラインの「リノートを表示」のトグルスイッチが反応しない問題を直す (#12577)
* [frontend] Fix renote toggle switch

* Fix MkMenu rather than usage
2023-12-06 12:07:53 +09:00
anatawa12
8866c530c4 fix(backend): エポックを固定することで年越し時にトレンドが壊れる問題を修正 (#12567) 2023-12-04 20:33:11 +09:00
syuilo
920e521176 2023.12.0-beta.2 2023-12-04 20:04:34 +09:00
syuilo
9c90ff7d06 update deps 2023-12-04 19:40:46 +09:00
おさむのひと
e90ad09551 fix (frontend): 絵文字ピッカー経由で投稿欄に絵文字を入れた際、ソフトウェアキーボードが立ち上がらないようにする (#12561) 2023-12-04 18:12:14 +09:00
anatawa12
bb38e62ae6 chore: 自分へのリプライのみ走査するように (#12570) 2023-12-04 17:56:48 +09:00
yupix
33034b0e02 feat: ユーザースキーマの改善 (#12568)
* chore: notifyにenumを設定

* feat: securityKeysListの型を明確に

* feat: notificationRecieveConfigにpropertiesを定義

* chore: misskey.jsのmodelを更新

* fix: as constをつけ忘れている
2023-12-04 16:53:31 +09:00
anatawa12
18109fcef7 Filter User / Instance Mutes in FanoutTimelineEndpointService (#12565)
* fix: unnecessary logging in FanoutTimelineEndpointService

* chore: TimelineOptions

* chore: add FanoutTimelineName type

* chore: forbid specifying both withReplies and withFiles since it's not implemented correctly

* chore: filter mutes, replies, renotes, files in FanoutTimelineEndpointService

* revert unintended changes

* use isReply in NoteCreateService

* fix: excludePureRenotes is not implemented

* fix: replies to me is excluded from local timeline

* chore(frontend): forbid enabling both withReplies and withFiles

* docs(changelog): インスタンスミュートが効かない問題の修正について言及
2023-12-04 14:38:21 +09:00
おさむのひと
b2c4973cda fix dev build (#12566) 2023-12-04 12:05:35 +09:00
おさむのひと
55c8ec80ed fix (backend): 「みつける」のなかにミュートしたユーザが現れてしまう問題を修正 (#12559)
* fix (backend): 「みつける」のなかにミュートしたユーザが現れてしまう問題を修正

* fix
2023-12-03 20:46:19 +09:00
おさむのひと
5e1d872404 入力フォームでもリアクション選択時に使用するピッカーを使うようにしたい (#12337)
* 入力フォームでもリアクション選択時に使用するピッカーを使うようにしたい

* erase console.log

* fix CHANGELOG.md

* reaction-picker.ts を戻し、今回の対応を入れた emoji-picker.ts を新たに作成

* fix CHANGELOG.md

* tweak

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-03 17:25:34 +09:00
Acid Chicken (硫酸鶏)
af15f8d09d fix(backend): reject malformed timestamp (#12554) 2023-12-03 14:38:42 +09:00
6543
34223f3da4 fix(backend): enhance nodeinfo by export instance admin via nodeAdmins key (#12503)
https://codeberg.org/thefederationinfo/nodeinfo_extension
2023-12-03 13:42:41 +09:00
おさむのひと
e17d741f4b enhance(misskey-js) misskey-jsのストリーミングAPI定義をバックエンドに追従 (#12552)
* (enhance) misskey-jsのストリーミングAPI定義をバックエンドに追従

* fix ci

* fix ci
2023-12-03 12:45:18 +09:00
果物リン
b4a83a22a1 may be fix ruby justify on safari (#12551) 2023-12-03 12:08:40 +09:00
かっこかり
5bf7813b2d enhance/feat(frontend): データセーバーの改良・強化 (#12526)
* enhance(frontend): データセーバーを個別で設定できるように

* Update Changelog

* fix design

* (fix) 設定が当たらない

* fix test(無理やり感)

* (fix) 設定がない状態ですべて有効・向操作が効かない

* fix

* tweak

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-03 10:58:42 +09:00
Nanaka Hiira
2eb86e0619 fix(backend): /emojiにおける拡張子の削除方法を修正 (#12543)
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-12-03 10:28:35 +09:00
おさむのひと
c68d87538a リストタイムラインでミュートが貫通してしまう問題に対処 (#12534)
* ユーザリストTL系の各種動作を修正・統一

* fix

* fix CHANGELOG.md

* テスト追加
2023-12-03 10:19:37 +09:00
shiosyakeyakini
4de4a2e143 fix: withChannelNotesとwithFilesを同時に指定したときの考慮 (#12550)
Co-authored-by: sorairo <sorairo@shiosyakeyakini.info>
2023-12-03 10:18:28 +09:00
おさむのひと
5ccd61b1f8 Revert "fix #12528 (#12536)" (#12548)
This reverts commit a5f0b5ec74.
2023-12-03 10:17:07 +09:00
おさむのひと
336416261a バックエンドが生成するapi.jsonからmisskey-jsの型を作成する (#12434)
* ひとまず生成できるところまで

* ファイル構成整理

* 生成コマンド整理

* misskey-jsへの組み込み

* fix generator.ts

* wip

* fix generator.ts

* fix package.json

* 生成ロジックの調整

* 型レベルでのswitch-case機構をmisskey-jsからfrontendに持ち込めるようにした

* 型チェック用のtsconfig.jsonを作成

* 他のエンドポイントを呼ぶ関数にも適用

* 未使用エンティティなどを削除

* misskey-js側で手動定義されていた型を自動生成された型に移行(ただしapi.jsonがvalidでなくなってしまったので後で修正する)

* messagingは廃止されている(テストのビルドエラー解消)

* validなapi.jsonを出力できるように修正

* 修正漏れ対応

* Ajvに怒られて起動できなかったところを修正

* fix ci(途中)

* パラメータenumをやめる

* add command

* add api.json

* 都度自動生成をやめる

* 一気通貫スクリプト修正

* fix ci

* 生成ロジック修正

* フロントの型チェックは結局やらなかったので戻しておく

* fix pnpm-lock.yaml

* add README.md

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-02 21:00:05 +09:00
おさむのひと
92029ac325 fix: #12544 (#12545)
* meを渡し忘れている

* fix CHANGELOG.md

* Revert "fix CHANGELOG.md"

This reverts commit aaee4e9b8a.
2023-12-02 20:11:31 +09:00
MeiMei
238e8ce939 fix: Filter featured collection (#12541) 2023-12-02 19:32:30 +09:00
anatawa12
a631b976c9 Refine fanout timeline (#12507)
* chore(endpoints/hybrid-timeline): don't pack inside getFromDb

* chore(endpoints/hybrid-timeline): Redisから取得する部分のうちSTLに依存しなそうなところを別のServiceに切り出し

* chore(endpoints/local-timeline): FanoutTimelineEndpointServiceで再実装

* chore(endpoints/channels/timeline): FanoutTimelineEndpointServiceで再実装

* chore(endpoints/timeline): FanoutTimelineEndpointServiceで再実装

* chore(endpoints/user-list-timeline): FanoutTimelineEndpointServiceで再実装

* chore(endpoints/users/notes): FanoutTimelineEndpointServiceで再実装

* chore: add useDbFallback to FanoutTimelineEndpointService.timeline and always true for channel / user note list

* style: fix lint error

* chore: split logic to multiple functions

* chore: implement redis fallback

* chore: 成功率を上げる

* fix: db fallback not working

* feat: allowPartial

* chore(frontend): set allowPartial

* chore(backend): remove fallbackIfEmpty

HTL will never be purged so it's no longer required

* fix: missing allowPartial in channel timeline

* fix: type of timelineConfig in hybrid-timeline

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-02 18:25:07 +09:00
paihu
cf3d45e7c8 fix(frontend): MFM ruby nyaize (#12362) 2023-12-02 17:09:22 +09:00
Camilla Ett
8968bfd309 fix(backend): カスタム絵文字のインポート時の動作を修正 (#12360)
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-12-02 17:07:57 +09:00
meron
c190b720d3 feat(frontend): 絵文字ピッカーのカテゴリを多階層フォルダで分類できるように (#12132)
* Update CHANGELOG.md

* Feat:emoji picker folder select

* Fix: lint error

* Fix: lint error 2

* Fix: lint error 3

* カスタム絵文字のカテゴリ表示部分が長かったので短くした

* エフェクトが壊れて出ないのを修正

* padding 18px -> 9px

* Update CHANGELOG.md

* Revert: en-US.yml

* chg: Folder -> folder

* chg: isChildrenExits -> isChildren

* chg: isChildren -> categoryFolderFlag

* カテゴリ末尾が / の場合ピッカーから消失するので「その他」と扱い対応

* 特定のパターンのカテゴリ名でピッカーに出てこないのを修正

「i18n.ts.other」や「/」始まりの場合壊れる

* chg: categoryFolderFlag -> hasChildSection

* code format

* Del: ti-fw

* fix

* 絵文字とフォルダの表示順序入れ替え

* ネストした場合にパネルでどこまでがどのフォルダのものかをわかりやすくした

* fix lint

* カテゴリの名前が長いと表示がおかしくなる問題を修正

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

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com>
Co-authored-by: Masaya Suzuki <15100604+massongit@users.noreply.github.com>
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>
Co-authored-by: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com>
Co-authored-by: nenohi <kimutipartylove@gmail.com>
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: rinsuki <428rinsuki+git@gmail.com>
Co-authored-by: FineArchs <133759614+FineArchs@users.noreply.github.com>
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2023-12-02 15:26:46 +09:00
anatawa12
b6b838416d chore: remove unimplemented excludeNsfw (#12520) 2023-12-02 12:05:53 +09:00
かっこかり
b37e8ffa69 (fix) 翻訳のダブりを解消 (#12519) 2023-12-02 12:05:03 +09:00
anatawa12
da0ecb650e chore: フォローしたときにHTLをパージしなくする (#12522) 2023-12-02 12:04:30 +09:00
zyoshoka
43c9ab2072 fix(frontend): 長い名前のチャンネルにおける投稿フォームの表示が崩れる問題を修正 (#12524)
* fix(frontend): 長い名前のチャンネルにおける投稿フォームの表示が崩れる問題を修正

* Update CHANGELOG.md
2023-12-02 12:04:11 +09:00
おさむのひと
a5f0b5ec74 fix #12528 (#12536) 2023-12-02 11:37:31 +09:00
Qwreey
c927d6824c Fix: missing receiver warn is not disappear (#12538) 2023-12-02 09:28:00 +09:00
nullnyat
5cd4c36cad rename docker-compose.yml.example to docker-compose_example.yml (#12530)
* rename docker-compose.yml.example to docker-compose_example.yml

* fix: dockle.yml
2023-12-01 11:19:33 +09:00
yupix
ca424df80e fix: invite系の戻り値が間違っている close #12517 (#12518) 2023-11-30 15:56:25 +09:00
Srgr0
e500fe2586 絵文字詳細ページに記載する情報を追加 (#12417)
* Update emojis.emoji.vue

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-30 14:59:42 +09:00
かっこかり
b05d71fabf feat(frontend): 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加 (#12450)
* (add) 今日誕生日のフォロイー一覧表示

* Update Changelog

* Update Changelog

* 実装漏れ

* create index

* (fix) index
2023-11-30 14:49:26 +09:00
yukineko
22d6fa1fdf enhance(dev): 開発モード時にlocaleと型定義が自動的に再生成されるように (#12481)
* enhance: localeを任意のタイミングでリビルドできるように

* enhance: localeも監視し、必要であればlocaleをリビルドするように

* feat: devモードの時のみナビゲーションバーからキャッシュクリアができるように

* refactor: キャッシュクリア部分を共通化

* fix: localesのファイル変更イベントが取れないのを修正

* fix: replaceAllでコケるのを修正

* change: 開発モードに関係なくナビゲーションバーからキャッシュクリアできるように

* refactor: 必要のないリビルドをしないように

* update: CHANGELOG.md

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-30 14:48:02 +09:00
Cocoa Hoto
4f6e098542 fix(docker): cannot build docker image on some environments (#12494) 2023-11-30 14:47:08 +09:00
Kisaragi
47a10f6a6d refactor(frontend): give local variable to explicit type annotation to avoid TS7043 (#12495)
* refactor: give local variable to explicit type annotation to avoid TS7043

* chore: fix lint error
2023-11-30 14:46:16 +09:00
GrapeApple0
28cb0fc70b enhance: 設定したタグをトレンドに表示させないようにする項目を管理画面で設定できるように (#12512)
* enhance: hiddenTagsを管理画面で設定できるように

* Update locales/ja-JP.yml

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

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-30 14:35:56 +09:00
zyoshoka
98e1af28b8 enhance(frontend): ノートプレビューにCWが反映されるように (#12509)
* enhance(frontend): ノートプレビューにCWが反映されるように

* Update CHANGELOG.md

* refactor: 不要な条件を除去

* Revert "refactor: 不要な条件を除去"

This reverts commit e4eff689bd.

* fix: やっぱり不要な条件だった
2023-11-30 13:49:31 +09:00
おさむのひと
413f7bfb44 Fix: navigator.share未サポートの場合は共有ボタンを非表示にする(+URLのコピーボタンを設置) (#12506)
* navigator.share未サポートの場合は共有ボタンを非表示にする

* fix CHANGELOG.md

* ライセンス表示追加

* URLのコピーボタンを設置
2023-11-30 08:15:13 +09:00
かっこかり
37cff405ed enhance(frontend): Shareページでの投稿完了時にpostMessageを発火するように (#12505)
* enhance(frontend): Shareページでの投稿完了時にpostMessageを発火

* Update Changelog

* fix

* 名前の混同をさける

* 名前をわかりやすくする

* watchを使わずパフォーマンス改善
2023-11-30 01:08:29 +09:00
anatawa12
c41d03018c ci: use refs/pull/*/merge to get head (#12508) 2023-11-30 01:06:11 +09:00
woxtu
ea1a2dc8db Update the Vitest configuration (#12493) 2023-11-29 10:41:11 +09:00
zyoshoka
d5deef5699 fix(frontend): WebKitブラウザー上でも「デバイスの画面を常にオンにする」機能が効くように (#12484)
* fix(frontend): WebKitブラウザー上でもkeepScreenOnが効くように

* chore: add comment
2023-11-29 10:29:24 +09:00
anatawa12
4e882414b2 fix: 音声が一切鳴らなくなる可能性がある (#12491)
* chore: 音声が一切鳴らなくなる可能性を軽減

https://github.com/misskey-dev/misskey/pull/12433#discussion_r1405774767

* chore: IIFEではなくPromise.prototype.finallyを使用するように
2023-11-29 10:29:00 +09:00
yupix
3b3b908ccd fix: packedNoteSchemaにclippedCountが抜けている (#12499) 2023-11-29 08:08:06 +09:00
おさむのひと
ec04c76ee5 通知グルーピング設定の即時反映対応 (#12485)
* wip

* ログ出しの削除

* fix CHANGELOG.md

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-28 20:43:25 +09:00
かっこかり
4e5b7768dc fix(docs): "docs(changelog): GHSA-3f39-6537-3cgc について追記 (#12482)" (#12483)
* Revert "docs(changelog): GHSA-3f39-6537-3cgc について追記 (#12482)"

This reverts commit d58ec4e65b.

* Update CHANGELOG.md
2023-11-27 21:41:19 +09:00
Kisaragi
d58ec4e65b docs(changelog): GHSA-3f39-6537-3cgc について追記 (#12482) 2023-11-27 21:37:37 +09:00
syuilo
2d0253bc42 2023.12.0-beta.1 2023-11-27 21:05:37 +09:00
syuilo
51cf906b25 update deps 2023-11-27 21:05:20 +09:00
かっこかり
2a451ebb57 enhance(frontend): 通知音にドライブのファイルを使用できるように (#12447)
* (enhance) サウンドにドライブのファイルを使用できるように

* Update Changelog

* fix

* fix design

* fix design

* Update store.ts

* (fix) ファイル名表示

* refactor

* (refactor) better types

* operationTypeとsoundTypeの混同を防止

* (refactor)

* (fix)

* enhance jsdoc

* driveFile -> _driveFile_
2023-11-27 17:33:42 +09:00
anatawa12
8f1da036f4 style: fix lint error of 6acaded8 (#12476) 2023-11-27 15:29:39 +09:00
anatawa12
6acaded898 fix: error can be happened if animation is on and hard mute matches (#12474) 2023-11-27 14:47:25 +09:00
woxtu
01d06e7121 Fix a frontend testing script (#12471) 2023-11-27 08:06:47 +09:00
ragujp
780b120c64 fix: wake lock error in safari etc (#12464) 2023-11-26 23:35:53 +09:00
Acid Chicken (硫酸鶏)
d60f645d1d chore(frontend/MkMediaVideo): loop and autoplay silent videos (#12392) 2023-11-26 16:15:24 +09:00
おさむのひと
c9503da8f8 サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加 (#12342)
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-26 16:12:02 +09:00
Acid Chicken (硫酸鶏)
ccb951f11e chore: create AudioContext when it is needed (#12460) 2023-11-26 14:38:34 +09:00
かっこかり
755ca97857 fix(frontend): 通知音がほぼ同時に鳴った場合は再生をブロックするように(音割れ防止) (#12433)
* (fix) 通知音がダブって音割れしないように

* Update Changelog
2023-11-26 13:20:46 +09:00
かっこかり
5bdae9f6d0 enhance(frontend): リアクション選択時に音を流せるように (#12441)
* (add) リアクション選択時に音を鳴らせるように

* Update Changelog

* tweak sound

* tweak sound

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-26 13:04:44 +09:00
anatawa12
d32631d159 fix: query error in notes/featured (#12439) 2023-11-26 12:54:23 +09:00
zyoshoka
2ee48ae04d fix(backend): ギャラリーの人気の投稿の選出にidを用いるように (#12448) 2023-11-26 10:05:56 +09:00
zyoshoka
7a494b2aa7 fix(backend): rename FunoutTimelineService to FanoutTimelineService (#12453) 2023-11-26 10:02:22 +09:00
zyoshoka
3e0231d995 fix(backend): 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正 (#12455)
* fix(backend): 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正

* Update CHANGELOG.md

* add test

* fix: incorrect bob's username
2023-11-26 10:01:06 +09:00
woxtu
c8b85a98b8 Add mocks for Web Audio API (#12457) 2023-11-26 09:54:24 +09:00
かっこかり
95095ee8d1 enhance(frontend): ユーザーのRawデータを読めるページを復活させる (#12436)
* (add) User raw page

* Update Changelog

* fix lint
2023-11-24 21:11:18 +09:00
syuilo
ccdb8ce7fc Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-11-24 20:25:36 +09:00
まっちゃとーにゅ
da3064343b enhance(frontend): 絵文字のオートコンプリートのアルゴリズムの改善 (MisskeyIO#261)
* 実際は同じ絵文字なら重複してサジェストに出ないように
* エイリアスではない絵文字>前方一致>部分一致>あいまい検索順で表示されるようになるように
2023-11-24 20:25:29 +09:00
yukineko
252efe8252 fix: 特定の条件下でチャンネルやユーザーのノート一覧に最新のノートが表示されなくなる問題を修正 (#12431)
* fix: 特定の条件下でチャンネルやユーザーのノート一覧に最新のノートが表示されなくなる問題を修正

* update: CHANGELOG.md
2023-11-24 20:19:46 +09:00
Camilla Ett
9c84055f50 Feat(frontend): コントロールパネル「通報」において、通報者もリンクで飛べるように修正 (#12427) 2023-11-24 16:19:37 +09:00
anatawa12
536f08c401 fix: hard mute limit not applied (#12428)
* fix: hard mute limit not applied

* Update CHANGELOG.md
2023-11-24 15:09:25 +09:00
woxtu
f7bdf5a2c0 Replace deprecated Repository.findOneById() (#12426) 2023-11-24 09:48:36 +09:00
syuilo
06ed64f26f update node to 20.10.0 2023-11-24 09:20:41 +09:00
syuilo
97c10ed1e5 Update index.d.ts 2023-11-24 09:20:34 +09:00
Camilla Ett
30b443de55 feat(frontend): リアクションの横幅を150pxに制限するかどうかユーザーが選べるように (#12416)
* feat(frontend): リアクションの横幅を150pxに制限するかどうかユーザーが選べるように

* localesの変更をjs-JP.ymlのみに修正し、日本語をより分かりやすく

* クラス名を.icon から .limitWidthに変更
2023-11-24 08:37:27 +09:00
mappi
521db37ca7 feat 12325 (#12425) 2023-11-24 08:36:55 +09:00
かっこかり
bf2d2ff0ca fix(frontend): プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正 (#12424)
* (fix) 招待コードを一度のみ利用できるように

* Update Changelog

* (fix) profile media grid

* Update Changelog

* Change Changelog
2023-11-23 21:18:24 +09:00
かっこかり
cba66c921e fix(frontend): コードエディタが正しく表示されない問題を修正 (#12418)
* (fix) コードエディタが正しく表示されない問題を修正

* Update Changelog
2023-11-23 20:37:41 +09:00
anatawa12
44a378c46e Use generate-api-json for api.json diff GitHub comment (#12408)
* ci: use generate-api-json to get api.json changes

* restore copying default.yml

* refactor: get api.json with single workflow

* ci: api.jsonのdiffをbackendが変更されたときのみ取るように
2023-11-23 20:17:21 +09:00
かっこかり
ed6f866a4f enhance/fix(AP/frontend): 最近追加されたMFMのもろもろを修正 (#12420)
* (enhance) MFM rubyが連合されるように

* Update Changelog

* Update Changelog

* (fix) unixtimeのフォールバック (AP)

* (fix) unixtimeのフォールバック (frontend)

* Update Changelog
2023-11-23 19:49:45 +09:00
かっこかり
4a2a44831b fix(backend): 招待コードが使い回せる問題を修正 (#12423)
* (fix) 招待コードを一度のみ利用できるように

* Update Changelog
2023-11-23 19:34:14 +09:00
anatawa12
864827f788 Hard mute (#12376)
* feat(backend,misskey-js): hard mute storage in backend

* fix(backend,misskey-js): mute word record type

* chore(frontend): generalize XWordMute

* feat(frontend): configure hard mute

* feat(frontend): hard mute notes on the timelines

* lint(backend,frontend): fix lint failure

* chore(misskey-js): update api.md

* fix(backend): test failure

* chore(frontend): check word mute for reply

* chore: limit hard mute count
2023-11-23 18:56:20 +09:00
おさむのひと
ded328fb43 絵文字のオートコンプリート強化の対応 (#12365)
* 前方一致・部分一致でなくても近似値でヒットするように

* fix CHANGELOG.md

* for of に変更

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-23 08:13:51 +09:00
syuilo
b15f293b82 fix lint, resolve code smell
Co-Authored-By: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2023-11-22 18:46:27 +09:00
おさむのひと
c284d41b5b swagger-cli validateがvalidとなるapi.jsonを作れるようにする (#12403)
* api.jsonがswagger-cli validateでエラーにならないように生成ロジックを修正

* フィールドの消し方に不備があったので変更

* バックエンドを起動しなくてもapi.jsonを作れるようにした

* deepCopyしてからレスポンス部分を作るようにした

* fix CHANGELOG.md

* securitySchemesの定義を復活&ApiCallServiceの実装的にベアラトークンなのでその形で

* bodyが無い(空オブジェクト)のときはrequestBodyを描画しないようにする

* allowGetがtrueな項目はget用の記載も作成

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-22 17:08:56 +09:00
taichan
a4f8863786 taichanne30 -> tai-cha (#12405) 2023-11-22 12:20:07 +09:00
y.takahashi
c6ed06d783 twitter埋め込みのsandbox属性にallow-popups-to-escape-sandboxを追加 (#12400)
Co-authored-by: unarist <m.unarist@gmail.com>
2023-11-22 10:19:30 +09:00
皐月なふ (Nafu Satsuki)
18bdec9641 fix: verifymail.io APIの設定項目が反映されない (#12399) 2023-11-22 10:13:46 +09:00
おさむのひと
4b13179ff9 サウンド再生方法の変更に追従できていなかった所を修正 (#12368)
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-21 20:05:04 +09:00
nenohi
481bca4cf2 広告掲載ページにてfilterをわかりやすく (#12385) 2023-11-21 19:50:06 +09:00
おさむのひと
b3d1cc9525 サーバ起動時にアンテナが非アクティブだった場合、アクティブ化しても再起動するまで反映されない (#12391)
* サーバ起動時にアンテナが非アクティブだった場合、アクティブ化しても再起動するまで反映されない

* Fix CHANGELOG.md

* lastUsedAtの更新に不備が出るので修正

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-21 15:32:34 +09:00
おさむのひと
b5be0e5780 note.tsのchannelを正しい形にしたことにより表出化した型チェックエラーを修正 (#12395)
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-21 15:12:05 +09:00
syuilo
77ac51a680 update typescript to 5.3 2023-11-21 11:32:13 +09:00
おさむのひと
8bd9077f77 json-schema配下の最新化 (#12312)
* user.ts、page.ts、drive-folder.tsを各EntityServiceの戻り値をもとに最新化

* 再確認

* fix error

* note以外の残りのファイルを対応

* fix CHANGELOG.md

* fix CHANGELOG.md

* fix user.ts

* fix user.ts

* コメント対応

* fix note.ts

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-21 11:13:56 +09:00
anatawa12
2ec3227012 update api.md (#12379)
for API changes in b65fd34981
2023-11-21 10:48:01 +09:00
syuilo
cd2131c4b5 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-11-21 09:55:52 +09:00
syuilo
ed0cc443ea fix(backend): ロールタイムラインが保存されない問題を修正 2023-11-21 09:55:49 +09:00
果物リン
e0de86359c backendのプロジェクトで単体で start できないのを修正 (#12371) 2023-11-19 13:39:25 +09:00
yukineko
02b0adf31f fix: 「設定のバックアップ」に一部の設定項目が含まれていない問題を修正 (#12366)
* fix: 一部の設定項目がバックアップに含まれていなかったのを修正

* update: CHANGELOG.md

* remove: バックアップ不要な項目を削除
2023-11-19 11:45:24 +09:00
Lynx Kotoura
cbebe85ccf ページ一覧ページの表示がモバイル環境において崩れているのを修正 (#12354)
* fix style of list of pages on mobile

* overflow clip に変えた
2023-11-19 11:43:04 +09:00
syuilo
b65fd34981 tweak of 2b6f789a5b 2023-11-19 10:18:57 +09:00
syuilo
2f7d10bf23 Update CHANGELOG.md 2023-11-18 21:08:32 +09:00
Nafu Satsuki
2b6f789a5b feat(moderation): モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (MisskeyIO#222)
Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
2023-11-18 21:07:47 +09:00
syuilo
30dc6e691d lint fix 2023-11-18 21:04:00 +09:00
syuilo
af668b15c4 Update CHANGELOG.md 2023-11-18 21:03:01 +09:00
Nafu Satsuki
0a73973a7c メールアドレスの認証にverifymail.ioを使えるようにする。 2023-11-18 21:01:53 +09:00
おさむのひと
83ea0395f6 DeepL TranslationのPro accountトグルスイッチが表示されていなかったのを修正 (#12355)
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-17 22:26:54 +09:00
syuilo
f007890e84 Revert "chore(frontend): tweak rt style for safari"
This reverts commit 76ccae8a2f.
2023-11-17 18:31:09 +09:00
syuilo
76ccae8a2f chore(frontend): tweak rt style for safari 2023-11-17 18:17:32 +09:00
syuilo
04709cf256 2023.11.1 2023-11-17 18:05:12 +09:00
syuilo
850b834758 New translations ja-jp.yml (Chinese Traditional) (#12351) 2023-11-17 18:04:42 +09:00
syuilo
08b3662bb8 chore(frontend): tweak ui 2023-11-17 18:00:42 +09:00
syuilo
4a7ccf6deb tweak MkTime.vue 2023-11-17 17:54:13 +09:00
syuilo
4b3f9bd9a6 enhance(backend): MFMのunixtimeをISO形式で連合するように 2023-11-17 15:44:36 +09:00
syuilo
5f5712a3ee fix(frontend): MFM unixtimeのプレビューがリアルタイムで反映されない
Fix #12350
2023-11-17 15:33:57 +09:00
syuilo
1518c5ddb0 2023.11.1-beta.2 2023-11-17 15:10:14 +09:00
syuilo
4f9922d46c update deps 2023-11-17 15:10:04 +09:00
syuilo
a9a743dab9 enhance(frontend): MFMでUNIX時間を指定して日時を表示できるように
Resolve #12294
2023-11-17 15:05:37 +09:00
syuilo
4d1a2bad17 typo 2023-11-17 13:27:33 +09:00
syuilo
cbab3affc9 Update packages/frontend/src/pages/role.vue
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-11-17 13:26:55 +09:00
syuilo
f89a827aa9 Update packages/frontend/src/pages/role.vue
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-11-17 13:26:48 +09:00
syuilo
43cb2d478c enhance(frontend): ruby内でMFMを使えるように 2023-11-17 13:20:40 +09:00
syuilo
b517d76084 enhance(frontend): MFMでルビを振れるように
Resolve #9161
2023-11-17 13:09:56 +09:00
syuilo
5ab7e03804 New Crowdin updates (#12348)
* New translations ja-jp.yml (Russian)

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)
2023-11-17 09:20:21 +09:00
syuilo
89389ad744 Update CHANGELOG.md 2023-11-16 16:03:17 +09:00
おさむのひと
1eb769dbe8 LTLに特定条件下でチャンネル投稿が混ざり込む現象の修正 (#12347)
* LTLにチャンネル投稿を含まないように修正

* fix CHANGELOG.md

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-16 16:02:46 +09:00
syuilo
9d78a1a8b3 enhance(backend): make ftt db fallback configurable 2023-11-16 10:20:57 +09:00
syuilo
838c70192e Update CHANGELOG.md 2023-11-15 18:04:26 +09:00
おさむのひと
38d6580a36 通知音などの発音方法を変え、iOSで音声出力が競合しないようにする (#12339)
* HTMLAudioElementを使わないようにする

* fix CHANGELOG.md

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-15 18:03:15 +09:00
syuilo
ca81f0ddbb fix(backend): 追加情報のカスタム絵文字がユーザー情報のtagに含まれない問題を修正
Fix #12316
2023-11-15 16:17:21 +09:00
syuilo
be6778ac61 chore(backend): improve performance 2023-11-15 16:10:05 +09:00
syuilo
7d24b29eb8 Update CHANGELOG.md 2023-11-15 15:51:01 +09:00
syuilo
44c0b5fd6f 2023.11.1-beta.1 2023-11-15 11:45:07 +09:00
syuilo
0aa3201480 New Crowdin updates (#12255)
* 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 (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (French)

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

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean)

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

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Turkish)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

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

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

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

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Turkish)
2023-11-15 11:44:21 +09:00
おさむのひと
96f1728573 fix: 表示済みのカラムから別のチャンネルを選択した時、タイムラインの内容が追従して変更されない問題に対処 (#12237)
* 表示済みのカラムから別のチャンネルを選択した時、タイムラインの内容が追従して変更されない問題に対処

* fix CHANGELOG.md

* fix code style

* Update MkTimeline.vue

* コメント対応(MkButtonのimportとonBeforeUpdate->watch)

* fix CHANGELOG.md

* Update MkPagination.vue

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-15 11:24:54 +09:00
atsuchan
6cc0685f2a Fix(frontend): サーバーサイレンス (#12292) 2023-11-15 11:14:27 +09:00
Jaehong Kang
04075ee0be enhance(backend): Implementation of HTTP header and body validation to fix SIF-2023-002 (#12334)
Using Buffer instead of string

Co-authored-by: perillamint <perillamint@silicon.moe>
2023-11-15 11:13:34 +09:00
zyoshoka
aa6d0d4359 fix(backend): 非公開の投稿に対して返信できないように (#12333)
* fix(backend): 非公開の投稿に対して返信できないように

* Update CHANGELOG.md

* fix: test
2023-11-15 11:10:45 +09:00
syuilo
3939360e55 fix(frontend): 特定の条件下でノートがnyaizeされない問題を修正
Fix #12331
2023-11-15 11:09:54 +09:00
syuilo
d790d658ad Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2023-11-15 11:07:13 +09:00
syuilo
5cb24e8470 update pnpm 2023-11-15 11:07:10 +09:00
dependabot[bot]
d2906d4628 build(deps): bump actions/github-script from 6 to 7 (#12328)
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

---
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>
2023-11-14 21:19:26 +09:00
syuilo
dd533eb948 Update CHANGELOG.md 2023-11-14 17:14:55 +09:00
syuilo
dfe4992e35 Update CHANGELOG.md 2023-11-14 17:10:49 +09:00
syuilo
65c5626b65 Merge pull request from GHSA-3f39-6537-3cgc
This commit implements HTTP header and body validation to fix
[SIF-2023-002](https://advisory.silicon.moe/advisory/sif-2023-002/)

Signed-off-by: perillamint <perillamint@silicon.moe>
Co-authored-by: perillamint <perillamint@silicon.moe>
Co-authored-by: yunochi <yuno@yunochi.com>
2023-11-14 17:09:45 +09:00
syuilo
30bb24d18c update deps 2023-11-14 10:35:48 +09:00
syuilo
2d2eefe3d4 feat: メールアドレスを使用してユーザー名を割り出す機能
Resolve #10158
2023-11-14 07:58:18 +09:00
Shun Sakai
a059dbe41b docs: Update Code of Conduct to version 2.1 (#12150) 2023-11-13 16:52:54 +09:00
Nya Candy
1361bdfbf2 fix: test break caused by #12273 (#12322)
* fix

* fix: websocket stream origin
2023-11-13 16:39:54 +09:00
atsuchan
31174d6b79 Fix(frontend): role users/timeline visiable ui (#12305) 2023-11-13 13:31:18 +09:00
Srgr0
c541ced3ab 非ログイン時に「メモを追加」を表示しない (#12310)
* Update home.vue

* Update CHANGELOG.md
2023-11-13 10:25:52 +09:00
おさむのひと
e1cc95d308 ユーザのノートのみ表示時にDBへフォールバックするとリノートを含んでしまうのを修正 (#12321)
* ユーザのノートのみ表示時にDBへフォールバックするとリノートを含んでしまうのを修正

* fix CHANGELOG.md

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-13 09:27:37 +09:00
zyoshoka
cec02966ad fix(frontend): 「フォロー中の人全員の返信を含める/含めないようにする」のボタンを押下した際の確認が機能していない問題を修正 (#12308)
* fix(frontend): 「フォロー中の人全員の返信を含める/含めないようにする」のボタンを押下した際の確認が機能していない問題を修正

* Update CHANGELOG.md
2023-11-11 16:34:46 +09:00
zyoshoka
0dd3cac8d9 fix(frontend): アイコンデコレーションが見切れる問題を修正 (#12239)
* fix(frontend): アイコンデコレーションがナビゲーションバーでクリップされないように

* Revert "fix(frontend): アイコンデコレーションがナビゲーションバーでクリップされないように"

This reverts commit db246b13d9.

* fix: tweak padding of account in navbar

* fix: set minimum height of note preview

* fix: リアクション一覧でアイコンデコレーションが見切れないように

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-11 16:07:07 +09:00
atsuchan
50430e310a Fix: latestRequestReceivedAt (#12270) 2023-11-11 14:51:29 +09:00
zyoshoka
f635a48f64 fix: 絵文字ピッカーでの検索が更新されない問題を修正 (#12283) 2023-11-10 18:48:31 +09:00
syuilo
689b88b942 fix(frontend): pull to refreshするたびにストリーミング全体の再接続が行われるのを修正 2023-11-10 17:52:50 +09:00
おさむのひと
253c0c42e2 デッキのカラムからリロードできる機能を追加 (#12274)
* デッキのカラムからリロードできる機能を追加

* tweak

---------

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-11-10 17:49:09 +09:00
ikasoba
54870d067b aiscript-vscodeのバージョンを更新 (#12299) 2023-11-10 17:18:23 +09:00
おさむのひと
7701bf0642 「この機能が解決すべき具体的な問題またはニーズ、および誰がそれに役立つと考えているかを説明してください。」という旨の項目を追加 (#12253)
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-10 15:08:12 +09:00
GrapeApple0
28e394eddc fix: 投稿通知がオンでもダイレクト投稿はユーザーに通知されないように (#12263)
* fix: 投稿通知がオンでもダイレクト投稿はユーザーに通知されないように

* Update CHANGELOG.md
2023-11-09 21:35:07 +09:00
Nya Candy
e2cac3d949 fix: show real instance url (#12273)
* Update CHANGELOG.md

* fix: show real instance url

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com>
Co-authored-by: Masaya Suzuki <15100604+massongit@users.noreply.github.com>
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>
Co-authored-by: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com>
Co-authored-by: nenohi <kimutipartylove@gmail.com>
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: rinsuki <428rinsuki+git@gmail.com>
Co-authored-by: FineArchs <133759614+FineArchs@users.noreply.github.com>
2023-11-09 21:21:39 +09:00
おさむのひと
879f2d2b7e ref化したnoteの変更通知がMfmコンポーネントまで到達してないのを修正 (#12282)
Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-09 21:15:48 +09:00
syuilo
b02f724475 Update CHANGELOG.md 2023-11-08 21:11:30 +09:00
zyoshoka
2834e54e78 fix(backend): make token nullable (#12280) 2023-11-08 21:10:41 +09:00
おさむのひと
828749be64 fix #12266 (#12267)
ポップアップの表示後、MkNoteとMkNoteDetailedでそれぞれが持つfocusメソッドを呼び出していたのをやめた

Co-authored-by: osamu <46447427+sam-osamu@users.noreply.github.com>
2023-11-06 19:26:17 +09:00
syuilo
bfca457510 enhance(frontend): improve aiscript plugin error handling 2023-11-06 11:21:43 +09:00
513 changed files with 45490 additions and 11673 deletions

View File

@@ -106,12 +106,16 @@ redis:
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
# host: meilisearch
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# scope: local
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
@@ -180,6 +184,9 @@ proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: true)
signToActivityPubGet: true
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]

View File

@@ -118,6 +118,9 @@ redis:
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
# host: localhost
# port: 7700
@@ -210,6 +213,9 @@ proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: true)
signToActivityPubGet: true
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]

View File

@@ -8,7 +8,7 @@
"version": "8.9.2"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "20.5.1"
"version": "20.10.0"
}
},
"forwardPorts": [3000],

View File

@@ -9,3 +9,9 @@ body:
description: Tell us what the suggestion is
validations:
required: true
- type: textarea
attributes:
label: Purpose
description: Describe the specific problem or need you think this feature will solve, and who it will help.
validations:
required: true

29
.github/labeler.yml vendored
View File

@@ -1,21 +1,34 @@
'packages/backend':
- packages/backend/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/backend/**/*']
'packages/backend:test':
- packages/backend/test/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/backend/test/**/*']
'packages/frontend':
- packages/frontend/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/frontend/**/*']
'packages/frontend:test':
- cypress/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['cypress/**/*']
'packages/sw':
- packages/sw/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/sw/**/*']
'packages/misskey-js':
- packages/misskey-js/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/misskey-js/**/*']
'packages/misskey-js:test':
- packages/misskey-js/test/**/*
- packages/misskey-js/test-d/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/misskey-js/test/**/*', 'packages/misskey-js/test-d/**/*']

View File

@@ -20,7 +20,7 @@ jobs:
sudo dpkg -i dockle.deb
- run: |
cp .config/docker_example.env .config/docker.env
cp ./docker-compose.yml.example ./docker-compose.yml
cp ./docker-compose_example.yml ./docker-compose.yml
- run: |
docker compose up -d web
docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest

View File

@@ -6,37 +6,30 @@ on:
branches:
- master
- develop
paths:
- packages/backend/**
- .github/workflows/get-api-diff.yml
jobs:
get-base:
get-from-misskey:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
node-version: [20.5.1]
services:
db:
image: postgres:13
ports:
- 5432:5432
env:
POSTGRES_DB: misskey
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_USER: example-misskey-user
POSTGRESS_PASS: example-misskey-pass
redis:
image: redis:7
ports:
- 6379:6379
node-version: [20.10.0]
api-json-name: [api-base.json, api-head.json]
include:
- api-json-name: api-base.json
ref: ${{ github.base_ref }}
- api-json-name: api-head.json
ref: refs/pull/${{ github.event.number }}/merge
steps:
- uses: actions/checkout@v4.1.1
with:
repository: ${{ github.event.pull_request.base.repo.full_name }}
ref: ${{ github.base_ref }}
ref: ${{ matrix.ref }}
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
@@ -56,121 +49,15 @@ jobs:
run: cp .config/example.yml .config/default.yml
- name: Build
run: pnpm build
- name : Migrate
run: pnpm migrate
- name: Launch misskey
run: |
screen -S misskey -dm pnpm run dev
sleep 30s
- name: Wait for Misskey to be ready
run: |
MAX_RETRIES=12
RETRY_DELAY=5
count=0
until $(curl --output /dev/null --silent --head --fail http://localhost:3000) || [[ $count -eq $MAX_RETRIES ]]; do
printf '.'
sleep $RETRY_DELAY
count=$((count + 1))
done
if [[ $count -eq $MAX_RETRIES ]]; then
echo "Failed to connect to Misskey after $MAX_RETRIES attempts."
exit 1
fi
- id: fetch
name: Get api.json from Misskey
run: |
RESULT=$(curl --retry 5 --retry-delay 5 --retry-max-time 60 http://localhost:3000/api.json)
echo $RESULT > api-base.json
- name: Generate API JSON
run: pnpm --filter backend generate-api-json
- name: Copy API.json
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: api-artifact
path: api-base.json
- name: Kill Misskey Job
run: screen -S misskey -X quit
get-head:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
node-version: [20.5.1]
services:
db:
image: postgres:13
ports:
- 5432:5432
env:
POSTGRES_DB: misskey
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_USER: example-misskey-user
POSTGRESS_PASS: example-misskey-pass
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4.1.1
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.head_ref }}
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .config/example.yml .config/default.yml
- name: Build
run: pnpm build
- name : Migrate
run: pnpm migrate
- name: Launch misskey
run: |
screen -S misskey -dm pnpm run dev
sleep 30s
- name: Wait for Misskey to be ready
run: |
MAX_RETRIES=12
RETRY_DELAY=5
count=0
until $(curl --output /dev/null --silent --head --fail http://localhost:3000) || [[ $count -eq $MAX_RETRIES ]]; do
printf '.'
sleep $RETRY_DELAY
count=$((count + 1))
done
if [[ $count -eq $MAX_RETRIES ]]; then
echo "Failed to connect to Misskey after $MAX_RETRIES attempts."
exit 1
fi
- id: fetch
name: Get api.json from Misskey
run: |
RESULT=$(curl --retry 5 --retry-delay 5 --retry-max-time 60 http://localhost:3000/api.json)
echo $RESULT > api-head.json
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: api-artifact
path: api-head.json
- name: Kill Misskey Job
run: screen -S misskey -X quit
path: ${{ matrix.api-json-name }}
save-pr-number:
runs-on: ubuntu-latest

View File

@@ -11,6 +11,6 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -13,7 +13,7 @@ jobs:
github.event.client_payload.slash_command.sha != '' &&
contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
steps:
- uses: actions/github-script@v6.3.3
- uses: actions/github-script@v7
id: check-id
env:
number: ${{ github.event.client_payload.pull_request.number }}
@@ -37,7 +37,7 @@ jobs:
return check[0].id;
- uses: actions/github-script@v6.3.3
- uses: actions/github-script@v7
env:
check_id: ${{ steps.check-id.outputs.result }}
details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
@@ -72,7 +72,7 @@ jobs:
timeout: 15m
# Update check run called "integration-fork"
- uses: actions/github-script@v6.3.3
- uses: actions/github-script@v7
id: update-check-run
if: ${{ always() }}
env:

View File

@@ -10,7 +10,7 @@ jobs:
destroy-preview-environment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6.3.3
- uses: actions/github-script@v7
id: check-conclusion
env:
number: ${{ github.event.number }}

View File

@@ -16,7 +16,7 @@ jobs:
# api-artifact
steps:
- name: Download artifact
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({

View File

@@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [20.5.1]
node-version: [20.10.0]
services:
postgres:

View File

@@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [20.5.1]
node-version: [20.10.0]
steps:
- uses: actions/checkout@v4.1.1
@@ -51,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20.5.1]
node-version: [20.10.0]
browser: [chrome]
services:

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [20.5.1]
node-version: [20.10.0]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [20.5.1]
node-version: [20.10.0]
steps:
- uses: actions/checkout@v4.1.1

View File

@@ -1 +1 @@
20.5.1
20.10.0

View File

@@ -5,13 +5,104 @@
-
### Client
-
- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正
- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正
### Server
-
-->
## 2023.x.x (unreleased)
### General
- Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed)
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
- Feat: TL上からートが見えなくなるワードミュートであるハードミュートを追加
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
### Client
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
- Feat: データセーバーでコードハイライトの読み込みを削減できるように
- Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336
- Enhance: 絵文字のオートコンプリート機能強化 #12364
- Enhance: ユーザーのRawデータを表示するページが復活
- Enhance: リアクション選択時に音を鳴らせるように
- Enhance: サウンドにドライブのファイルを使用できるように
- Enhance: ナビゲーションバーに項目「キャッシュを削除」を追加
- Enhance: Shareページで投稿を完了すると、親ウィンドウ親フレームにpostMessageするように
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
- Enhance: ノートプレビューに「内容を隠す」が反映されるように
- Enhance: データセーバーの適用範囲を個別で設定できるように
- 従来のデータセーバーの設定はリセットされます
- Feat: センシティブと判断されたウェブサイトのサムネイルをぼかすように
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Enhance: 絵文字の詳細ページに記載される情報を追加
- Fix: コードエディタが正しく表示されない問題を修正
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
- Fix: 共有機能をサポートしていないブラウザの場合は共有ボタンを非表示にする #11305
- Fix: 通知のグルーピング設定を変更してもリロードされるまで表示が変わらない問題を修正 #12470
- Fix: 長い名前のチャンネルにおける投稿フォームの表示が崩れる問題を修正
- Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化
### Server
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
- Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
- Fix: ロールタイムラインが保存されない問題を修正
- Fix: api.jsonの生成ロジックを改善 #12402
- Fix: 招待コードが使い回せる問題を修正
- Fix: 特定の条件下でチャンネルやユーザーのノート一覧に最新のノートが表示されなくなる問題を修正
- Fix: 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正
- Fix: リストタイムラインにてミュートが機能しないケースがある問題と、チャンネル投稿がストリーミングで流れてきてしまう問題を修正 #10443
- Fix: 「みつける」のなかにミュートしたユーザが現れてしまう問題を修正 #12383
- Fix: Social/Local/Home Timelineにてインスタンスミュートが効かない問題
- Fix: ユーザのノート一覧にてインスタンスミュートが効かない問題
- Fix: チャンネルのノート一覧にてインスタンスミュートが効かない問題
- Fix: 「みつける」が年越し時に壊れる問題を修正
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
## 2023.11.1
### Note
- 悪意のある第三者がリモートユーザーになりすました任意のアクティビティを受け取れてしまう問題を修正しました。詳しくは[GitHub security advisory](https://github.com/misskey-dev/misskey/security/advisories/GHSA-3f39-6537-3cgc)をご覧ください。
### General
- Feat: 管理者がコントロールパネルからメールアドレスの照会を行えるようになりました
- Enhance: ローカリゼーションの更新
- Enhance: 依存関係の更新
- Enhance: json-schema(OpenAPIの戻り値として使用されるスキーマ定義)を出来る限り最新化 #12311
### Client
- Enhance: MFMでルビを振れるように
- 例: `$[ruby 三須木 みすき]`
- Enhance: MFMでUNIX時間を指定して日時を表示できるように
- 例: `$[unixtime 1701356400]`
- Enhance: プラグインでエラーが発生した場合のハンドリングを強化
- Enhance: 細かなUIのブラッシュアップ
- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加
- Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339
- Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236
- Fix: プラグインでノートの表示を書き換えられない問題を修正
- Fix: アイコンデコレーションが見切れる場合がある問題を修正
- Fix: 「フォロー中の人全員の返信を含める/含めないようにする」のボタンを押下した際の確認が機能していない問題を修正
- Fix: 非ログイン時に「メモを追加」を表示しないように変更 #12309
- Fix: 絵文字ピッカーでの検索が更新されない問題を修正
- Fix: 特定の条件下でートがnyaizeされない問題を修正
### Server
- Enhance: FTTのデータベースへのフォールバック処理を行うかどうかを設定可能に
- Fix: トークンのないプラグインをアンインストールするときにエラーが出ないように
- Fix: 投稿通知がオンでもダイレクト投稿はユーザーに通知されないようにされました
- Fix: ユーザタイムラインの「ノート」選択時にリノートが混ざり込んでしまうことがある問題の修正 #12306
- Fix: LTLに特定条件下にてチャンネルへの投稿が混ざり込む現象を修正
- Fix: ActivityPub: 追加情報のカスタム絵文字がユーザー情報のtagに含まれない問題を修正
- Fix: ActivityPubに関するセキュリティの向上
- Fix: 非公開の投稿に対して返信できないように
## 2023.11.0
### Note
@@ -102,6 +193,7 @@
### Client
- Enhance: TLの返信表示オプションを記憶するように
- Enhance: 投稿されてから時間が経過しているノートであることを視覚的に分かりやすく
- Feat: 絵文字ピッカーのカテゴリに「/」を入れることでフォルダ分け表示できるように
### Server
- Enhance: タイムライン取得時のパフォーマンスを向上

View File

@@ -2,45 +2,131 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
Examples of behavior that contributes to a positive environment for our
community include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior by participants include:
Examples of unacceptable behavior include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
## Enforcement Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at syuilotan@yahoo.co.jp. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<syuilotan@yahoo.co.jp>.
All complaints will be reviewed and investigated promptly and fairly.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -117,6 +117,10 @@ command.
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
- Service Worker is watched by esbuild.
- The front end can be viewed by accessing `http://localhost:5173`.
- The backend listens on the port configured with `port` in .config/default.yml.
If you have not changed it from the default, it will be "http://localhost:3000".
If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts.
### Dev Container
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.4
ARG NODE_VERSION=20.5.1-bullseye
ARG NODE_VERSION=20.10.0-bullseye
# build assets & compile TypeScript
@@ -67,8 +67,8 @@ RUN apt-get update \
&& corepack enable \
&& groupadd -g "${GID}" misskey \
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \
&& find / -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \
&& find / -type d -path /proc -prune -o -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \
&& find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \
&& find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists

View File

@@ -0,0 +1,42 @@
version: "3"
# このconfigは、 dockerでMisskey本体を起動せず、 redisとpostgresql などだけを起動します
services:
redis:
restart: always
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- ./redis:/data
healthcheck:
test: "redis-cli ping"
interval: 5s
retries: 20
db:
restart: always
image: postgres:15-alpine
ports:
- "5432:5432"
env_file:
- .config/docker.env
volumes:
- ./db:/var/lib/postgresql/data
healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s
retries: 20
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4
# environment:
# - MEILI_NO_ANALYTICS=true
# - MEILI_ENV=production
# env_file:
# - .config/meilisearch.env
# volumes:
# - ./meili_data:/meili_data

View File

@@ -45,6 +45,7 @@ pin: "Fixar al perfil"
unpin: "Para de fixar del perfil"
copyContent: "Copiar el contingut"
copyLink: "Copiar l'enllaç"
copyLinkRenote: "Copiar l'enllaç de la renota"
delete: "Elimina"
deleteAndEdit: "Elimina i edita"
deleteAndEditConfirm: "Segur que vols eliminar aquesta publicació i editar-la? Perdràs totes les reaccions, impulsos i respostes."
@@ -110,6 +111,8 @@ renoted: "S'ha impulsat"
cantRenote: "No es pot impulsar aquesta publicació"
cantReRenote: "No es pot impulsar l'impuls."
quote: "Cita"
inChannelRenote: "Renotar només al Canal"
inChannelQuote: "Citar només al Canal"
pinnedNote: "Nota fixada"
pinned: "Fixar al perfil"
you: "Tu"
@@ -127,6 +130,8 @@ unmarkAsSensitive: "Deixar de marcar com a sensible"
enterFileName: "Defineix nom del fitxer"
mute: "Silencia"
unmute: "Deixa de silenciar"
renoteMute: "Silenciar Renotes"
renoteUnmute: "Treure el silenci de les renotes"
block: "Bloqueja"
unblock: "Desbloqueja"
suspend: "Suspèn"
@@ -136,6 +141,8 @@ unblockConfirm: "Vols desbloquejar-lo?"
suspendConfirm: "Estàs segur que vols suspendre aquest compte?"
unsuspendConfirm: "Estàs segur que vols treure la suspensió d'aquest compte?"
selectList: "Tria una llista"
editList: "Editar llista"
selectChannel: "Selecciona un canal"
selectAntenna: "Tria una antena"
editAntenna: "Modificar antena"
selectWidget: "Triar un giny"

View File

@@ -564,6 +564,10 @@ output: "Output"
script: "Script"
disablePagesScript: "Disable AiScript on Pages"
updateRemoteUser: "Update remote user information"
unsetUserAvatar: "Delete user icon"
unsetUserAvatarConfirm: "Are you sure that you want to delete this user's icon?"
unsetUserBanner: "Delete user banner"
unsetUserBannerConfirm: "Are you sure that you want to delete this user's banner?"
deleteAllFiles: "Delete all files"
deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
removeAllFollowing: "Unfollow all followed users"
@@ -1169,6 +1173,8 @@ _announcement:
readConfirmText: "This will mark the contents of \"{title}\" as read."
shouldNotBeUsedToPresentPermanentInfo: "As it may significantly impact the user experience for new users, it is recommended to use notifications in the flow information rather than stock information."
dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully."
silence: "No notification"
silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it."
_initialAccountSetting:
accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile."

View File

@@ -616,9 +616,9 @@ notificationType: "Type de notifications"
edit: "Editer"
emailServer: "Serveur de messagerie"
enableEmail: "Activer la distribution de courriel"
emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas doubli."
emailConfigInfo: "Utilisé pour confirmer votre adresse e-mail et réinitialiser votre mot de passe en cas doubli"
email: "E-mail "
emailAddress: "Adresses e-mail"
emailAddress: "Adresse e-mail"
smtpConfig: "Paramètres du serveur SMTP"
smtpHost: "Serveur distant"
smtpPort: "Port"
@@ -764,7 +764,7 @@ inUse: "utilisé"
editCode: "Modifier le code"
apply: "Appliquer"
receiveAnnouncementFromInstance: "Recevoir les messages d'information de l'instance"
emailNotification: "Notifications par mail"
emailNotification: "Notifications par courriel"
publish: "Public"
inChannelSearch: "Chercher dans le canal"
useReactionPickerForContextMenu: "Clic-droit pour ouvrir le panneau de réactions"
@@ -946,6 +946,7 @@ unsubscribePushNotification: "Désactiver les notifications push"
pushNotificationAlreadySubscribed: "Les notifications push sont déjà activées"
pushNotificationNotSupported: "Votre navigateur ou votre instance ne prend pas en charge les notifications push"
sendPushNotificationReadMessage: "Supprimer les notifications push une fois que les notifications ou messages pertinents ont été lus."
sendPushNotificationReadMessageCaption: "Cela peut augmenter la consommation de batterie de votre appareil."
windowMaximize: "Maximiser"
windowMinimize: "Minimaliser"
windowRestore: "Restaurer"
@@ -986,6 +987,7 @@ thisPostMayBeAnnoyingIgnore: "Publier quand-même"
collapseRenotes: "Réduire les renotes déjà vues"
internalServerError: "Erreur interne du serveur"
copyErrorInfo: "Copier les détails de lerreur"
joinThisServer: "S'inscrire à cette instance"
exploreOtherServers: "Trouver une autre instance"
disableFederationOk: "Désactiver"
postToTheChannel: "Publier au canal"
@@ -996,6 +998,7 @@ license: "Licence"
myClips: "Mes clips"
retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur."
showClipButtonInNoteFooter: "Ajouter « Clip » au menu d'action de la note"
reactionsDisplaySize: "Taille de l'affichage des réactions"
noteIdOrUrl: "Identifiant de la note ou URL"
video: "Vidéo"
videos: "Vidéos"
@@ -1022,6 +1025,7 @@ pleaseAgreeAllToContinue: "Pour continuer, veuillez accepter tous les champs ci-
continue: "Continuer"
preservedUsernames: "Noms d'utilisateur·rice réservés"
archive: "Archive"
thisChannelArchived: "Ce canal a été archivé."
displayOfNote: "Affichage de la note"
initialAccountSetting: "Configuration initiale du profil"
youFollowing: "Abonné·e"
@@ -1033,6 +1037,7 @@ failedToPreviewUrl: "Aperçu d'URL échoué"
update: "Mettre à jour"
later: "Plus tard"
goToMisskey: "Retour vers Misskey"
installed: "Installé"
expirationDate: "Date dexpiration"
waitingForMailAuth: "En attente de la vérification de l'adresse courriel"
usedAt: "Utilisé le"
@@ -1049,6 +1054,7 @@ pastAnnouncements: "Annonces passées"
replies: "Répondre"
renotes: "Renoter"
loadReplies: "Inclure les réponses"
loadConversation: "Afficher la conversation"
pinnedList: "Liste épinglée"
notifyNotes: "Notifier à propos des nouvelles notes"
authentication: "Authentification"
@@ -1082,15 +1088,24 @@ refreshing: "Rafraîchissement..."
pullDownToRefresh: "Tirer vers le bas pour rafraîchir"
disableStreamingTimeline: "Désactiver les mises à jour en temps réel de la ligne du temps"
useGroupedNotifications: "Grouper les notifications"
signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré."
cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie."
doReaction: "Réagir"
_announcement:
readConfirmTitle: "Marquer comme lu ?"
shouldNotBeUsedToPresentPermanentInfo: "Puisque cela pourrait nuire considérablement à l'expérience utilisateur pour les nouveaux utilisateurs, il est recommandé d'utiliser les annonces pour afficher des informations temporaires plutôt que des informations persistantes."
dialogAnnouncementUxWarn: "Avoir deux ou plus annonces de style dialogue en même temps pourrait nuire considérablement à l'expérience utilisateur. Veuillez les utiliser avec caution."
silence: "Ne pas me notifier"
silenceDescription: "Si activée, vous ne recevrez pas de notifications sur les annonces et n'aurez pas besoin de les marquer comme lues."
_initialAccountSetting:
profileSetting: "Paramètres du profil"
privacySetting: "Paramètres de confidentialité"
initialAccountSettingCompleted: "Configuration du profil terminée avec succès !"
youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {nom}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement."
startTutorial: "Démarrer le tutoriel"
skipAreYouSure: "Désirez-vous ignorer la configuration du profil ?"
_initialTutorial:
launchTutorial: "Visionner le tutoriel"
title: "Tutoriel"
wellDone: "Bien joué !"
skipAreYouSure: "Quitter le tutoriel ?"
@@ -1108,9 +1123,62 @@ _initialTutorial:
title: "Qu'est-ce que les réactions ?"
description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime."
letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « + » de la note. Essayez d'ajouter une réaction à cet exemple de note !"
reactToContinue: "Ajoutez une réaction pour procéder."
reactNotification: "Vous recevez des notifications en temps réel lorsque quelqu'un réagit à votre note."
reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « - » ."
_timeline:
title: "Fonctionnement des fils"
description1: "Misskey offre plusieurs fils selon l'usage (certains peuvent être désactivés par le serveur)."
home: "Vous pouvez voir les notes des utilisateurs auxquels vous êtes abonné·e."
local: "Vous pouvez voir les notes de tous les utilisateurs sur cette instance."
social: "Les notes des fils principal et local sont affichées."
global: "Vous pouvez voir les notes de toutes les instances connectées."
description2: "Vous pouvez passer d'un fil à l'autre en haut de l'écran à tout moment."
description3: "De plus, il y a les fils des listes et des canaux. Pour plus de détails, consultez {link}."
_postNote:
title: "Paramètres de la publication de note"
description1: "Lorsque vous publiez des notes sur Misskey, diverses options sont disponibles. Voici le formulaire de publication."
_visibility:
description: "Vous pouvez choisir qui peut voir vos notes."
public: "Visible à tous les utilisateurs."
home: "Uniquement visible sur le fil principal. Les utilisateurs pourront la voir en visitant ton profil, en s'abonnant à vous et par les renotes."
followers: "Uniquement visible à vos abonnés. Elle ne pourra être renotée que par vous-même."
direct: "Uniquement visible aux utilisateurs de votre choix. Les récipients seront notifiés. Cette option peut être utilisée comme alternative aux messages directs."
doNotSendConfidencialOnDirect1: "Faites attention quand vous envoyez vos informations sensibles !"
doNotSendConfidencialOnDirect2: "Les administrateurs de l'instance destinataire peuvent voir toutes les notes publiées. Soyez prudent·e avec vos informations sensibles quand vous envoyez des notes directes aux utilisateurs dont vous ne vous fiez pas aux instances."
localOnly: "Désactiver la fédération de la note aux autres instances. Les utilisateurs des autres instances ne pourront pas voir directement la note quelle que soit l'étendue de la publication mentionnée ci-dessus."
_cw:
title: "Masquer le contenu (CW)"
description: "Au lieu du corps du texte, le contenu du champ « commentaires » s'affichera. Appuyez sur « afficher le contenu » pour voir le corps du texte."
_exampleNote:
cw: "Attention : cela vous donnera faim !"
note: "J'ai mangé un beignet enrobé de chocolat 🍩😋"
useCases: "Utilisé pour désigner certaines notes selon les règles du serveur ou pour cacher des spoilers ou des textes sensibles."
_howToMakeAttachmentsSensitive:
title: "Comment marquer un fichier joint comme sensible ?"
description: "Attachez un drapeau « sensible » aux fichiers joints selon les règles du serveur ou si vous ne voulez pas que le fichier soit vu directement."
tryThisFile: "Essayez de marquer l'image jointe à ce formulaire de publication comme sensible !"
_exampleNote:
note: "Oups, j'ai échoué à ouvrir le couvercle du natto..."
method: "Pour marquer un fichier joint comme sensible, cliquez sur la vignette du fichier, ouvrez le menu et cliquez sur « marquer comme sensible » ."
sensitiveSucceeded: "Quand vous joignez des fichiers, veuillez indiquer la sensibilité selon les règles du serveur."
doItToContinue: "Marquez le fichier joint comme sensible pour procéder."
_done:
title: "Le tutoriel est terminé ! 🎉"
description: "Les fonctionnalités introduites ici ne sont que quelques-unes. Pour savoir plus sur l'utilisation de Misskey, veuillez consulter {lien}."
_timelineDescription:
home: "Sur le fil principal, vous pouvez voir les notes des utilisateurs auxquels vous êtes abonné·e."
local: "Sur le fil local, vous pouvez voir les notes de tous les utilisateurs sur cette instance."
social: "Sur le fil social, les notes des fils principal et local sont affichées."
global: "Sur le fil global, vous pouvez voir les notes de toutes les instances connectées."
_serverSettings:
iconUrl: "URL de licône"
appIconResolutionMustBe: "La résolution doit être au moins {resolution}."
shortName: "Nom court"
shortNameDescription: "Si le nom officiel de l'instance est long, cette abréviation peut être affichée à la place."
fanoutTimelineDescription: "Si activée, la performance de la récupération de la chronologie augmentera considérablement et la charge sur la base de données sera réduite. En revanche, l'utilisation de la mémoire de Redis augmentera. Considérez désactiver cette option si le serveur est bas en mémoire ou instable."
fanoutTimelineDbFallback: "Recours à la base de données"
fanoutTimelineDbFallbackDescription: "Si activée, une demande supplémentaire à la base de données est effectuée comme solution de rechange quand le fil n'est pas mis en cache. Si désactivée, la demande à la base de données n'est pas effectuée, ce qui réduit davantage la charge du serveur mais limite l'étendue du fil récupérable."
_accountMigration:
moveFrom: "Migrer un autre compte vers le présent compte"
moveFromSub: "Créer un alias vers un autre compte"
@@ -1221,7 +1289,7 @@ _achievements:
_viewInstanceChart:
title: "Analyste"
_outputHelloWorldOnScratchpad:
title: "Bonjour tout le monde !"
title: "Hello, world!"
_open3windows:
title: "Multi-fenêtres"
_driveFolderCircularReference:
@@ -1243,6 +1311,12 @@ _achievements:
flavor: "Attendez une minute, vous êtes sur le mauvais site web ?"
_brainDiver:
flavor: "Misskey-Misskey La-Tu-Ma"
_smashTestNotificationButton:
title: "Débordement de tests"
description: "Détruire le bouton de test de notifications dans un intervalle extrêmement court"
_tutorialCompleted:
title: "Diplôme de la course élémentaire de Misskey"
description: "Terminer le tutoriel"
_role:
new: "Nouveau rôle"
edit: "Modifier le rôle"
@@ -1268,6 +1342,7 @@ _role:
canManageCustomEmojis: "Gestion des émojis personnalisés"
canManageAvatarDecorations: "Gestion des décorations d'avatar"
wordMuteMax: "Nombre maximal de caractères dans le filtre de mots"
canUseTranslator: "Usage de la fonctionnalité de traduction"
_sensitiveMediaDetection:
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
sensitivity: "Sensibilité de la détection"
@@ -1304,7 +1379,7 @@ _ad:
adsSettings: "Paramètres des publicités"
notesPerOneAd: "Intervalle de diffusion de publicités lors de la mise à jour en temps réel (nombre de notes par publicité)"
setZeroToDisable: "Mettre cette valeur à 0 pour désactiver la diffusion de publicités lors de la mise à jour en temps réel"
adsTooClose: "L'expérience de l'utilisateur peut être gravement compromise par un intervalle de diffusion de publicités extrêmement court."
adsTooClose: "L'expérience utilisateur peut être gravement compromise par un intervalle de diffusion de publicités extrêmement court."
_forgotPassword:
enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte. Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette adresse."
ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice de votre instance."
@@ -1380,6 +1455,7 @@ _channel:
notesCount: "{n} Notes"
nameAndDescription: "Nom et description"
nameOnly: "Nom seulement"
allowRenoteToExternal: "Permettre la renote et la citation hors du canal"
_menuDisplay:
sideFull: "Latéral"
sideIcon: "Latéral (icônes)"
@@ -1585,7 +1661,7 @@ _widgets:
chooseList: "Sélectionner une liste"
_cw:
hide: "Masquer"
show: "Afficher plus …"
show: "Afficher le contenu"
chars: "{count} caractères"
files: "{count} fichiers"
_poll:
@@ -1754,6 +1830,7 @@ _notification:
unreadAntennaNote: "Antenne {name}"
emptyPushNotificationMessage: "Les notifications push ont été mises à jour"
achievementEarned: "Accomplissement"
testNotification: "Tester la notification"
reactedBySomeUsers: "{n} utilisateur·rice·s ont réagi"
renotedBySomeUsers: "{n} utilisateur·rice·s ont renoté"
followedBySomeUsers: "{n} utilisateur·rice·s se sont abonné·e·s à vous"
@@ -1798,7 +1875,7 @@ _deck:
tl: "Fil"
antenna: "Antennes"
list: "Listes"
channel: "Canaux"
channel: "Canal"
mentions: "Mentions"
direct: "Direct"
_webhookSettings:

View File

@@ -56,6 +56,18 @@ export default function generateDTS() {
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
),
),
ts.factory.createFunctionDeclaration(
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
undefined,
ts.factory.createIdentifier('build'),
undefined,
[],
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('Locale'),
undefined,
),
undefined,
),
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
];
const printed = ts.createPrinter({

58
locales/index.d.ts vendored
View File

@@ -314,6 +314,7 @@ export interface Locale {
"createFolder": string;
"renameFolder": string;
"deleteFolder": string;
"folder": string;
"addFile": string;
"emptyDrive": string;
"emptyFolder": string;
@@ -440,7 +441,6 @@ export interface Locale {
"notFound": string;
"notFoundDescription": string;
"uploadFolder": string;
"cacheClear": string;
"markAsReadAllNotifications": string;
"markAsReadAllUnreadNotes": string;
"markAsReadAllTalkMessages": string;
@@ -547,6 +547,8 @@ export interface Locale {
"popout": string;
"volume": string;
"masterVolume": string;
"notUseSound": string;
"useSoundOnlyWhenActive": string;
"details": string;
"chooseEmoji": string;
"unableToProcess": string;
@@ -567,6 +569,10 @@ export interface Locale {
"script": string;
"disablePagesScript": string;
"updateRemoteUser": string;
"unsetUserAvatar": string;
"unsetUserAvatarConfirm": string;
"unsetUserBanner": string;
"unsetUserBannerConfirm": string;
"deleteAllFiles": string;
"deleteAllFilesConfirm": string;
"removeAllFollowing": string;
@@ -638,6 +644,7 @@ export interface Locale {
"smtpSecureInfo": string;
"testEmail": string;
"wordMute": string;
"hardWordMute": string;
"regexpError": string;
"regexpErrorDescription": string;
"instanceMute": string;
@@ -1023,6 +1030,8 @@ export interface Locale {
"sensitiveWords": string;
"sensitiveWordsDescription": string;
"sensitiveWordsDescription2": string;
"hiddenTags": string;
"hiddenTagsDescription": string;
"notesSearchNotAvailable": string;
"license": string;
"unfavoriteConfirm": string;
@@ -1035,6 +1044,7 @@ export interface Locale {
"enableChartsForFederatedInstances": string;
"showClipButtonInNoteFooter": string;
"reactionsDisplaySize": string;
"limitWidthOfReaction": string;
"noteIdOrUrl": string;
"video": string;
"videos": string;
@@ -1161,6 +1171,8 @@ export interface Locale {
"signupPendingError": string;
"cwNotationRequired": string;
"doReaction": string;
"code": string;
"reloadRequiredToApplySettings": string;
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;
@@ -1285,6 +1297,8 @@ export interface Locale {
"shortName": string;
"shortNameDescription": string;
"fanoutTimelineDescription": string;
"fanoutTimelineDbFallback": string;
"fanoutTimelineDbFallbackDescription": string;
};
"_accountMigration": {
"moveFrom": string;
@@ -1635,7 +1649,9 @@ export interface Locale {
"assignTarget": string;
"descriptionOfAssignTarget": string;
"manual": string;
"manualRoles": string;
"conditional": string;
"conditionalRoles": string;
"condition": string;
"isConditionalRole": string;
"isPublic": string;
@@ -1933,6 +1949,15 @@ export interface Locale {
"notification": string;
"antenna": string;
"channel": string;
"reaction": string;
};
"_soundSettings": {
"driveFile": string;
"driveFileWarn": string;
"driveFileTypeWarn": string;
"driveFileTypeWarnDescription": string;
"driveFileDurationWarn": string;
"driveFileDurationWarnDescription": string;
};
"_ago": {
"future": string;
@@ -1946,6 +1971,15 @@ export interface Locale {
"yearsAgo": string;
"invalid": string;
};
"_timeIn": {
"seconds": string;
"minutes": string;
"hours": string;
"days": string;
"weeks": string;
"months": string;
"years": string;
};
"_time": {
"second": string;
"minute": string;
@@ -2078,6 +2112,7 @@ export interface Locale {
"chooseList": string;
};
"clicker": string;
"birthdayFollowings": string;
};
"_cw": {
"hide": string;
@@ -2402,6 +2437,8 @@ export interface Locale {
"createAvatarDecoration": string;
"updateAvatarDecoration": string;
"deleteAvatarDecoration": string;
"unsetUserAvatar": string;
"unsetUserBanner": string;
};
"_fileViewer": {
"title": string;
@@ -2467,8 +2504,27 @@ export interface Locale {
};
};
};
"_dataSaver": {
"_media": {
"title": string;
"description": string;
};
"_avatar": {
"title": string;
"description": string;
};
"_urlPreview": {
"title": string;
"description": string;
};
"_code": {
"title": string;
"description": string;
};
};
}
declare const locales: {
[lang: string]: Locale;
};
export function build(): Locale;
export default locales;

View File

@@ -51,33 +51,37 @@ const primaries = {
// 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {});
export function build() {
const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {});
// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
const removeEmpty = (obj) => {
for (const [k, v] of Object.entries(obj)) {
if (v === '') {
delete obj[k];
} else if (typeof v === 'object') {
removeEmpty(v);
// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
const removeEmpty = (obj) => {
for (const [k, v] of Object.entries(obj)) {
if (v === '') {
delete obj[k];
} else if (typeof v === 'object') {
removeEmpty(v);
}
}
}
return obj;
};
removeEmpty(locales);
return obj;
};
removeEmpty(locales);
export default Object.entries(locales)
.reduce((a, [k ,v]) => (a[k] = (() => {
const [lang] = k.split('-');
switch (k) {
case 'ja-JP': return v;
case 'ja-KS':
case 'en-US': return merge(locales['ja-JP'], v);
default: return merge(
locales['ja-JP'],
locales['en-US'],
locales[`${lang}-${primaries[lang]}`] ?? {},
v
);
}
})(), a), {});
return Object.entries(locales)
.reduce((a, [k, v]) => (a[k] = (() => {
const [lang] = k.split('-');
switch (k) {
case 'ja-JP': return v;
case 'ja-KS':
case 'en-US': return merge(locales['ja-JP'], v);
default: return merge(
locales['ja-JP'],
locales['en-US'],
locales[`${lang}-${primaries[lang]}`] ?? {},
v
);
}
})(), a), {});
}
export default build();

View File

@@ -241,7 +241,7 @@ publishing: "Pubblicazione"
notResponding: "Nessuna risposta"
instanceFollowing: "Seguiti dall'istanza"
instanceFollowers: "Follower dell'istanza"
instanceUsers: "Utenti dell'istanza"
instanceUsers: "Profili nell'istanza"
changePassword: "Aggiorna Password"
security: "Sicurezza"
retypedNotMatch: "Le password non corrispondono."
@@ -362,8 +362,8 @@ inMb: "in Megabytes"
bannerUrl: "URL dell'immagine d'intestazione"
backgroundImageUrl: "URL dello sfondo"
basicInfo: "Informazioni fondamentali"
pinnedUsers: "Utenti in evidenza"
pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina \"Esplora\", un@ per riga."
pinnedUsers: "Profili in evidenza"
pinnedUsersDescription: "Elenca i profili delle persone che vuoi fissare nella pagina \"Esplora\"."
pinnedPages: "Pagine in evidenza"
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
pinnedClipId: "ID della Clip in evidenza"
@@ -391,7 +391,7 @@ antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (an
notifyAntenna: "Invia notifiche delle nuove note"
withFileAntenna: "Solo note con file in allegato"
enableServiceworker: "Abilita ServiceWorker"
antennaUsersDescription: "Inserisci solo un nome utente per riga"
antennaUsersDescription: "Elenca un nome utente per riga"
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
withReplies: "Includere le risposte"
connectedTo: "Connessione ai seguenti profili:"
@@ -401,11 +401,11 @@ silence: "Silenzia"
silenceConfirm: "Vuoi davvero silenziare questo profilo?"
unsilence: "Riattiva"
unsilenceConfirm: "Vuoi davvero riattivare questo profilo?"
popularUsers: "Utenti popolari"
popularUsers: "Profili popolari"
recentlyUpdatedUsers: "Utenti attivi di recente"
recentlyRegisteredUsers: "Utenti registrati di recente"
recentlyDiscoveredUsers: "Utenti scoperti di recente"
exploreUsersCount: "Ci sono {count} utenti"
recentlyRegisteredUsers: "Profili iscritti di recente"
recentlyDiscoveredUsers: "Profili scoperti di recente"
exploreUsersCount: "Ci sono {count} profili"
exploreFediverse: "Esplora il Fediverso"
popularTags: "Tag di tendenza"
userList: "Liste"
@@ -415,8 +415,8 @@ administrator: "Amministratore"
token: "Token"
2fa: "Autenticazione a due fattori"
setupOf2fa: "Impostare l'autenticazione a due fattori"
totp: "App di autenticazione"
totpDescription: "Inserisci un codice OTP tramite un'app di autenticazione"
totp: "App di autenticazione a due fattori (2FA/MFA)"
totpDescription: "Puoi autenticarti inserendo un codice OTP tramite la tua App di autenticazione a due fattori (2FA/MFA)"
moderator: "Moderatore"
moderation: "moderazione"
moderationNote: "Promemoria di moderazione"
@@ -463,7 +463,7 @@ invitations: "Invita"
invitationCode: "Codice di invito"
checking: "Confermando"
available: "Disponibile"
unavailable: "Il nome utente è già in uso"
unavailable: "Non puoi usarlo"
usernameInvalidFormat: "Il nome utente deve avere solo caratteri alfanumerici e trattino basso '_'"
tooShort: "Troppo breve"
tooLong: "Troppo lungo"
@@ -563,13 +563,13 @@ scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScr
output: "Uscita"
script: "Script"
disablePagesScript: "Disabilita AiScript nelle pagine"
updateRemoteUser: "Aggiornare le informazioni di utente remot@"
updateRemoteUser: "Aggiorna le informazioni dal profilo remoto"
deleteAllFiles: "Elimina tutti i file"
deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?"
removeAllFollowing: "Annulla tutti i follow"
removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più."
userSuspended: "L'utente è in sospensione"
userSilenced: "L'utente è silenziat@."
userSilenced: "Profilo silente."
yourAccountSuspendedTitle: "Questo profilo è sospeso"
yourAccountSuspendedDescription: "Questo profilo è stato sospeso a causa di una violazione del regolamento. Per informazioni, contattare l'amministrazione. Si prega di non creare un nuovo account."
tokenRevoked: "Il token non è valido"
@@ -741,8 +741,8 @@ reloadToApplySetting: "Le tue preferenze verranno impostate dopo il ricaricament
needReloadToApply: "È necessario riavviare per rendere effettive le modifiche."
showTitlebar: "Visualizza la barra del titolo"
clearCache: "Svuota la cache"
onlineUsersCount: "{n} utenti online"
nUsers: "{n} utenti"
onlineUsersCount: "{n} persone online"
nUsers: "{n} profili"
nNotes: "{n}Note"
sendErrorReports: "Invia segnalazioni di errori"
sendErrorReportsDescription: "Quando abilitato, se si verifica un problema, informazioni dettagliate sugli errori verranno condivise con Misskey in modo da aiutare a migliorare la qualità del software.\nCiò include informazioni come la versione del sistema operativo, il tipo di navigatore web che usi, la cronologia delle attività, ecc."
@@ -788,7 +788,7 @@ addDescription: "Aggiungi descrizione"
userPagePinTip: "Qui puoi appuntare note, premendo \"Fissa sul profilo\" nel menù delle singole note."
notSpecifiedMentionWarning: "Sono stati menzionati profili non inclusi fra i destinatari"
info: "Informazioni"
userInfo: "Informazioni utente"
userInfo: "Informazioni sul profilo"
unknown: "Sconosciuto"
onlineStatus: "Stato di connessione"
hideOnlineStatus: "Modalità invisibile"
@@ -853,7 +853,7 @@ lastCommunication: "La comunicazione più recente"
resolved: "Risolto"
unresolved: "Non risolto"
breakFollow: "Non farti più seguire"
breakFollowConfirm: "Vuoi davvero smettere di seguire questo profilo?"
breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?"
itsOn: "Abilitato"
itsOff: "Disabilitato"
on: "Acceso"
@@ -911,7 +911,7 @@ noEmailServerWarning: "Il server di posta non è configurato."
thereIsUnresolvedAbuseReportWarning: "Ci sono report non evasi."
recommended: "Consigliato"
check: "Verifica"
driveCapOverrideLabel: "Modificare il limite di spazio per questo utente"
driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore."
isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
@@ -1169,6 +1169,8 @@ _announcement:
readConfirmText: "Hai già letto \"{title}˝?"
shouldNotBeUsedToPresentPermanentInfo: "Ti consigliamo di utilizzare gli annunci per pubblicare informazioni tempestive e limitate nel tempo, anziché informazioni importanti a lungo andare nel tempo, poiché potrebbero risultare difficili da ritrovare e peggiorare la fruibilità del servizio, specialmente alle nuove persone iscritte."
dialogAnnouncementUxWarn: "Ti consigliamo di usarli con cautela, poiché è molto probabile che avere più di un annuncio in stile \"finestra di dialogo\" peggiori sensibilmente la fruibilità del servizio, specialmente alle nuove persone iscritte."
silence: "Silenzia gli annunci"
silenceDescription: "Se attivi questa opzione, non riceverai notifiche sugli annunci, evitando di contrassegnarle come già lette."
_initialAccountSetting:
accountCreated: "Il tuo profilo è stato creato!"
letsStartAccountSetup: "Per iniziare, impostiamo il tuo profilo."
@@ -1822,14 +1824,14 @@ _time:
day: "giorni"
_2fa:
alreadyRegistered: "La configurazione è stata già completata."
registerTOTP: "Registra un'app di autenticazione"
step1: "Innanzitutto, installare sul dispositivo un'applicazione di autenticazione come {a} o {b}."
step2: "Quindi, scansionare il codice QR visualizzato con l'app."
registerTOTP: "Registra una App di autenticazione a due fattori (2FA/MFA)"
step1: "Innanzitutto, installa sul dispositivo un'App di autenticazione come {a} o {b}."
step2: "Quindi, tramite la App installata, scansiona questo codice QR."
step2Click: "Cliccando sul codice QR, puoi registrarlo con l'app di autenticazione o il portachiavi installato sul tuo dispositivo."
step2Uri: "Inserisci il seguente URL se desideri utilizzare una App per PC"
step3Title: "Inserisci il codice di verifica"
step3: "Inserite il token visualizzato nell'app e il gioco è fatto."
setupCompleted: "Impostazione completata"
setupCompleted: "Impostazione completata! 🎉"
step4: "D'ora in poi, quando si accede, si inserisce il token nello stesso modo."
securityKeyNotSupported: "Il tuo browser non supporta le chiavi di sicurezza."
registerTOTPBeforeKey: "Ti occorre un'app di autenticazione con OTP, prima di registrare la chiave di sicurezza."
@@ -1874,8 +1876,8 @@ _permissions:
"write:pages": "Gestire pagine"
"read:page-likes": "Visualizzare i \"Mi piace\" di pagine"
"write:page-likes": "Gestire i \"Mi piace\" di pagine"
"read:user-groups": "Vedi gruppi di utenti"
"write:user-groups": "Gestisci gruppi di utenti"
"read:user-groups": "Vedere i gruppi di utenti"
"write:user-groups": "Gestire i gruppi di utenti"
"read:channels": "Visualizza canali"
"write:channels": "Gerisci canali"
"read:gallery": "Visualizza la galleria."
@@ -1930,7 +1932,7 @@ _widgets:
postForm: "Finestra di pubblicazione"
slideshow: "Diapositive"
button: "Pulsante"
onlineUsers: "Utenti online"
onlineUsers: "Persone online"
jobQueue: "Coda di lavoro"
serverMetric: "Statistiche server"
aiscript: "Console AiScript"
@@ -1942,7 +1944,7 @@ _widgets:
clicker: "Cliccaggio"
_cw:
hide: "Nascondere"
show: "Attenzione: continua la lettura"
show: "Continua la lettura..."
chars: "{count} caratteri"
files: "{count} file"
_poll:
@@ -2028,7 +2030,7 @@ _charts:
storageUsageTotal: "Utilizzo totale dell'immagazzinamento"
_instanceCharts:
requests: "Richieste"
users: "Variazione del numero di utenti"
users: "Variazione del numero di profili"
usersTotal: "Totale cumulativo di utenti"
notes: "Variazione del numero di note"
notesTotal: "Totale cumulato di note"
@@ -2089,8 +2091,8 @@ _pages:
font: "Tipo di carattere"
fontSerif: "Serif"
fontSansSerif: "Sans serif"
eyeCatchingImageSet: "Imposta un'immagine attrattiva"
eyeCatchingImageRemove: "Elimina l'anteprima immagine"
eyeCatchingImageSet: "Imposta un'immagine attraente"
eyeCatchingImageRemove: "Elimina immagine attraente"
chooseBlock: "Aggiungi blocco"
selectType: "Seleziona tipo"
contentBlocks: "Contenuto"

View File

@@ -311,6 +311,7 @@ folderName: "フォルダー名"
createFolder: "フォルダーを作成"
renameFolder: "フォルダー名を変更"
deleteFolder: "フォルダーを削除"
folder: "フォルダー"
addFile: "ファイルを追加"
emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です"
@@ -437,7 +438,6 @@ share: "共有"
notFound: "見つかりません"
notFoundDescription: "指定されたURLに該当するページはありませんでした。"
uploadFolder: "既定アップロード先"
cacheClear: "キャッシュを削除"
markAsReadAllNotifications: "すべての通知を既読にする"
markAsReadAllUnreadNotes: "すべての投稿を既読にする"
markAsReadAllTalkMessages: "すべてのチャットを既読にする"
@@ -544,6 +544,8 @@ showInPage: "ページで表示"
popout: "ポップアウト"
volume: "音量"
masterVolume: "マスター音量"
notUseSound: "サウンドを出力しない"
useSoundOnlyWhenActive: "Misskeyがアクティブな時のみサウンドを出力する"
details: "詳細"
chooseEmoji: "絵文字を選択"
unableToProcess: "操作を完了できません"
@@ -564,6 +566,10 @@ output: "出力"
script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にする"
updateRemoteUser: "リモートユーザー情報の更新"
unsetUserAvatar: "アイコンを解除"
unsetUserAvatarConfirm: "アイコンを解除しますか?"
unsetUserBanner: "バナーを解除"
unsetUserBannerConfirm: "バナーを解除しますか?"
deleteAllFiles: "すべてのファイルを削除"
deleteAllFilesConfirm: "すべてのファイルを削除しますか?"
removeAllFollowing: "フォローを全解除"
@@ -635,6 +641,7 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
smtpSecureInfo: "STARTTLS使用時はオフにします。"
testEmail: "配信テスト"
wordMute: "ワードミュート"
hardWordMute: "ハードワードミュート"
regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
instanceMute: "サーバーミュート"
@@ -1020,6 +1027,8 @@ resetPasswordConfirm: "パスワードリセットしますか?"
sensitiveWords: "センシティブワード"
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
hiddenTags: "非表示ハッシュタグ"
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
notesSearchNotAvailable: "ノート検索は利用できません。"
license: "ライセンス"
unfavoriteConfirm: "お気に入り解除しますか?"
@@ -1032,6 +1041,7 @@ enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
reactionsDisplaySize: "リアクションの表示サイズ"
limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
noteIdOrUrl: "ートIDまたはURL"
video: "動画"
videos: "動画"
@@ -1158,6 +1168,8 @@ useGroupedNotifications: "通知をグルーピングして表示する"
signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
doReaction: "リアクションする"
code: "コード"
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
_announcement:
forExistingUsers: "既存ユーザーのみ"
@@ -1272,6 +1284,8 @@ _serverSettings:
shortName: "略称"
shortNameDescription: "サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。"
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
fanoutTimelineDbFallback: "データベースへのフォールバック"
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに移行"
@@ -1545,7 +1559,9 @@ _role:
assignTarget: "アサイン"
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。\n<b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。"
manual: "マニュアル"
manualRoles: "マニュアルロール"
conditional: "コンディショナル"
conditionalRoles: "コンディショナルロール"
condition: "条件"
isConditionalRole: "これはコンディショナルロールです。"
isPublic: "公開ロール"
@@ -1838,6 +1854,15 @@ _sfx:
notification: "通知"
antenna: "アンテナ受信"
channel: "チャンネル通知"
reaction: "リアクション選択時"
_soundSettings:
driveFile: "ドライブの音声を使用"
driveFileWarn: "ドライブのファイルを選択してください"
driveFileTypeWarn: "このファイルは対応していません"
driveFileTypeWarnDescription: "音声ファイルを選択してください"
driveFileDurationWarn: "音声が長すぎます"
driveFileDurationWarnDescription: "長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか"
_ago:
future: "未来"
@@ -1849,7 +1874,16 @@ _ago:
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
invalid: "ありません"
invalid: "日時の解析に失敗"
_timeIn:
seconds: "{n}秒後"
minutes: "{n}分後"
hours: "{n}時間後"
days: "{n}日後"
weeks: "{n}週間後"
months: "{n}ヶ月後"
years: "{n}年後"
_time:
second: "秒"
@@ -1982,6 +2016,7 @@ _widgets:
_userList:
chooseList: "リストを選択"
clicker: "クリッカー"
birthdayFollowings: "今日誕生日のユーザー"
_cw:
hide: "隠す"
@@ -2303,6 +2338,8 @@ _moderationLogTypes:
createAvatarDecoration: "アイコンデコレーションを作成"
updateAvatarDecoration: "アイコンデコレーションを更新"
deleteAvatarDecoration: "アイコンデコレーションを削除"
unsetUserAvatar: "ユーザーのアイコンを解除"
unsetUserBanner: "ユーザーのバナーを解除"
_fileViewer:
title: "ファイルの詳細"
@@ -2354,3 +2391,17 @@ _externalResourceInstaller:
_themeInstallFailed:
title: "テーマのインストールに失敗しました"
description: "テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。"
_dataSaver:
_media:
title: "メディアの読み込み"
description: "画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。"
_avatar:
title: "アイコン画像"
description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。"
_urlPreview:
title: "URLプレビューのサムネイル"
description: "URLプレビューのサムネイル画像が読み込まれなくなります。"
_code:
title: "コードハイライト"
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"

View File

@@ -979,6 +979,7 @@ assign: "アサイン"
unassign: "アサインを解除"
color: "色"
manageCustomEmojis: "カスタム絵文字の管理"
manageAvatarDecorations: "アバターを飾るモンの管理"
youCannotCreateAnymore: "これ以上作れなさそうやわ"
cannotPerformTemporary: "一時的に利用できへんで"
cannotPerformTemporaryDescription: "操作回数が制限を超えたから一時的に利用できへんくなったで。ちょっと時間置いてからもう一回やってやー。"
@@ -1149,6 +1150,14 @@ detach: ""
angle: ""
flip: "反転"
showAvatarDecorations: ""
releaseToRefresh: "離してリロード"
refreshing: "リロード中"
pullDownToRefresh: "引っ張ってリロードするで"
disableStreamingTimeline: "タイムラインのリアルタイム更新をやめるで"
useGroupedNotifications: "通知をグルーピングしてだすで"
signupPendingError: "メールアドレスの確認中に問題が起こってえらいこっちゃ。リンクの有効期限が切れてるかもやで"
cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要やで。"
doReaction: "ツッコミすんで"
_announcement:
forExistingUsers: "もうおるユーザーのみ"
forExistingUsersDescription: "有効にすると、このお知らせ作成時点でおるユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。"
@@ -1158,6 +1167,10 @@ _announcement:
tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討した方がええよ。"
readConfirmTitle: "既読にしてええんやな?"
readConfirmText: "「{title}」の内容を読み、既読にします。"
shouldNotBeUsedToPresentPermanentInfo: "新規ユーザーのUXを損ねやすいから、お知らせはストック情報やのうてフロー情報の掲示に使った方がええで。"
dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が高くなるから、使用は慎重にすんのがおすすめやで。"
silence: "通知せんで"
silenceDescription: "オンにすると、このお知らせは通知されないで、既読にする必要もなくなるで。"
_initialAccountSetting:
accountCreated: "アカウント作り終わったで。"
letsStartAccountSetup: "アカウントの初期設定をしよか。"
@@ -1170,8 +1183,24 @@ _initialAccountSetting:
pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。"
initialAccountSettingCompleted: "初期設定が終わったで。"
haveFun: "{name}、楽しんでな~"
youCanContinueTutorial: "このまま{name}(Misskey)の使い方のチュートリアルに進めるけど、ここで中断してすぐに使い始めることもできるで。"
startTutorial: "チュートリアルを開始するで"
skipAreYouSure: "初期設定飛ばすか?"
laterAreYouSure: "初期設定あとでやり直すん?"
_initialTutorial:
launchTutorial: "チュートリアルを見るで"
title: "チュートリアルやで"
wellDone: "やるやん"
skipAreYouSure: "チュートリアルをやめるか?"
_landing:
title: "チュートリアルによう来たな"
description: "ここでは、Misskeyの基本的な使い方や機能を確認できるで。"
_note:
title: "ノートってなんや?"
description: "Misskeyでの投稿は「ート」って呼ばれてるで。ートはタイムラインに時系列で並んでいて、リアルタイムで更新されてるで。"
reply: "返信することもできるで。返信に対しての返信も可能で、スレッドのように会話を続けることもできるで。"
renote: "そのノートを自分のタイムラインに流して共有することもできるで。テキストを追加して引用することもできるで。"
reaction: "ツッコミをつけることもできるで。細かいことは次のページで解説するで。"
_serverRules:
description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。"
_serverSettings:
@@ -1484,6 +1513,7 @@ _role:
inviteLimitCycle: "招待コードの発行間隔"
inviteExpirationTime: "招待コードの有効期限"
canManageCustomEmojis: "カスタム絵文字の管理"
canManageAvatarDecorations: "アバターを飾るモンの管理"
driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける"
pinMax: "ノートのピン留めの最大数"

View File

@@ -113,7 +113,7 @@ cantReRenote: "리노트를 리노트할 수 없습니다."
quote: "인용"
inChannelRenote: "채널 내 리노트"
inChannelQuote: "채널 내 인용"
pinnedNote: "고정해놓은 노트"
pinnedNote: "고정 노트"
pinned: "프로필에 고정"
you: "당신"
clickToShow: "클릭하여 보기"
@@ -195,6 +195,7 @@ perHour: "1시간마다"
perDay: "1일마다"
stopActivityDelivery: "액티비티 보내지 않기"
blockThisInstance: "이 서버를 차단"
silenceThisInstance: "서버를 사일런스"
operations: "작업"
software: "소프트웨어"
version: "버전"
@@ -214,6 +215,8 @@ clearCachedFiles: "캐시 비우기"
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
blockedInstances: "차단된 서버"
blockedInstancesDescription: "차단하려는 서버의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
silencedInstances: "사일런스한 서버"
silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다."
muteAndBlock: "뮤트 및 차단"
mutedUsers: "뮤트한 유저"
blockedUsers: "차단한 유저"
@@ -364,7 +367,7 @@ pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은
pinnedPages: "고정한 페이지"
pinnedPagesDescription: "서버의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
pinnedClipId: "고정할 클립의 ID"
pinnedNotes: "고정해놓은 노트"
pinnedNotes: "고정 노트"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptcha 활성화"
hcaptchaSiteKey: "사이트 키"
@@ -976,6 +979,7 @@ assign: "할당"
unassign: "할당 취소"
color: "색"
manageCustomEmojis: "커스텀 이모지 관리"
manageAvatarDecorations: "아바타 꾸미기 관리"
youCannotCreateAnymore: "더 이상 생성할 수 없습니다."
cannotPerformTemporary: "일시적으로 사용할 수 없음"
cannotPerformTemporaryDescription: "조작 횟수 제한을 초과하여 일시적으로 사용이 불가합니다. 잠시 후 다시 시도해 주세요."
@@ -1114,16 +1118,19 @@ replies: "답글"
renotes: "리노트"
loadReplies: "답글 보기"
loadConversation: "대화 보기"
pinnedList: "고정해놓은 리스트"
pinnedList: "고정 리스트"
keepScreenOn: "기기 화면을 항상 켜기"
verifiedLink: "이 링크의 소유자임이 확인되었습니다."
notifyNotes: "새 노트 알림 켜기"
unnotifyNotes: "새 노트 알림 끄기"
authentication: "인증"
authenticationRequiredToContinue: "계속하려면 인증하십시오"
dateAndTime: "일시"
showRenotes: "리노트 표시"
edited: "수정됨"
notificationRecieveConfig: "알림 설정"
mutualFollow: "맞팔로우"
fileAttachedOnly: "미디어를 포함한 노트만"
showRepliesToOthersInTimeline: "타임라인에 다른 사람에게 보내는 답글을 포함"
hideRepliesToOthersInTimeline: "타임라인에 다른 사람에게 보내는 답글을 포함하지 않음"
showRepliesToOthersInTimelineAll: "타임라인에 현재 팔로우 중인 사람 전원의 답글을 포함하게 하기"
@@ -1134,16 +1141,23 @@ externalServices: "외부 서비스"
impressum: "운영자 정보"
impressumUrl: "운영자 정보 URL"
impressumDescription: "독일 등의 일부 나라와 지역에서는 꼭 표시해야 합니다(Impressum)."
privacyPolicy: "개인정보 보호 정책"
privacyPolicyUrl: "개인정보 보호 정책 URL"
tosAndPrivacyPolicy: "약관 및 개인정보 보호 정책"
avatarDecorations: "아이콘 장식"
attach: "붙이기"
detach: "떼기"
angle: "각도"
flip: "플립"
showAvatarDecorations: "아이콘 장식을 표시"
releaseToRefresh: "놓아서 새로고침"
refreshing: "새로고침 중"
pullDownToRefresh: "아래로 내려서 새로고침"
disableStreamingTimeline: "타임라인의 실시간 갱신을 무효화하기"
useGroupedNotifications: "알림을 그룹화하고 표시"
signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다."
cwNotationRequired: "'내용을 숨기기'를 체크했을 경우 주석을 써야 합니다."
cwNotationRequired: "'내용을 숨기기'를 체크 경우 주석을 써야 합니다."
doReaction: "리액션 추가"
_announcement:
forExistingUsers: "기존 유저에게만 알림"
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
@@ -1153,6 +1167,10 @@ _announcement:
tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 사용자 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다."
readConfirmTitle: "읽음으로 표시합니까?"
readConfirmText: "\"{title}\"을(를) 읽음으로 표시합니다."
shouldNotBeUsedToPresentPermanentInfo: "신규 유저의 이용 경험에 악영향을 끼칠 수 있으므로, 일시적인 알림 수단으로만 사용하고 고정된 정보에는 사용을 지양하는 것을 추천합니다."
dialogAnnouncementUxWarn: "다이얼로그 형태의 알림이 동시에 2개 이상 존재하는 경우, 사용자 경험에 악영향을 끼칠 수 있으므로 신중히 결정하십시오."
silence: "조용히 알림"
silenceDescription: "활성화하면 공지사항에 대한 알림이 가지 않게 되며, 확인 버튼을 누를 필요가 없게 됩니다."
_initialAccountSetting:
accountCreated: "계정 생성이 완료되었습니다!"
letsStartAccountSetup: "계정의 초기 설정을 진행합니다."
@@ -1175,22 +1193,79 @@ _initialTutorial:
wellDone: "잘 하셨습니다"
skipAreYouSure: "튜토리얼을 종료하시겠습니까?"
_landing:
title: "튜토리얼에 오신 걸 환영합니다"
description: "여기서는 미스키의 기본적인 사용법이나 기능을 확인할 수 있습니다."
_note:
title: "'노트'가 무엇인가요?"
description: "미스키에서는 게시물을 '노트'라고 합니다. 노트는 타임라인에 시간순으로 정렬되어 있고, 실시간으로 갱신됩니다."
reply: "답글을 다는 것이 가능합니다. 답글에 답글을 다는 것도 가능하며 스레드처럼 대화를 계속하는 것도 가능합니다."
renote: "그 노트를 자기 타임라인에 가져와서 공유하는 것이 가능합니다. 글을 추가해서 인용하는 것도 가능합니다."
reaction: "리액션을 다는 것이 가능합니다. 다음 페이지에서 자세한 설명을 볼 수 있습니다."
menu: "노트의 상세 정보를 표시하거나, 링크를 복사하는 등의 다양한 조작을 할 수 있습니다."
_reaction:
title: "'리액션'이 무엇인가요?"
description: "노트에 '리액션'을 보낼 수 있습니다. '좋아요'만으로는 충분히 전해지지 않는 감정을, 이모지에 실어서 가볍게 보낼 수 있습니다."
letsTryReacting: "리액션은 노트의 '+' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!"
reactToContinue: "다음으로 진행하려면 리액션을 보내세요."
reactNotification: "누군가가 나의 노트에 리액션을 보내면 실시간으로 알림을 받게 됩니다."
reactDone: "'-' 버튼을 눌러서 리액션을 취소할 수 있습니다."
_timeline:
title: "타임라인에 대하여"
description1: "Misskey에는 종류에 따라 여러 가지의 타임라인으로 구성되어 있습니다. (서버에 따라서는 일부 타임라인을 사용할 수 없는 경우가 있습니다)"
home: "내가 팔로우 중인 계정의 노트를 볼 수 있습니다."
local: "이 서버에 있는 모든 유저의 게시물을 볼 수 있습니다."
social: "홈 타임라인과 로컬 타임라인의 게시물을 모두 볼 수 있습니다."
global: "연결되어 있는 모든 서버의 게시물을 볼 수 있습니다."
description2: "각각의 타임라인은 화면 상단에서 언제든지 변경할 수 있습니다."
description3: "이 외에도, '리스트 타임라인'이나 '채널 타임라인' 등이 있습니다. 자세한 사항은 {link}에서 확인하실 수 있습니다."
_postNote:
title: "노트 게시 설정"
description1: "Misskey에 노트를 쓸 때에는 다양한 옵션을 설정할 수 있습니다. 노트를 작성하는 화면은 이렇게 생겼습니다."
_visibility:
description: "노트를 볼 수 있는 사람을 제한할 수 있습니다."
public: "모든 유저에게 공개합니다."
home: "홈 타임라인에만 공개합니다. 팔로워, 프로필 화면, 리노트를 통해서 다른 유저가 볼 수 있습니다."
followers: "팔로워에게만 공개. 자기 자신을 제외하고는 리노트가 불가능하며, 팔로워 외에는 열람할 수 없습니다."
direct: "지정한 유저에게만 공개되며, 상대방에게 알림이 갑니다. 다이렉트 메시지(DM) 대용으로써 사용하실 수 있습니다."
doNotSendConfidencialOnDirect1: "민감한 정보를 보낼 때에는 주의하십시오."
doNotSendConfidencialOnDirect2: "서버 관리자는 기술적으로 게시물 내용을 열람할 수 있습니다. 신뢰할 수 없는 서버의 유저에게 다이렉트 메시지를 보내는 경우, 민감한 정보가 포함되어 있는 지 확인하십시오."
localOnly: "다른 서버에 게시물을 보내지 않습니다. 앞서 설정한 공개 범위와 상관 없이, 다른 서버의 유저는 이 게시물을 직접 열람할 수 없게 됩니다."
_cw:
title: "내용 가리기 (CW)"
description: "본문 대신에 '내용에 대한 주석'에 입력한 텍스트가 먼저 표시됩니다. '더 보기' 버튼을 누르면 본문이 표시됩니다."
_exampleNote:
cw: "배고픈 사람 주의"
note: "방금 초코도넛을 먹었어요 🍩😋"
useCases: "서버의 가이드라인에 따라 특정 주제를 다룰 때에 사용하거나, 스포일러 및 민감한 화제를 다룰 때에 자율적으로 사용하기도 합니다."
_howToMakeAttachmentsSensitive:
title: "첨부 파일을 열람주의로 설정하려면?"
description: "서버의 가이드라인에 따라 필요한 이미지, 또는 그대로 노출되기에 부적절한 미디어는 '열람 주의'를 설정해 주세요."
tryThisFile: "이 작성 창에 첨부된 이미지를 열람 주의로 설정해 보세요!"
_exampleNote:
note: "낫또 뚜껑 뜯다가 실수했다…"
method: "첨부 파일을 열람 주의로 설정하려면, 해당 파일을 클릭하여 메뉴를 열고, '열람주의로 설정'을 클릭합니다."
sensitiveSucceeded: "파일을 첨부할 때에는 서버의 가이드라인에 따라 적절히 열람주의를 설정해 주시기 바랍니다."
doItToContinue: "이미지를 열람 주의로 설정하면 다음으로 넘어갈 수 있게 됩니다."
_done:
title: "튜토리얼이 끝났습니다! 🎉"
description: "여기에서 소개한 기능은 극히 일부에 지나지 않습니다. Misskey의 사용 방법을 더 자세히 알아보려면 {link}를 확인해 주세요!"
_timelineDescription:
home: "홈 타임라인에서는, 내가 팔로우한 계정의 게시물을 볼 수 있습니다."
local: "로컬 타임라인에서는, 이 서버의 모든 유저의 게시물을 볼 수 있습니다."
social: "소셜 타임라인에서는, 홈 타임라인과 로컬 타임라인의 게시물을 모두 볼 수 있습니다."
global: "글로벌 타임라인에서는, 여기와 연결된 다른 모든 서버의 게시물을 볼 수 있습니다."
_serverRules:
description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
_serverSettings:
iconUrl: "아이콘 URL"
appIconDescription: "{host}이 앱으로 표시될 때의 아이콘을 지정합니다."
appIconUsageExample: "예를 들어, PWA나 스마트폰 홈 화면에 북마크로 추가되었을 때 등"
appIconStyleRecommendation: "아이콘이 원형 또는 둥근 사각형으로 잘리는 경우가 있으므로, 가장자리 여백이 충분한 사진을 사용하는 것을 추천합니다."
appIconResolutionMustBe: "해상도는 반드시 {resolution} 이어야 합니다."
manifestJsonOverride: "manifest.json 오버라이드"
shortName: "약칭"
shortNameDescription: "서버의 정식 명칭이 긴 경우에, 대신에 표시할 수 있는 약칭이나 통칭."
fanoutTimelineDescription: "활성화하면 각종 타임라인을 가져올 때의 성능을 대폭 향상하며, 데이터베이스의 부하를 줄일 수 있습니다. 단, Redis의 메모리 사용량이 증가합니다. 서버의 메모리 용량이 작거나, 서비스가 불안정해지는 경우 비활성화할 수 있습니다."
_accountMigration:
moveFrom: "다른 계정에서 이 계정으로 이사"
moveFromSub: "다른 계정에 대한 별칭을 생성"
@@ -1445,6 +1520,12 @@ _achievements:
title: "Brain Diver"
description: "Brain Diver로의 링크를 첨부했습니다"
flavor: "Misskey-Misskey La-Tu-Ma"
_smashTestNotificationButton:
title: "테스트 과잉"
description: "매우 짧은 시간 안에 알림 테스트를 여러 번 수행했습니다"
_tutorialCompleted:
title: "Misskey 입문자 과정 수료증"
description: "튜토리얼을 완료했습니다"
_role:
new: "새 역할 생성"
edit: "역할 수정"
@@ -1488,6 +1569,7 @@ _role:
inviteLimitCycle: "초대 발급 간격"
inviteExpirationTime: "초대 만료 기간"
canManageCustomEmojis: "커스텀 이모지 관리"
canManageAvatarDecorations: "아바타 꾸미기 관리"
driveCapacity: "드라이브 용량"
alwaysMarkNsfw: "파일을 항상 NSFW로 지정"
pinMax: "고정할 수 있는 노트 수"
@@ -1502,6 +1584,7 @@ _role:
descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다."
canHideAds: "광고 숨기기"
canSearchNotes: "노트 검색 이용 가능 여부"
canUseTranslator: "번역 기능의 사용"
_condition:
isLocal: "로컬 사용자"
isRemote: "리모트 사용자"
@@ -1550,6 +1633,10 @@ _ad:
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
hide: "보이지 않음"
timezoneinfo: "요일은 서버의 표준 시간대에 따라 결정됩니다."
adsSettings: "광고 표시 설정"
notesPerOneAd: "실시간으로 갱신되는 타임라인에서 광고를 노출시키는 간격 (노트 당)"
setZeroToDisable: "0으로 지정하면 실시간 타임라인에서의 광고를 비활성화합니다"
adsTooClose: "광고의 표시 간격이 매우 작아, 사용자 경험에 부정적인 영향을 미칠 수 있습니다."
_forgotPassword:
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
@@ -1602,6 +1689,7 @@ _aboutMisskey:
donate: "Misskey에 기부하기"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자"
projectMembers: "프로젝트 구성원"
_displayOfSensitiveMedia:
respect: "민감한 콘텐츠로 표시된 미디어 숨기기"
ignore: "민감한 콘텐츠로 표시된 미디어 보이기"
@@ -1626,6 +1714,7 @@ _channel:
notesCount: "{n}노트"
nameAndDescription: "이름과 설명"
nameOnly: "이름만"
allowRenoteToExternal: "채널 외부로의 리노트와 인용 리노트를 허가"
_menuDisplay:
sideFull: "가로"
sideIcon: "가로(아이콘)"
@@ -1812,8 +1901,9 @@ _auth:
_antennaSources:
all: "모든 노트"
homeTimeline: "팔로우중인 유저의 노트"
users: "지정한 한 명 혹은 여러 명의 유저의 노트"
users: "지정한 유저의 노트"
userList: "지정한 리스트에 속한 유저의 노트"
userBlacklist: "지정한 유저를 제외한 모든 노트"
_weekday:
sunday: "일요일"
monday: "월요일"
@@ -1913,6 +2003,7 @@ _profile:
metadataContent: "내용"
changeAvatar: "아바타 이미지 변경"
changeBanner: "배너 이미지 변경"
verifiedLinkDescription: "내용에 자신의 프로필로 향하는 링크가 포함된 페이지의 URL을 삽입하면 소유자 인증 마크가 표시됩니다."
_exportOrImport:
allNotes: "모든 노트"
favoritedNotes: "즐겨찾기한 노트"
@@ -1922,6 +2013,7 @@ _exportOrImport:
userLists: "리스트"
excludeMutingUsers: "뮤트한 유저 제외하기"
excludeInactiveUsers: "휴면 중인 계정 제외하기"
withReplies: "가져오기한 유저에 의한 답글을 타임라인에 포함"
_charts:
federation: "연합"
apRequest: "요청"
@@ -1995,7 +2087,7 @@ _pages:
url: "페이지 URL"
summary: "페이지 요약"
alignCenter: "가운데 정렬"
hideTitleWhenPinned: "프로필에 고정해놓은 경우 타이틀을 표시하지 않음"
hideTitleWhenPinned: "프로필에 고정 경우 타이틀을 표시하지 않음"
font: "폰트"
fontSerif: "명조체"
fontSansSerif: "고딕체"
@@ -2031,6 +2123,7 @@ _notification:
youReceivedFollowRequest: "새로운 팔로우 요청이 있습니다"
yourFollowRequestAccepted: "팔로우 요청이 수락되었습니다"
pollEnded: "투표 결과가 발표되었습니다"
newNote: "새 게시물"
unreadAntennaNote: "안테나 {name}"
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
achievementEarned: "도전 과제를 달성했습니다"
@@ -2043,6 +2136,7 @@ _notification:
followedBySomeUsers: "{n}명에게 팔로우됨"
_types:
all: "전부"
note: "유저의 새 게시물"
follow: "팔로잉"
mention: "멘션"
reply: "답글"
@@ -2113,6 +2207,85 @@ _webhookSettings:
reaction: "누군가 내 노트에 리액션했을 때"
mention: "누군가 나를 멘션했을 때"
_moderationLogTypes:
createRole: "역할 생성"
deleteRole: "역할 삭제"
updateRole: "역할 수정"
assignRole: "역할 할당"
unassignRole: "역할 해제"
suspend: "정지"
unsuspend: "정지 해제"
addCustomEmoji: "커스텀 이모지 추가"
updateCustomEmoji: "커스텀 이모지 수정"
deleteCustomEmoji: "커스텀 이모지 삭제"
updateServerSettings: "서버 설정 갱신"
updateUserNote: "모더레이션 노트 갱신"
deleteDriveFile: "파일 삭제"
deleteNote: "노트 삭제"
createGlobalAnnouncement: "전역 공지사항 생성"
createUserAnnouncement: "유저 공지사항 생성"
updateGlobalAnnouncement: "전역 공지사항 수정"
updateUserAnnouncement: "유저 공지사항 수정"
deleteGlobalAnnouncement: "전역 공지사항 삭제"
deleteUserAnnouncement: "유저 공지사항 삭제"
resetPassword: "비밀번호 재설정"
suspendRemoteInstance: "리모트 서버를 정지"
unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
markSensitiveDriveFile: "파일에 열람주의를 설정"
unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
resolveAbuseReport: "신고 해결"
createInvitation: "초대 코드 생성"
createAd: "광고 생성"
deleteAd: "광고 삭제"
updateAd: "광고 수정"
createAvatarDecoration: "아이콘 장식 추가"
updateAvatarDecoration: "아이콘 장식 수정"
deleteAvatarDecoration: "아이콘 장식 삭제"
_fileViewer:
title: "파일 상세"
type: "파일 유형"
size: "파일 크기"
url: "URL"
uploadedAt: "업로드 날짜"
attachedNotes: "첨부된 노트"
thisPageCanBeSeenFromTheAuthor: "이 페이지는 파일 소유자만 열람할 수 있습니다"
_externalResourceInstaller:
title: "외부 사이트로부터 설치"
checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오."
_plugin:
title: "이 플러그인을 설치하시겠습니까?"
metaTitle: "플러그인 정보"
_theme:
title: "이 테마를 설치하시겠습니까?"
metaTitle: "테마 정보"
_meta:
base: "기본 컬러 스키마"
_vendorInfo:
title: "제공자 정보"
endpoint: "참조한 엔드포인트"
hashVerify: "파일 무결성 확인"
_errors:
_invalidParams:
title: "파라미터가 부족합니다"
description: "외부 사이트로부터 데이터를 불러오기 위해 필요한 정보가 부족합니다. URL을 다시 한 번 확인하십시오."
_resourceTypeNotSupported:
title: "해당하는 외부 리소스는 지원되지 않습니다."
description: "외부 사이트의 해당 리소스는 지원되지 않습니다. 사이트 관리자에게 문의하십시오."
_failedToFetch:
title: "데이터를 불러올 수 없습니다"
fetchErrorDescription: "외부 사이트와의 통신에 실패하였습니다. 여러 번 시도해도 동일한 오류가 표시되는 경우 사이트 관리자에게 문의하십시오."
parseErrorDescription: "외부 사이트에서 불러온 데이터를 읽어들일 수 없습니다. 사이트 관리자에게 문의하십시오."
_hashUnmatched:
title: "데이터가 올바르지 않습니다."
description: "데이터의 무결성 확인에 실패하여, 보안을 위해 설치가 중단되었습니다. 사이트 관리자에게 문의하십시오."
_pluginParseFailed:
title: "AiScript 오류"
description: "데이터를 성공적으로 불러왔으나, AiScript 분석 과정에서 오류가 발생하여 읽어들일 수 없습니다. 플러그인 작성자에게 문의하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
_pluginInstallFailed:
title: "플러그인 설치에 실패했습니다"
description: "플러그인을 설치하는 도중 문제가 발생하였습니다. 다시 한 번 시도하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
_themeParseFailed:
title: "테마 코드 분석 오류"
description: "데이터를 성공적으로 불러왔으나, 테마 코드 분석 과정에서 오류가 발생하여 읽어들일 수 없습니다. 테마 작성자에게 문의하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."
_themeInstallFailed:
title: "테마를 설치하지 못했습니다"
description: "테마를 설치하는 도중 문제가 발생하였습니다. 다시 한 번 시도하십시오. 자세한 사항은 브라우저에 내장된 개발자 도구의 Javascript 콘솔에서 확인하실 수 있습니다."

View File

@@ -59,7 +59,7 @@ copyFileId: "Скопировать ID файла"
copyFolderId: "Скопировать ID папки"
copyProfileUrl: "Скопировать URL профиля "
searchUser: "Поиск людей"
reply: "Ответить"
reply: "Ответ"
loadMore: "Показать еще"
showMore: "Показать еще"
showLess: "Закрыть"
@@ -409,6 +409,7 @@ aboutMisskey: "О Misskey"
administrator: "Администратор"
token: "Токен"
2fa: "2-х факторная аутентификация"
setupOf2fa: "Настроить двухфакторную аутентификацию"
totp: "Приложение-аутентификатор"
totpDescription: "Описание приложения-аутентификатора"
moderator: "Модератор"
@@ -652,6 +653,7 @@ behavior: "Поведение"
sample: "Пример"
abuseReports: "Жалобы"
reportAbuse: "Жалоба"
reportAbuseRenote: "Пожаловаться на репост"
reportAbuseOf: "Пожаловаться на пользователя {name}"
fillAbuseReportDescription: "Опишите, пожалуйста, причину жалобы подробнее. Если речь о конкретной заметке, будьте добры приложить ссылку на неё."
abuseReported: "Жалоба отправлена. Большое спасибо за информацию."
@@ -1060,12 +1062,14 @@ goToMisskey: "К Misskey"
additionalEmojiDictionary: "Дополнительные словари эмодзи"
installed: "Установлено"
branding: "Бренд"
enableIdenticonGeneration: "Включить генерацию иконки пользователя"
turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность."
expirationDate: "Дата истечения"
unused: "Неиспользуемый"
expired: "Срок действия приглашения истёк"
doYouAgree: "Согласны?"
icon: "Аватар"
replies: "Ответить"
replies: "Ответы"
renotes: "Репост"
flip: "Переворот"
_initialAccountSetting:
@@ -1075,6 +1079,9 @@ _initialAccountSetting:
privacySetting: "Настройки конфиденциальности"
initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!"
skipAreYouSure: "Пропустить настройку?"
_initialTutorial:
_note:
description: "Посты в Misskey называются 'Заметками.' Заметки отсортированы в хронологическом порядке в ленте и обновляются в режиме реального времени."
_serverSettings:
iconUrl: "Адрес на иконку роли"
_achievements:
@@ -1892,7 +1899,7 @@ _notification:
app: "Уведомления из приложений"
_actions:
followBack: "отвечает взаимной подпиской"
reply: "Ответить"
reply: "Ответ"
renote: "Репост"
_deck:
alwaysShowMainColumn: "Всегда показывать главную колонку"

View File

@@ -9,6 +9,7 @@ notifications: "Bildirim"
username: "Kullanıcı Adı"
password: "Şifre"
forgotPassword: "şifremi unuttum"
fetchingAsApObject: "從聯邦宇宙取得中..."
ok: "TAMAM"
gotIt: "Anladım"
cancel: "İptal"
@@ -44,6 +45,7 @@ pin: "Sabitlenmiş"
unpin: "Sabitlemeyi kaldır"
copyContent: "İçeriği kopyala"
copyLink: "Bağlantıyı Kopyala"
copyLinkRenote: "Turkish"
delete: "Sil"
deleteAndEdit: "Sil ve yeniden düzenle"
deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek istiyor musunuz? Bu nota ilişkin tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir."
@@ -155,6 +157,7 @@ addEmoji: "Emoji ekle"
settingGuide: "Önerilen ayarlar"
cacheRemoteFiles: "Uzak dosyalar ön belleğe alınsın"
cacheRemoteFilesDescription: "Bu ayar açık olduğunda diğer sitelerin dosyaları doğrudan uzak sunucudan yüklenecektir. Bu ayarı kapatmak depolama kullanımını azaltacak ama küçük resimler oluşturulmadığından trafiği arttıracaktır."
youCanCleanRemoteFilesCache: ""
cacheRemoteSensitiveFiles: "Hassas uzak dosyalar ön belleğe alınsın"
cacheRemoteSensitiveFilesDescription: "Bu ayar kapalı olduğunda hassas uzak dosyalar ön belleğe alınmadan doğrudan uzak sunucudan yüklenecektir."
flagAsBot: "Bot olarak işaretle"
@@ -192,6 +195,7 @@ perHour: "Saatlik"
perDay: "Günlük"
stopActivityDelivery: "Durum güncellemelerini gönderme"
blockThisInstance: "Bu sunucuyu engelle"
silenceThisInstance: ""
operations: "İşlemler"
software: "Yazılımlar"
version: "Sürüm"
@@ -211,6 +215,8 @@ clearCachedFiles: "Ön belleği temizle"
clearCachedFilesConfirm: "Ön belleğe alınmış tüm uzak sunucu dosyaları silinsin mi?"
blockedInstances: "Engellenen sunucular"
blockedInstancesDescription: "Engellemek istediğiniz sunucuların alan adlarını satır sonlarıyla ayırarak yazın. Yazılan sunucular bu sunucuyla iletişime geçemeyecek."
silencedInstances: "Turkısh"
silencedInstancesDescription: ""
muteAndBlock: "Susturma ve Engelleme"
mutedUsers: "Susturulan kullanıcılar"
blockedUsers: "Engellenen kullanıcılar"

View File

@@ -299,7 +299,7 @@ light: "淺色"
dark: "深色"
lightThemes: "淺色主題"
darkThemes: "深色主題"
syncDeviceDarkMode: "同步至此裝置的深色模式設定"
syncDeviceDarkMode: "與設備的深色模式同步"
drive: "雲端硬碟"
fileName: "檔案名稱"
selectFile: "選擇檔案"
@@ -584,7 +584,7 @@ relays: "中繼"
addRelay: "新增中繼"
inboxUrl: "收件夾URL"
addedRelays: "已加入的中繼"
serviceworkerInfo: "您需要啟用推通知。"
serviceworkerInfo: "如要使用推通知,需要啟用此選項並設定金鑰。"
deletedNote: "已刪除的貼文"
invisibleNote: "私密的貼文"
enableInfiniteScroll: "啟用自動滾動頁面模式"
@@ -731,7 +731,7 @@ thisIsExperimentalFeature: "這是實驗性的功能。可能會有變更規格
developer: "開發者"
makeExplorable: "使自己的帳戶能夠在「探索」頁面中顯示"
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。"
showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文"
showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文"
duplicate: "複製"
left: "左"
center: "置中"
@@ -1030,7 +1030,7 @@ retryAllQueuesConfirmTitle: "要現在重試嗎?"
retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
enableChartsForRemoteUser: "生成遠端使用者的圖表"
enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
showClipButtonInNoteFooter: "新增摘錄至貼文"
showClipButtonInNoteFooter: "新增摘錄按鈕至貼文"
reactionsDisplaySize: "反應的顯示尺寸"
noteIdOrUrl: "貼文ID或URL"
video: "影片"
@@ -1169,6 +1169,8 @@ _announcement:
readConfirmText: "閱讀「{title}」的內容並標記為已讀。"
shouldNotBeUsedToPresentPermanentInfo: "由於可能會破壞使用者體驗,尤其是對於新使用者而言,我們建議使用公告來發布有時效性的資訊而不是固定不變的資訊。"
dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。"
silence: "不發送通知"
silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。"
_initialAccountSetting:
accountCreated: "帳戶已建立完成!"
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
@@ -1248,7 +1250,7 @@ _initialTutorial:
title: "教學課程已結束"
description: "這裡介紹的功能只是其中的一小部分。要了解更多有關如何使用Misskey的資訊請瀏覽{link}。"
_timelineDescription:
home: "在首頁時間上,可以看到您追隨的使用者的貼文。"
home: "在首頁時間上,可以看到您追隨的使用者的貼文。"
local: "在本地時間軸上,可以看到此伺服器所有使用者的貼文。"
social: "在社交時間軸上,可以看到首頁與本地時間軸的貼文。"
global: "在公開時間軸上,可以看到其他已連接伺服器的貼文。\n"
@@ -1264,6 +1266,8 @@ _serverSettings:
shortName: "簡稱"
shortNameDescription: "如果伺服器的正式名稱很長,可用簡稱或通稱代替。"
fanoutTimelineDescription: "如果啟用的話檢索各個時間軸的性能會顯著提昇資料庫的負荷也會減少。不過Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
fanoutTimelineDbFallback: "資料庫的回退"
fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
_accountMigration:
moveFrom: "從其他帳戶遷移到這個帳戶"
moveFromSub: "為另一個帳戶建立別名"
@@ -1272,7 +1276,7 @@ _accountMigration:
moveTo: "將這個帳戶遷移至新的帳戶"
moveToLabel: "要遷移到的帳戶:"
moveCannotBeUndone: "一旦遷移帳戶,就無法取消。"
moveAccountDescription: "遷移至新帳戶。\n ・此帳戶的追隨者將自動追隨新帳戶\n ・此帳戶的所有追隨者將被取消追隨\n ・此帳戶不能再發文。\n\n雖然會自動遷移您追隨者但必須手動遷移您追隨的帳戶。請在遷移前匯出此帳戶的「追隨中」名單並在遷移後自行匯入。\n列表名單、靜音名單及封鎖名單也必須如此處理。\n\n此說明適用於本伺服器以及運行 Misskey v13.12.0 或更新版本的其他伺服器;如 Mastodon 等使用 ActivityPub 協定的其他軟體或有不同的處理方式。)"
moveAccountDescription: "遷移至新帳戶。\n ・此帳戶的追隨者將自動追隨新帳戶\n ・此帳戶的所有追隨者將被取消追隨\n ・此帳戶不能再發文。\n\n雖然會自動遷移您追隨者,但必須手動遷移您追隨的帳戶。請在遷移前匯出此帳戶的「追隨中」名單,並在遷移後自行匯入。\n列表名單、靜音名單及封鎖名單也必須如此處理。\n\n此說明適用於本伺服器以及運行 Misskey v13.12.0 或更新版本的其他伺服器;如 Mastodon 等使用 ActivityPub 協定的其他軟體或有不同的處理方式。)"
moveAccountHowTo: "要遷移帳戶,首先要在目標帳戶中為此帳戶建立一個別名。\n 建立別名後,像這樣輸入目標帳戶:@username@server.example.com"
startMigration: "遷移"
migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外請確認在要遷移到的帳戶已經建立了一個別名。"
@@ -1723,7 +1727,7 @@ _wordMute:
muteWordsDescription: "空格代表「以及」AND換行代表「或者」OR。"
muteWordsDescription2: "用斜線包圍關鍵字代表正規表達式。"
_instanceMute:
instanceMuteDescription: "包括對被靜音實例上的使用者的回覆,被設定的實例上所有貼文及轉發都會被靜音。"
instanceMuteDescription: "包括對被靜音伺服器上的使用者的回覆,被設定的伺服器上所有貼文及轉發都會被靜音。"
instanceMuteDescription2: "設定時以換行進行分隔"
title: "將隱藏被設定的實例貼文。"
heading: "將實例靜音"
@@ -1815,6 +1819,14 @@ _ago:
monthsAgo: "{n} 個月前"
yearsAgo: "{n} 年前"
invalid: "無"
_timeIn:
seconds: "{n} 秒後"
minutes: "{n} 分後"
hours: "{n} 小時後"
days: "{n} 日後"
weeks: "{n} 週後"
months: "{n} 個月後"
years: "{n} 年後"
_time:
second: "秒"
minute: "分鐘"

View File

@@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2023.11.0",
"version": "2023.12.0-beta.3",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@8.10.0",
"packageManager": "pnpm@8.10.5",
"workspaces": [
"packages/frontend",
"packages/backend",
@@ -18,6 +18,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 --filter backend 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",
"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",
@@ -26,7 +27,7 @@
"check:connect": "cd packages/backend && pnpm check:connect",
"migrateandstart": "pnpm migrate && pnpm start",
"watch": "pnpm dev",
"dev": "node ./scripts/dev.mjs",
"dev": "pnpm -r dev",
"lint": "pnpm -r lint",
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
@@ -47,17 +48,18 @@
"execa": "8.0.1",
"cssnano": "6.0.1",
"js-yaml": "4.1.0",
"postcss": "8.4.31",
"postcss": "8.4.32",
"terser": "5.24.0",
"typescript": "5.2.2"
"typescript": "5.3.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "6.9.1",
"@typescript-eslint/parser": "6.9.1",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"cross-env": "7.0.3",
"cypress": "13.4.0",
"eslint": "8.52.0",
"start-server-and-test": "2.0.1"
"cypress": "13.6.1",
"eslint": "8.55.0",
"start-server-and-test": "2.0.3",
"ncp": "2.0.0"
},
"optionalDependencies": {
"@tensorflow/tfjs-core": "4.4.0"

View File

@@ -0,0 +1,8 @@
import { loadConfig } from './built/config.js'
import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
import { writeFileSync } from "node:fs";
const config = loadConfig();
const spec = genOpenapiSpec(config);
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class EnableFanoutTimelineDbFallback1700096812223 {
name = 'EnableFanoutTimelineDbFallback1700096812223'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableFanoutTimelineDbFallback" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableFanoutTimelineDbFallback"`);
}
}

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class SupportVerifyMailApi1700303245007 {
name = 'SupportVerifyMailApi1700303245007'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "verifymailAuthKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "enableVerifymailApi" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableVerifymailApi"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "verifymailAuthKey"`);
}
}

View File

@@ -0,0 +1,11 @@
export class HardMute1700383825690 {
name = 'HardMute1700383825690'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ADD "hardMutedWords" jsonb NOT NULL DEFAULT '[]'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "hardMutedWords"`);
}
}

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddBdayIndex1700902349231 {
name = 'AddBdayIndex1700902349231'
async up(queryRunner) {
await queryRunner.query(`CREATE INDEX "IDX_de22cd2b445eee31ae51cdbe99" ON "user_profile" (SUBSTR("birthday", 6, 5))`);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_de22cd2b445eee31ae51cdbe99"`);
}
}

View File

@@ -7,8 +7,8 @@
"node": ">=18.16.0"
},
"scripts": {
"start": "node ./built/index.js",
"start:test": "NODE_ENV=test node ./built/index.js",
"start": "node ./built/boot/entry.js",
"start:test": "NODE_ENV=test node ./built/boot/entry.js",
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
@@ -16,6 +16,7 @@
"watch:swc": "swc src -d built -D -w",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"dev": "node ./built/boot/entry.js",
"typecheck": "tsc --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
@@ -23,7 +24,8 @@
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
"test": "pnpm jest",
"test-and-coverage": "pnpm jest-and-coverage"
"test-and-coverage": "pnpm jest-and-coverage",
"generate-api-json": "node ./generate_api_json.js"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
@@ -59,27 +61,27 @@
"dependencies": {
"@aws-sdk/client-s3": "3.412.0",
"@aws-sdk/lib-storage": "3.412.0",
"@smithy/node-http-handler": "2.1.5",
"@bull-board/api": "5.9.1",
"@bull-board/fastify": "5.9.1",
"@bull-board/ui": "5.9.1",
"@bull-board/api": "5.10.2",
"@bull-board/fastify": "5.10.2",
"@bull-board/ui": "5.10.2",
"@discordapp/twemoji": "14.1.2",
"@fastify/accepts": "4.2.0",
"@fastify/cookie": "9.1.0",
"@fastify/cors": "8.4.1",
"@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.2.0",
"@fastify/cors": "8.4.2",
"@fastify/express": "2.3.0",
"@fastify/http-proxy": "9.2.1",
"@fastify/http-proxy": "9.3.0",
"@fastify/multipart": "8.0.0",
"@fastify/static": "6.12.0",
"@fastify/view": "8.2.0",
"@nestjs/common": "10.2.8",
"@nestjs/core": "10.2.8",
"@nestjs/testing": "10.2.8",
"@nestjs/common": "10.2.10",
"@nestjs/core": "10.2.10",
"@nestjs/testing": "10.2.10",
"@peertube/http-signature": "1.7.0",
"@simplewebauthn/server": "8.3.5",
"@sinonjs/fake-timers": "11.2.2",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.95",
"@smithy/node-http-handler": "2.1.10",
"@swc/cli": "0.1.63",
"@swc/core": "1.3.100",
"accepts": "1.3.8",
"ajv": "8.12.0",
"archiver": "6.0.1",
@@ -87,7 +89,7 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
"bullmq": "4.12.8",
"bullmq": "4.15.2",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.1",
"chalk": "5.3.0",
@@ -99,8 +101,9 @@
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fastify": "4.24.3",
"fastify-raw-body": "4.3.0",
"feed": "4.2.2",
"file-type": "18.6.0",
"file-type": "18.7.0",
"fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0",
"got": "13.0.0",
@@ -112,17 +115,17 @@
"ipaddr.js": "2.1.0",
"is-svg": "5.0.0",
"js-yaml": "4.1.0",
"jsdom": "22.1.0",
"jsdom": "23.0.1",
"json5": "2.2.3",
"jsonld": "8.3.1",
"jsrsasign": "10.8.6",
"meilisearch": "0.35.0",
"jsonld": "8.3.2",
"jsrsasign": "10.9.0",
"meilisearch": "0.36.0",
"mfm-js": "0.23.3",
"microformats-parser": "1.5.2",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"ms": "3.0.0-canary.1",
"nanoid": "5.0.2",
"nanoid": "5.0.4",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"nodemailer": "6.9.7",
@@ -131,7 +134,7 @@
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
"otpauth": "9.1.5",
"otpauth": "9.2.1",
"parse5": "7.1.2",
"pg": "8.11.3",
"pkce-challenge": "4.0.1",
@@ -143,27 +146,28 @@
"qrcode": "1.5.3",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.20.5",
"re2": "1.20.9",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"reflect-metadata": "0.1.14",
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
"sanitize-html": "2.11.0",
"secure-json-parse": "2.7.0",
"sharp": "0.32.6",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
"systeminformation": "5.21.15",
"systeminformation": "5.21.20",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
"tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.17",
"typescript": "5.2.2",
"typescript": "5.3.3",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.6",
@@ -174,50 +178,50 @@
"@jest/globals": "29.7.0",
"@simplewebauthn/typescript-types": "8.3.4",
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.6",
"@types/archiver": "6.0.0",
"@types/bcryptjs": "2.4.5",
"@types/body-parser": "1.19.4",
"@types/accepts": "1.3.7",
"@types/archiver": "6.0.2",
"@types/bcryptjs": "2.4.6",
"@types/body-parser": "1.19.5",
"@types/cbor": "6.0.0",
"@types/color-convert": "2.0.2",
"@types/content-disposition": "0.5.7",
"@types/fluent-ffmpeg": "2.1.23",
"@types/http-link-header": "1.0.4",
"@types/jest": "29.5.7",
"@types/js-yaml": "4.0.8",
"@types/jsdom": "21.1.4",
"@types/jsonld": "1.5.11",
"@types/jsrsasign": "10.5.11",
"@types/mime-types": "2.1.3",
"@types/ms": "0.7.33",
"@types/node": "20.8.10",
"@types/color-convert": "2.0.3",
"@types/content-disposition": "0.5.8",
"@types/fluent-ffmpeg": "2.1.24",
"@types/http-link-header": "1.0.5",
"@types/jest": "29.5.11",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.6",
"@types/jsonld": "1.5.13",
"@types/jsrsasign": "10.5.12",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
"@types/node": "20.10.4",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.13",
"@types/oauth": "0.9.3",
"@types/oauth2orize": "1.11.2",
"@types/oauth2orize-pkce": "0.1.1",
"@types/pg": "8.10.7",
"@types/pug": "2.0.8",
"@types/punycode": "2.1.1",
"@types/qrcode": "1.5.4",
"@types/random-seed": "0.3.4",
"@types/ratelimiter": "3.4.5",
"@types/rename": "1.0.6",
"@types/sanitize-html": "2.9.3",
"@types/semver": "7.5.4",
"@types/nodemailer": "6.4.14",
"@types/oauth": "0.9.4",
"@types/oauth2orize": "1.11.3",
"@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.10.9",
"@types/pug": "2.0.10",
"@types/punycode": "2.1.3",
"@types/qrcode": "1.5.5",
"@types/random-seed": "0.3.5",
"@types/ratelimiter": "3.4.6",
"@types/rename": "1.0.7",
"@types/sanitize-html": "2.9.5",
"@types/semver": "7.5.6",
"@types/sharp": "0.32.0",
"@types/simple-oauth2": "5.0.6",
"@types/sinonjs__fake-timers": "8.1.4",
"@types/tinycolor2": "1.4.5",
"@types/tmp": "0.2.5",
"@types/vary": "1.1.2",
"@types/web-push": "3.6.2",
"@types/ws": "8.5.8",
"@typescript-eslint/eslint-plugin": "6.9.1",
"@typescript-eslint/parser": "6.9.1",
"@types/simple-oauth2": "5.0.7",
"@types/sinonjs__fake-timers": "8.1.5",
"@types/tinycolor2": "1.4.6",
"@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": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3",
"eslint": "8.52.0",
"eslint": "8.55.0",
"eslint-plugin-import": "2.29.0",
"execa": "8.0.1",
"jest": "29.7.0",

View File

@@ -16,7 +16,7 @@ import type { AntennasRepository, UserListMembershipsRepository } from '@/models
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@@ -39,7 +39,7 @@ export class AntennaService implements OnApplicationShutdown {
private utilityService: UtilityService,
private globalEventService: GlobalEventService,
private funoutTimelineService: FunoutTimelineService,
private fanoutTimelineService: FanoutTimelineService,
) {
this.antennasFetched = false;
this.antennas = [];
@@ -60,11 +60,21 @@ export class AntennaService implements OnApplicationShutdown {
lastUsedAt: new Date(body.lastUsedAt),
});
break;
case 'antennaUpdated':
this.antennas[this.antennas.findIndex(a => a.id === body.id)] = {
...body,
lastUsedAt: new Date(body.lastUsedAt),
};
case 'antennaUpdated': {
const idx = this.antennas.findIndex(a => a.id === body.id);
if (idx >= 0) {
this.antennas[idx] = {
...body,
lastUsedAt: new Date(body.lastUsedAt),
};
} else {
// サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり
this.antennas.push({
...body,
lastUsedAt: new Date(body.lastUsedAt),
});
}
}
break;
case 'antennaDeleted':
this.antennas = this.antennas.filter(a => a.id !== body.id);
@@ -84,7 +94,7 @@ export class AntennaService implements OnApplicationShutdown {
const redisPipeline = this.redisForTimelines.pipeline();
for (const antenna of matchedAntennas) {
this.funoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
this.fanoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
}

View File

@@ -4,6 +4,7 @@
*/
import { Module } from '@nestjs/common';
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
import { AccountMoveService } from './AccountMoveService.js';
import { AccountUpdateService } from './AccountUpdateService.js';
import { AiService } from './AiService.js';
@@ -62,7 +63,7 @@ import { FileInfoService } from './FileInfoService.js';
import { SearchService } from './SearchService.js';
import { ClipService } from './ClipService.js';
import { FeaturedService } from './FeaturedService.js';
import { FunoutTimelineService } from './FunoutTimelineService.js';
import { FanoutTimelineService } from './FanoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
import { RegistryApiService } from './RegistryApiService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
@@ -194,7 +195,8 @@ const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: Fi
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService };
const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
@@ -330,7 +332,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SearchService,
ClipService,
FeaturedService,
FunoutTimelineService,
FanoutTimelineService,
FanoutTimelineEndpointService,
ChannelFollowingService,
RegistryApiService,
ChartLoggerService,
@@ -459,7 +462,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SearchService,
$ClipService,
$FeaturedService,
$FunoutTimelineService,
$FanoutTimelineService,
$FanoutTimelineEndpointService,
$ChannelFollowingService,
$RegistryApiService,
$ChartLoggerService,
@@ -589,7 +593,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SearchService,
ClipService,
FeaturedService,
FunoutTimelineService,
FanoutTimelineService,
FanoutTimelineEndpointService,
ChannelFollowingService,
RegistryApiService,
FederationChart,
@@ -717,7 +722,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SearchService,
$ClipService,
$FeaturedService,
$FunoutTimelineService,
$FanoutTimelineService,
$FanoutTimelineEndpointService,
$ChannelFollowingService,
$RegistryApiService,
$FederationChart,

View File

@@ -3,9 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URLSearchParams } from 'node:url';
import * as nodemailer from 'nodemailer';
import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator';
import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js';
import { MetaService } from '@/core/MetaService.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@@ -13,6 +15,7 @@ import type Logger from '@/logger.js';
import type { UserProfilesRepository } from '@/models/_.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
@Injectable()
export class EmailService {
@@ -27,6 +30,7 @@ export class EmailService {
private metaService: MetaService,
private loggerService: LoggerService,
private httpRequestService: HttpRequestService,
) {
this.logger = this.loggerService.getLogger('email');
}
@@ -160,14 +164,25 @@ export class EmailService {
email: emailAddress,
});
const validated = meta.enableActiveEmailValidation ? await validateEmail({
email: emailAddress,
validateRegex: true,
validateMx: true,
validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
validateDisposable: true, // 捨てアドかどうかチェック
validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
}) : { valid: true, reason: null };
const verifymailApi = meta.enableVerifymailApi && meta.verifymailAuthKey != null;
let validated;
if (meta.enableActiveEmailValidation && meta.verifymailAuthKey) {
if (verifymailApi) {
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
} else {
validated = meta.enableActiveEmailValidation ? await validateEmail({
email: emailAddress,
validateRegex: true,
validateMx: true,
validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
validateDisposable: true, // 捨てアドかどうかチェック
validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
}) : { valid: true, reason: null };
}
} else {
validated = { valid: true, reason: null };
}
const available = exist === 0 && validated.valid;
@@ -182,4 +197,65 @@ export class EmailService {
null,
};
}
private async verifyMail(emailAddress: string, verifymailAuthKey: string): Promise<{
valid: boolean;
reason: 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | null;
}> {
const endpoint = 'https://verifymail.io/api/' + emailAddress + '?key=' + verifymailAuthKey;
const res = await this.httpRequestService.send(endpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json, */*',
},
});
const json = (await res.json()) as {
block: boolean;
catch_all: boolean;
deliverable_email: boolean;
disposable: boolean;
domain: string;
email_address: string;
email_provider: string;
mx: boolean;
mx_fallback: boolean;
mx_host: string[];
mx_ip: string[];
mx_priority: { [key: string]: number };
privacy: boolean;
related_domains: string[];
};
if (json.email_address === undefined) {
return {
valid: false,
reason: 'format',
};
}
if (json.deliverable_email !== undefined && !json.deliverable_email) {
return {
valid: false,
reason: 'smtp',
};
}
if (json.disposable) {
return {
valid: false,
reason: 'disposable',
};
}
if (json.mx !== undefined && !json.mx) {
return {
valid: false,
reason: 'mx',
};
}
return {
valid: true,
reason: null,
};
}
}

View File

@@ -0,0 +1,186 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import type { MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
import { Packed } from '@/misc/json-schema.js';
import type { NotesRepository } from '@/models/_.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
import { CacheService } from '@/core/CacheService.js';
import { isReply } from '@/misc/is-reply.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
type TimelineOptions = {
untilId: string | null,
sinceId: string | null,
limit: number,
allowPartial: boolean,
me?: { id: MiUser['id'] } | undefined | null,
useDbFallback: boolean,
redisTimelines: FanoutTimelineName[],
noteFilter?: (note: MiNote) => boolean,
alwaysIncludeMyNotes?: boolean;
ignoreAuthorFromBlock?: boolean;
ignoreAuthorFromMute?: boolean;
excludeNoFiles?: boolean;
excludeReplies?: boolean;
excludePureRenotes: boolean;
dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>,
};
@Injectable()
export class FanoutTimelineEndpointService {
constructor(
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private noteEntityService: NoteEntityService,
private cacheService: CacheService,
private fanoutTimelineService: FanoutTimelineService,
) {
}
@bindThis
async timeline(ps: TimelineOptions): Promise<Packed<'Note'>[]> {
return await this.noteEntityService.packMany(await this.getMiNotes(ps), ps.me);
}
@bindThis
private async getMiNotes(ps: TimelineOptions): Promise<MiNote[]> {
let noteIds: string[];
let shouldFallbackToDb = false;
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
const shouldPrepend = ps.sinceId && !ps.untilId;
const idCompare: (a: string, b: string) => number = shouldPrepend ? (a, b) => a < b ? -1 : 1 : (a, b) => a > b ? -1 : 1;
const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId);
// TODO: いい感じにgetMulti内でソート済だからuniqするときにredisResultが全てソート済なのを利用して再ソートを避けたい
const redisResultIds = Array.from(new Set(redisResult.flat(1)));
redisResultIds.sort(idCompare);
noteIds = redisResultIds.slice(0, ps.limit);
shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0);
if (!shouldFallbackToDb) {
let filter = ps.noteFilter ?? (_note => true);
if (ps.alwaysIncludeMyNotes && ps.me) {
const me = ps.me;
const parentFilter = filter;
filter = (note) => note.userId === me.id || parentFilter(note);
}
if (ps.excludeNoFiles) {
const parentFilter = filter;
filter = (note) => note.fileIds.length !== 0 && parentFilter(note);
}
if (ps.excludeReplies) {
const parentFilter = filter;
filter = (note) => !isReply(note, ps.me?.id) && parentFilter(note);
}
if (ps.excludePureRenotes) {
const parentFilter = filter;
filter = (note) => !isPureRenote(note) && parentFilter(note);
}
if (ps.me) {
const me = ps.me;
const [
userIdsWhoMeMuting,
userIdsWhoMeMutingRenotes,
userIdsWhoBlockingMe,
userMutedInstances,
] = await Promise.all([
this.cacheService.userMutingsCache.fetch(ps.me.id),
this.cacheService.renoteMutingsCache.fetch(ps.me.id),
this.cacheService.userBlockedCache.fetch(ps.me.id),
this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)),
]);
const parentFilter = filter;
filter = (note) => {
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
if (isInstanceMuted(note, userMutedInstances)) return false;
return parentFilter(note);
};
}
const redisTimeline: MiNote[] = [];
let readFromRedis = 0;
let lastSuccessfulRate = 1; // rateをキャッシュする
while ((redisResultIds.length - readFromRedis) !== 0) {
const remainingToRead = ps.limit - redisTimeline.length;
// DBからの取り直しを減らす初回と同じ割合以上で成功すると仮定するが、クエリの長さを考えて三倍まで
const countToGet = Math.ceil(remainingToRead * Math.min(1.1 / lastSuccessfulRate, 3));
noteIds = redisResultIds.slice(readFromRedis, readFromRedis + countToGet);
readFromRedis += noteIds.length;
const gotFromDb = await this.getAndFilterFromDb(noteIds, filter, idCompare);
redisTimeline.push(...gotFromDb);
lastSuccessfulRate = gotFromDb.length / noteIds.length;
if (ps.allowPartial ? redisTimeline.length !== 0 : redisTimeline.length >= ps.limit) {
// 十分Redisからとれた
const result = redisTimeline.slice(0, ps.limit);
if (shouldPrepend) result.reverse();
return result;
}
}
// まだ足りない分はDBにフォールバック
const remainingToRead = ps.limit - redisTimeline.length;
let dbUntil: string | null;
let dbSince: string | null;
if (shouldPrepend) {
redisTimeline.reverse();
dbUntil = ps.untilId;
dbSince = noteIds[noteIds.length - 1];
} else {
dbUntil = noteIds[noteIds.length - 1];
dbSince = ps.sinceId;
}
const gotFromDb = await ps.dbFallback(dbUntil, dbSince, remainingToRead);
return shouldPrepend ? [...gotFromDb, ...redisTimeline] : [...redisTimeline, ...gotFromDb];
}
return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit);
}
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
const notes = (await query.getMany()).filter(noteFilter);
notes.sort((a, b) => idCompare(a.id, b.id));
return notes;
}
}

View File

@@ -9,8 +9,37 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
export type FanoutTimelineName =
// home timeline
| `homeTimeline:${string}`
| `homeTimelineWithFiles:${string}` // only notes with files are included
// local timeline
| `localTimeline` // replies are not included
| `localTimelineWithFiles` // only non-reply notes with files are included
| `localTimelineWithReplies` // only replies are included
| `localTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id.
// antenna
| `antennaTimeline:${string}`
// user timeline
| `userTimeline:${string}` // replies are not included
| `userTimelineWithFiles:${string}` // only non-reply notes with files are included
| `userTimelineWithReplies:${string}` // only replies are included
| `userTimelineWithChannel:${string}` // only channel notes are included, replies are included
// user list timelines
| `userListTimeline:${string}`
| `userListTimelineWithFiles:${string}` // only notes with files are included
// channel timelines
| `channelTimeline:${string}` // replies are included
// role timelines
| `roleTimeline:${string}` // any notes are included
@Injectable()
export class FunoutTimelineService {
export class FanoutTimelineService {
constructor(
@Inject(DI.redisForTimelines)
private redisForTimelines: Redis.Redis,
@@ -20,7 +49,7 @@ export class FunoutTimelineService {
}
@bindThis
public push(tl: string, id: string, maxlen: number, pipeline: Redis.ChainableCommander) {
public push(tl: FanoutTimelineName, id: string, maxlen: number, pipeline: Redis.ChainableCommander) {
// リモートから遅れて届いた(もしくは後から追加された)投稿日時が古い投稿が追加されるとページネーション時に問題を引き起こすため、
// 3分以内に投稿されたものでない場合、Redisにある最古のIDより新しい場合のみ追加する
if (this.idService.parse(id).date.getTime() > Date.now() - 1000 * 60 * 3) {
@@ -41,7 +70,7 @@ export class FunoutTimelineService {
}
@bindThis
public get(name: string, untilId?: string | null, sinceId?: string | null) {
public get(name: FanoutTimelineName, untilId?: string | null, sinceId?: string | null) {
if (untilId && sinceId) {
return this.redisForTimelines.lrange('list:' + name, 0, -1)
.then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1));
@@ -58,7 +87,7 @@ export class FunoutTimelineService {
}
@bindThis
public getMulti(name: string[], untilId?: string | null, sinceId?: string | null): Promise<string[][]> {
public getMulti(name: FanoutTimelineName[], untilId?: string | null, sinceId?: string | null): Promise<string[][]> {
const pipeline = this.redisForTimelines.pipeline();
for (const n of name) {
pipeline.lrange('list:' + n, 0, -1);
@@ -79,7 +108,7 @@ export class FunoutTimelineService {
}
@bindThis
public purge(name: string) {
public purge(name: FanoutTimelineName) {
return this.redisForTimelines.del('list:' + name);
}
}

View File

@@ -5,14 +5,17 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { MiNote, MiUser } from '@/models/_.js';
import type { MiGalleryPost, MiNote, MiUser } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
export const GALLERY_POSTS_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
const PER_USER_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 7; // 1週間ごと
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60; // 1時間ごと
const featuredEpoc = new Date('2023-01-01T00:00:00Z').getTime();
@Injectable()
export class FeaturedService {
constructor(
@@ -23,7 +26,7 @@ export class FeaturedService {
@bindThis
private getCurrentWindow(windowRange: number): number {
const passed = new Date().getTime() - new Date(new Date().getFullYear(), 0, 1).getTime();
const passed = new Date().getTime() - featuredEpoc;
return Math.floor(passed / windowRange);
}
@@ -79,6 +82,11 @@ export class FeaturedService {
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
}
@bindThis
public updateGalleryPostsRanking(galleryPostId: MiGalleryPost['id'], score = 1): Promise<void> {
return this.updateRankingOf('featuredGalleryPostsRanking', GALLERY_POSTS_RANKING_WINDOW, galleryPostId, score);
}
@bindThis
public updateInChannelNotesRanking(channelId: MiNote['channelId'], noteId: MiNote['id'], score = 1): Promise<void> {
return this.updateRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
@@ -99,6 +107,11 @@ export class FeaturedService {
return this.getRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, threshold);
}
@bindThis
public getGalleryPostsRanking(threshold: number): Promise<MiGalleryPost['id'][]> {
return this.getRankingOf('featuredGalleryPostsRanking', GALLERY_POSTS_RANKING_WINDOW, threshold);
}
@bindThis
public getInChannelNotesRanking(channelId: MiNote['channelId'], threshold: number): Promise<MiNote['id'][]> {
return this.getRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, threshold);

View File

@@ -7,11 +7,11 @@ import { Inject, Injectable } from '@nestjs/common';
import { ulid } from 'ulid';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { genAid, parseAid } from '@/misc/id/aid.js';
import { genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
import { genAid, isSafeAidT, parseAid } from '@/misc/id/aid.js';
import { genAidx, isSafeAidxT, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, isSafeMeidT, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, isSafeMeidgT, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, isSafeObjectIdT, parseObjectId } from '@/misc/id/object-id.js';
import { bindThis } from '@/decorators.js';
import { parseUlid } from '@/misc/id/ulid.js';
@@ -26,6 +26,19 @@ export class IdService {
this.method = config.id.toLowerCase();
}
@bindThis
public isSafeT(t: number): boolean {
switch (this.method) {
case 'aid': return isSafeAidT(t);
case 'aidx': return isSafeAidxT(t);
case 'meid': return isSafeMeidT(t);
case 'meidg': return isSafeMeidgT(t);
case 'ulid': return t > 0;
case 'objectid': return isSafeObjectIdT(t);
default: throw new Error('unrecognized id generation method');
}
}
/**
* 時間を元にIDを生成します(省略時は現在日時)
* @param time 日時

View File

@@ -250,6 +250,12 @@ export class MfmService {
}
}
function fnDefault(node: mfm.MfmFn) {
const el = doc.createElement('i');
appendChildren(node.children, el);
return el;
}
const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any } = {
bold: (node) => {
const el = doc.createElement('b');
@@ -276,9 +282,69 @@ export class MfmService {
},
fn: (node) => {
const el = doc.createElement('i');
appendChildren(node.children, el);
return el;
switch (node.props.name) {
case 'unixtime': {
const text = node.children[0].type === 'text' ? node.children[0].props.text : '';
try {
const date = new Date(parseInt(text, 10) * 1000);
const el = doc.createElement('time');
el.setAttribute('datetime', date.toISOString());
el.textContent = date.toISOString();
return el;
} catch (err) {
return fnDefault(node);
}
}
case 'ruby': {
if (node.children.length === 1) {
const child = node.children[0];
const text = child.type === 'text' ? child.props.text : '';
const rubyEl = doc.createElement('ruby');
const rtEl = doc.createElement('rt');
// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備りゅうび」となるようにする
const rpStartEl = doc.createElement('rp');
rpStartEl.appendChild(doc.createTextNode('('));
const rpEndEl = doc.createElement('rp');
rpEndEl.appendChild(doc.createTextNode(')'));
rubyEl.appendChild(doc.createTextNode(text.split(' ')[0]));
rtEl.appendChild(doc.createTextNode(text.split(' ')[1]));
rubyEl.appendChild(rpStartEl);
rubyEl.appendChild(rtEl);
rubyEl.appendChild(rpEndEl);
return rubyEl;
} else {
const rt = node.children.at(-1);
if (!rt) {
return fnDefault(node);
}
const text = rt.type === 'text' ? rt.props.text : '';
const rubyEl = doc.createElement('ruby');
const rtEl = doc.createElement('rt');
// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備りゅうび」となるようにする
const rpStartEl = doc.createElement('rp');
rpStartEl.appendChild(doc.createTextNode('('));
const rpEndEl = doc.createElement('rp');
rpEndEl.appendChild(doc.createTextNode(')'));
appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
rtEl.appendChild(doc.createTextNode(text.trim()));
rubyEl.appendChild(rpStartEl);
rubyEl.appendChild(rtEl);
rubyEl.appendChild(rpEndEl);
return rubyEl;
}
}
default: {
return fnDefault(node);
}
}
},
blockCode: (node) => {

View File

@@ -54,9 +54,10 @@ import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { isReply } from '@/misc/is-reply.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -194,7 +195,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private idService: IdService,
private globalEventService: GlobalEventService,
private queueService: QueueService,
private funoutTimelineService: FunoutTimelineService,
private fanoutTimelineService: FanoutTimelineService,
private noteReadService: NoteReadService,
private notificationService: NotificationService,
private relayService: RelayService,
@@ -521,11 +522,13 @@ export class NoteCreateService implements OnApplicationShutdown {
followeeId: user.id,
notify: 'normal',
}).then(followings => {
for (const following of followings) {
// TODO: ワードミュート考慮
this.notificationService.createNotification(following.followerId, 'note', {
noteId: note.id,
}, user.id);
if (note.visibility !== 'specified') {
for (const following of followings) {
// TODO: ワードミュート考慮
this.notificationService.createNotification(following.followerId, 'note', {
noteId: note.id,
}, user.id);
}
}
});
}
@@ -841,9 +844,9 @@ export class NoteCreateService implements OnApplicationShutdown {
const r = this.redisForTimelines.pipeline();
if (note.channelId) {
this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
this.funoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
const channelFollowings = await this.channelFollowingsRepository.find({
where: {
@@ -853,9 +856,9 @@ export class NoteCreateService implements OnApplicationShutdown {
});
for (const channelFollowing of channelFollowings) {
this.funoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.funoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
} else {
@@ -889,13 +892,13 @@ export class NoteCreateService implements OnApplicationShutdown {
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
if (isReply(note, following.followerId)) {
if (!following.withReplies) continue;
}
this.funoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.funoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
@@ -907,40 +910,43 @@ export class NoteCreateService implements OnApplicationShutdown {
) continue;
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
if (isReply(note, userListMembership.userListUserId)) {
if (!userListMembership.withReplies) continue;
}
this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
}
}
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.funoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
}
}
// 自分自身以外への返信
if (note.replyId && note.replyUserId !== note.userId) {
this.funoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
if (isReply(note)) {
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
if (note.visibility === 'public' && note.userHost == null) {
this.funoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
if (note.replyUserHost == null) {
this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r);
}
}
} else {
this.funoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
if (note.fileIds.length > 0) {
this.funoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
}
if (note.visibility === 'public' && note.userHost == null) {
this.funoutTimelineService.push('localTimeline', note.id, 1000, r);
this.fanoutTimelineService.push('localTimeline', note.id, 1000, r);
if (note.fileIds.length > 0) {
this.funoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
}
}
}

View File

@@ -77,7 +77,7 @@ export class NotePiningService {
} as MiUserNotePining);
// Deliver to remote followers
if (this.userEntityService.isLocalUser(user)) {
if (this.userEntityService.isLocalUser(user) && !note.localOnly && ['public', 'home'].includes(note.visibility)) {
this.deliverPinnedChange(user.id, note.id, true);
}
}
@@ -105,7 +105,7 @@ export class NotePiningService {
});
// Deliver to remote followers
if (this.userEntityService.isLocalUser(user)) {
if (this.userEntityService.isLocalUser(user) && !note.localOnly && ['public', 'home'].includes(note.visibility)) {
this.deliverPinnedChange(user.id, noteId, false);
}
}

View File

@@ -20,7 +20,7 @@ import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type RolePolicies = {
@@ -87,6 +87,9 @@ export class RoleService implements OnApplicationShutdown {
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.redisForTimelines)
private redisForTimelines: Redis.Redis,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@@ -105,7 +108,7 @@ export class RoleService implements OnApplicationShutdown {
private globalEventService: GlobalEventService,
private idService: IdService,
private moderationLogService: ModerationLogService,
private funoutTimelineService: FunoutTimelineService,
private fanoutTimelineService: FanoutTimelineService,
) {
//this.onMessage = this.onMessage.bind(this);
@@ -476,10 +479,10 @@ export class RoleService implements OnApplicationShutdown {
public async addNoteToRoleTimeline(note: Packed<'Note'>): Promise<void> {
const roles = await this.getUserRoles(note.userId);
const redisPipeline = this.redisClient.pipeline();
const redisPipeline = this.redisForTimelines.pipeline();
for (const role of roles) {
this.funoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
this.fanoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
this.globalEventService.publishRoleTimelineStream(role.id, 'note', note);
}

View File

@@ -12,6 +12,8 @@ import { MiNote } from '@/models/Note.js';
import { MiUser } from '@/models/_.js';
import type { NotesRepository } from '@/models/_.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { CacheService } from '@/core/CacheService.js';
import { QueryService } from '@/core/QueryService.js';
import { IdService } from '@/core/IdService.js';
import type { Index, MeiliSearch } from 'meilisearch';
@@ -74,6 +76,7 @@ export class SearchService {
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private cacheService: CacheService,
private queryService: QueryService,
private idService: IdService,
) {
@@ -187,8 +190,19 @@ export class SearchService {
limit: pagination.limit,
});
if (res.hits.length === 0) return [];
const notes = await this.notesRepository.findBy({
const [
userIdsWhoMeMuting,
userIdsWhoBlockingMe,
] = me ? await Promise.all([
this.cacheService.userMutingsCache.fetch(me.id),
this.cacheService.userBlockedCache.fetch(me.id),
]) : [new Set<string>(), new Set<string>()];
const notes = (await this.notesRepository.findBy({
id: In(res.hits.map(x => x.id)),
})).filter(note => {
if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
return true;
});
return notes.sort((a, b) => a.id > b.id ? -1 : 1);
} else {

View File

@@ -29,7 +29,7 @@ import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import { AccountMoveService } from '@/core/AccountMoveService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import Logger from '../logger.js';
const logger = new Logger('following/create');
@@ -84,7 +84,7 @@ export class UserFollowingService implements OnModuleInit {
private webhookService: WebhookService,
private apRendererService: ApRendererService,
private accountMoveService: AccountMoveService,
private funoutTimelineService: FunoutTimelineService,
private fanoutTimelineService: FanoutTimelineService,
private perUserFollowingChart: PerUserFollowingChart,
private instanceChart: InstanceChart,
) {
@@ -304,8 +304,6 @@ export class UserFollowingService implements OnModuleInit {
});
}
});
this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
}
// Publish followed event
@@ -373,8 +371,6 @@ export class UserFollowingService implements OnModuleInit {
});
}
});
this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
}
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {

View File

@@ -306,9 +306,15 @@ export class ApInboxService {
this.logger.info(`Creating the (Re)Note: ${uri}`);
const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc);
const createdAt = activity.published ? new Date(activity.published) : null;
if (createdAt && createdAt < this.idService.parse(renote.id).date) {
this.logger.warn('skip: malformed createdAt');
return;
}
await this.noteCreateService.create(actor, {
createdAt: activity.published ? new Date(activity.published) : null,
createdAt,
renote,
visibility: activityAudience.visibility,
visibleUsers: activityAudience.visibleUsers,

View File

@@ -464,7 +464,7 @@ export class ApRendererService {
const attachment = profile.fields.map(field => ({
type: 'PropertyValue',
name: field.name,
value: /^https?:/.test(field.value)
value: (field.value.startsWith('http://') || field.value.startsWith('https://'))
? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
: field.value,
}));

View File

@@ -92,6 +92,10 @@ export class ApNoteService {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
}
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
return new Error('invalid Note: published timestamp is malformed');
}
return null;
}

View File

@@ -47,6 +47,7 @@ export class InstanceEntityService {
faviconUrl: instance.faviconUrl,
themeColor: instance.themeColor,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
};
}

View File

@@ -198,12 +198,14 @@ export class NotificationEntityService implements OnModuleInit {
});
} else if (notification.type === 'renote:grouped') {
const users = await Promise.all(notification.userIds.map(userId => {
const user = hint?.packedUsers != null
? hint.packedUsers.get(userId)
: this.userEntityService.pack(userId!, { id: meId }, {
detail: false,
});
return user;
const packedUser = hint?.packedUsers != null ? hint.packedUsers.get(userId) : null;
if (packedUser) {
return packedUser;
}
return this.userEntityService.pack(userId, { id: meId }, {
detail: false,
});
}));
return await awaitAll({
id: notification.id,

View File

@@ -473,6 +473,7 @@ export class UserEntityService implements OnModuleInit {
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
unreadNotificationsCount: notificationsInfo?.unreadCount,
mutedWords: profile!.mutedWords,
hardMutedWords: profile!.hardMutedWords,
mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: [], // 後方互換性のため
notificationRecieveConfig: profile!.notificationRecieveConfig,

View File

@@ -34,3 +34,7 @@ export function parseAid(id: string): { date: Date; } {
const time = parseInt(id.slice(0, 8), 36) + TIME2000;
return { date: new Date(time) };
}
export function isSafeAidT(t: number): boolean {
return t > TIME2000;
}

View File

@@ -41,3 +41,7 @@ export function parseAidx(id: string): { date: Date; } {
const time = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000;
return { date: new Date(time) };
}
export function isSafeAidxT(t: number): boolean {
return t > TIME2000;
}

View File

@@ -38,3 +38,7 @@ export function parseMeid(id: string): { date: Date; } {
date: new Date(parseInt(id.slice(0, 12), 16) - 0x800000000000),
};
}
export function isSafeMeidT(t: number): boolean {
return t > 0;
}

View File

@@ -38,3 +38,7 @@ export function parseMeidg(id: string): { date: Date; } {
date: new Date(parseInt(id.slice(1, 12), 16)),
};
}
export function isSafeMeidgT(t: number): boolean {
return t > 0;
}

View File

@@ -38,3 +38,7 @@ export function parseObjectId(id: string): { date: Date; } {
date: new Date(parseInt(id.slice(0, 8), 16) * 1000),
};
}
export function isSafeObjectIdT(t: number): boolean {
return t > 0;
}

View File

@@ -3,12 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MiNote } from '@/models/Note.js';
import type { Packed } from './json-schema.js';
export function isInstanceMuted(note: Packed<'Note'>, mutedInstances: Set<string>): boolean {
if (mutedInstances.has(note.user.host ?? '')) return true;
if (mutedInstances.has(note.reply?.user.host ?? '')) return true;
if (mutedInstances.has(note.renote?.user.host ?? '')) return true;
export function isInstanceMuted(note: Packed<'Note'> | MiNote, mutedInstances: Set<string>): boolean {
if (mutedInstances.has(note.user?.host ?? '')) return true;
if (mutedInstances.has(note.reply?.user?.host ?? '')) return true;
if (mutedInstances.has(note.renote?.user?.host ?? '')) return true;
return false;
}

View File

@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MiUser } from '@/models/User.js';
export function isReply(note: any, viewerId?: MiUser['id'] | undefined | null): boolean {
return note.replyId && note.replyUserId !== note.userId && note.replyUserId !== viewerId;
}

View File

@@ -36,6 +36,8 @@ import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -71,6 +73,9 @@ export const refs = {
EmojiSimple: packedEmojiSimpleSchema,
EmojiDetailed: packedEmojiDetailedSchema,
Flash: packedFlashSchema,
Signin: packedSigninSchema,
RoleLite: packedRoleLiteSchema,
Role: packedRoleSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;

View File

@@ -446,6 +446,17 @@ export class MiMeta {
})
public enableActiveEmailValidation: boolean;
@Column('boolean', {
default: false,
})
public enableVerifymailApi: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public verifymailAuthKey: string | null;
@Column('boolean', {
default: true,
})
@@ -494,6 +505,11 @@ export class MiMeta {
})
public enableFanoutTimeline: boolean;
@Column('boolean', {
default: true,
})
public enableFanoutTimelineDbFallback: boolean;
@Column('integer', {
default: 300,
})

View File

@@ -29,6 +29,7 @@ export class MiUserProfile {
})
public location: string | null;
@Index()
@Column('char', {
length: 10, nullable: true,
comment: 'The birthday (YYYY-MM-DD) of the User.',
@@ -215,7 +216,12 @@ export class MiUserProfile {
@Column('jsonb', {
default: [],
})
public mutedWords: string[][];
public mutedWords: (string[] | string)[];
@Column('jsonb', {
default: [],
})
public hardMutedWords: (string[] | string)[];
@Column('jsonb', {
default: [],

View File

@@ -42,11 +42,15 @@ export const packedAnnouncementSchema = {
type: 'string',
optional: false, nullable: false,
},
forYou: {
needConfirmationToRead: {
type: 'boolean',
optional: false, nullable: false,
},
needConfirmationToRead: {
silence: {
type: 'boolean',
optional: false, nullable: false,
},
forYou: {
type: 'boolean',
optional: false, nullable: false,
},

View File

@@ -19,7 +19,7 @@ export const packedChannelSchema = {
},
lastNotedAt: {
type: 'string',
optional: false, nullable: true,
nullable: true, optional: false,
format: 'date-time',
},
name: {
@@ -28,38 +28,18 @@ export const packedChannelSchema = {
},
description: {
type: 'string',
nullable: true, optional: false,
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
isArchived: {
type: 'boolean',
optional: false, nullable: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
usersCount: {
type: 'number',
nullable: false, optional: false,
},
isFollowing: {
type: 'boolean',
optional: true, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
optional: false, nullable: true,
},
userId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
pinnedNoteIds: {
type: 'array',
nullable: false, optional: false,
@@ -72,6 +52,18 @@ export const packedChannelSchema = {
type: 'string',
optional: false, nullable: false,
},
isArchived: {
type: 'boolean',
optional: false, nullable: false,
},
usersCount: {
type: 'number',
nullable: false, optional: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
@@ -80,5 +72,22 @@ export const packedChannelSchema = {
type: 'boolean',
optional: false, nullable: false,
},
isFollowing: {
type: 'boolean',
optional: true, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
},
pinnedNotes: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
},
},
} as const;

View File

@@ -44,13 +44,13 @@ export const packedClipSchema = {
type: 'boolean',
optional: false, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
},
favoritedCount: {
type: 'number',
optional: false, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View File

@@ -74,7 +74,7 @@ export const packedDriveFileSchema = {
},
url: {
type: 'string',
optional: false, nullable: true,
optional: false, nullable: false,
format: 'url',
},
thumbnailUrl: {

View File

@@ -21,6 +21,12 @@ export const packedDriveFolderSchema = {
type: 'string',
optional: false, nullable: false,
},
parentId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
foldersCount: {
type: 'number',
optional: true, nullable: false,
@@ -29,12 +35,6 @@ export const packedDriveFolderSchema = {
type: 'number',
optional: true, nullable: false,
},
parentId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object',
optional: true, nullable: true,

View File

@@ -79,6 +79,10 @@ export const packedFederationInstanceSchema = {
type: 'string',
optional: false, nullable: true,
},
isSilenced: {
type: 'boolean',
optional: false, nullable: false,
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
@@ -93,15 +97,15 @@ export const packedFederationInstanceSchema = {
type: 'string',
optional: false, nullable: true,
},
isSilenced: {
type: "boolean",
optional: false,
nullable: false,
},
infoUpdatedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
latestRequestReceivedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
},
} as const;

View File

@@ -22,6 +22,16 @@ export const packedFlashSchema = {
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
title: {
type: 'string',
optional: false, nullable: false,
@@ -34,16 +44,6 @@ export const packedFlashSchema = {
type: 'string',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
likedCount: {
type: 'number',
optional: false, nullable: true,

View File

@@ -22,16 +22,16 @@ export const packedFollowingSchema = {
optional: false, nullable: false,
format: 'id',
},
followee: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
followerId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
followee: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
follower: {
type: 'object',
optional: true, nullable: false,

View File

@@ -22,14 +22,6 @@ export const packedGalleryPostSchema = {
optional: false, nullable: false,
format: 'date-time',
},
title: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
userId: {
type: 'string',
optional: false, nullable: false,
@@ -40,6 +32,14 @@ export const packedGalleryPostSchema = {
ref: 'UserLite',
optional: false, nullable: false,
},
title: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
fileIds: {
type: 'array',
optional: true, nullable: false,
@@ -70,5 +70,13 @@ export const packedGalleryPostSchema = {
type: 'boolean',
optional: false, nullable: false,
},
likedCount: {
type: 'number',
optional: false, nullable: false,
},
isLiked: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View File

@@ -127,22 +127,26 @@ export const packedNoteSchema = {
channel: {
type: 'object',
optional: true, nullable: true,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: true,
},
isSensitive: {
type: 'boolean',
optional: true, nullable: false,
},
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
color: {
type: 'string',
optional: false, nullable: false,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
allowRenoteToExternal: {
type: 'boolean',
optional: false, nullable: false,
},
},
},
@@ -182,6 +186,10 @@ export const packedNoteSchema = {
optional: false, nullable: false,
},
},
clippedCount: {
type: 'number',
optional: true, nullable: false,
},
myReaction: {
type: 'object',

View File

@@ -42,13 +42,9 @@ export const packedNotificationSchema = {
type: 'string',
optional: true, nullable: true,
},
choice: {
type: 'number',
optional: true, nullable: true,
},
invitation: {
type: 'object',
optional: true, nullable: true,
achievement: {
type: 'string',
optional: true, nullable: false,
},
body: {
type: 'string',
@@ -81,14 +77,14 @@ export const packedNotificationSchema = {
required: ['user', 'reaction'],
},
},
},
users: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
users: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
},
},
} as const;

View File

@@ -22,6 +22,32 @@ export const packedPageSchema = {
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
content: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
},
},
variables: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
},
},
title: {
type: 'string',
optional: false, nullable: false,
@@ -34,23 +60,47 @@ export const packedPageSchema = {
type: 'string',
optional: false, nullable: true,
},
content: {
type: 'array',
hideTitleWhenPinned: {
type: 'boolean',
optional: false, nullable: false,
},
variables: {
type: 'array',
alignCenter: {
type: 'boolean',
optional: false, nullable: false,
},
userId: {
font: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
script: {
type: 'string',
optional: false, nullable: false,
},
eyeCatchingImageId: {
type: 'string',
optional: false, nullable: true,
},
eyeCatchingImage: {
type: 'object',
optional: false, nullable: true,
ref: 'DriveFile',
},
attachedFiles: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
likedCount: {
type: 'number',
optional: false, nullable: false,
},
isLiked: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View File

@@ -0,0 +1,157 @@
const rolePolicyValue = {
type: 'object',
properties: {
value: {
oneOf: [
{
type: 'integer',
optional: false, nullable: false,
},
{
type: 'boolean',
optional: false, nullable: false,
},
],
},
priority: {
type: 'integer',
optional: false, nullable: false,
},
useDefault: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;
export const packedRoleLiteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
name: {
type: 'string',
optional: false, nullable: false,
example: 'New Role',
},
color: {
type: 'string',
optional: false, nullable: true,
example: '#000000',
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
},
description: {
type: 'string',
optional: false, nullable: false,
},
isModerator: {
type: 'boolean',
optional: false, nullable: false,
example: false,
},
isAdministrator: {
type: 'boolean',
optional: false, nullable: false,
example: false,
},
displayOrder: {
type: 'integer',
optional: false, nullable: false,
example: 0,
},
},
} as const;
export const packedRoleSchema = {
type: 'object',
allOf: [
{
type: 'object',
ref: 'RoleLite',
},
{
type: 'object',
properties: {
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
target: {
type: 'string',
optional: false, nullable: false,
enum: ['manual', 'conditional'],
},
condFormula: {
type: 'object',
optional: false, nullable: false,
},
isPublic: {
type: 'boolean',
optional: false, nullable: false,
example: false,
},
isExplorable: {
type: 'boolean',
optional: false, nullable: false,
example: false,
},
asBadge: {
type: 'boolean',
optional: false, nullable: false,
example: false,
},
canEditMembersByModerator: {
type: 'boolean',
optional: false, nullable: false,
example: false,
},
policies: {
type: 'object',
optional: false, nullable: false,
properties: {
pinLimit: rolePolicyValue,
canInvite: rolePolicyValue,
clipLimit: rolePolicyValue,
canHideAds: rolePolicyValue,
inviteLimit: rolePolicyValue,
antennaLimit: rolePolicyValue,
gtlAvailable: rolePolicyValue,
ltlAvailable: rolePolicyValue,
webhookLimit: rolePolicyValue,
canPublicNote: rolePolicyValue,
userListLimit: rolePolicyValue,
wordMuteLimit: rolePolicyValue,
alwaysMarkNsfw: rolePolicyValue,
canSearchNotes: rolePolicyValue,
driveCapacityMb: rolePolicyValue,
rateLimitFactor: rolePolicyValue,
inviteLimitCycle: rolePolicyValue,
noteEachClipsLimit: rolePolicyValue,
inviteExpirationTime: rolePolicyValue,
canManageCustomEmojis: rolePolicyValue,
userEachUserListsLimit: rolePolicyValue,
canManageAvatarDecorations: rolePolicyValue,
canUseTranslator: rolePolicyValue,
},
},
usersCount: {
type: 'integer',
optional: false, nullable: false,
},
},
},
],
} as const;

View File

@@ -0,0 +1,26 @@
export const packedSigninSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
ip: {
type: 'string',
optional: false, nullable: false,
},
headers: {
type: 'object',
optional: false, nullable: false,
},
success: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

View File

@@ -3,6 +3,18 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
const notificationRecieveConfig = {
type: 'object',
nullable: false, optional: true,
properties: {
type: {
type: 'string',
nullable: false, optional: false,
enum: ['all', 'following', 'follower', 'mutualFollow', 'list', 'never'],
},
},
} as const;
export const packedUserLiteSchema = {
type: 'object',
properties: {
@@ -49,11 +61,6 @@ export const packedUserLiteSchema = {
nullable: false, optional: false,
format: 'id',
},
url: {
type: 'string',
format: 'url',
nullable: false, optional: false,
},
angle: {
type: 'number',
nullable: false, optional: true,
@@ -62,19 +69,14 @@ export const packedUserLiteSchema = {
type: 'boolean',
nullable: false, optional: true,
},
url: {
type: 'string',
format: 'url',
nullable: false, optional: false,
},
},
},
},
isAdmin: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isModerator: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isBot: {
type: 'boolean',
nullable: false, optional: true,
@@ -83,12 +85,67 @@ export const packedUserLiteSchema = {
type: 'boolean',
nullable: false, optional: true,
},
instance: {
type: 'object',
nullable: false, optional: true,
properties: {
name: {
type: 'string',
nullable: true, optional: false,
},
softwareName: {
type: 'string',
nullable: true, optional: false,
},
softwareVersion: {
type: 'string',
nullable: true, optional: false,
},
iconUrl: {
type: 'string',
nullable: true, optional: false,
},
faviconUrl: {
type: 'string',
nullable: true, optional: false,
},
themeColor: {
type: 'string',
nullable: true, optional: false,
},
},
},
emojis: {
type: 'object',
nullable: false, optional: false,
},
onlineStatus: {
type: 'string',
format: 'url',
nullable: true, optional: false,
nullable: false, optional: false,
enum: ['unknown', 'online', 'active', 'offline'],
},
badgeRoles: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
iconUrl: {
type: 'string',
nullable: true, optional: false,
},
displayOrder: {
type: 'number',
nullable: false, optional: false,
},
},
},
},
},
} as const;
@@ -105,21 +162,18 @@ export const packedUserDetailedNotMeOnlySchema = {
format: 'uri',
nullable: true, optional: false,
},
movedToUri: {
movedTo: {
type: 'string',
format: 'uri',
nullable: true,
optional: false,
nullable: true, optional: false,
},
alsoKnownAs: {
type: 'array',
nullable: true,
optional: false,
nullable: true, optional: false,
items: {
type: 'string',
format: 'id',
nullable: false,
optional: false,
nullable: false, optional: false,
},
},
createdAt: {
@@ -249,6 +303,11 @@ export const packedUserDetailedNotMeOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
ffVisibility: {
type: 'string',
nullable: false, optional: false,
enum: ['public', 'followers', 'private'],
},
twoFactorEnabled: {
type: 'boolean',
nullable: false, optional: false,
@@ -264,6 +323,23 @@ export const packedUserDetailedNotMeOnlySchema = {
nullable: false, optional: false,
default: false,
},
roles: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
ref: 'RoleLite',
},
},
memo: {
type: 'string',
nullable: true, optional: false,
},
moderationNote: {
type: 'string',
nullable: false, optional: true,
},
//#region relations
isFollowing: {
type: 'boolean',
@@ -297,13 +373,10 @@ export const packedUserDetailedNotMeOnlySchema = {
type: 'boolean',
nullable: false, optional: true,
},
memo: {
type: 'string',
nullable: false, optional: true,
},
notify: {
type: 'string',
nullable: false, optional: true,
enum: ['normal', 'none'],
},
withReplies: {
type: 'boolean',
@@ -326,29 +399,37 @@ export const packedMeDetailedOnlySchema = {
nullable: true, optional: false,
format: 'id',
},
injectFeaturedNote: {
isModerator: {
type: 'boolean',
nullable: true, optional: false,
},
isAdmin: {
type: 'boolean',
nullable: true, optional: false,
},
injectFeaturedNote: {
type: 'boolean',
nullable: false, optional: false,
},
receiveAnnouncementEmail: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
autoSensitive: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
carefulBot: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
autoAcceptFollowed: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
noCrawle: {
type: 'boolean',
@@ -387,10 +468,23 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
unreadAnnouncements: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
ref: 'Announcement',
},
},
hasUnreadAntenna: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadChannel: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadNotification: {
type: 'boolean',
nullable: false, optional: false,
@@ -415,6 +509,18 @@ export const packedMeDetailedOnlySchema = {
},
},
},
hardMutedWords: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
},
mutedInstances: {
type: 'array',
nullable: true, optional: false,
@@ -426,15 +532,148 @@ export const packedMeDetailedOnlySchema = {
notificationRecieveConfig: {
type: 'object',
nullable: false, optional: false,
properties: {
app: notificationRecieveConfig,
quote: notificationRecieveConfig,
reply: notificationRecieveConfig,
follow: notificationRecieveConfig,
renote: notificationRecieveConfig,
mention: notificationRecieveConfig,
reaction: notificationRecieveConfig,
pollEnded: notificationRecieveConfig,
achievementEarned: notificationRecieveConfig,
receiveFollowRequest: notificationRecieveConfig,
followRequestAccepted: notificationRecieveConfig,
},
},
emailNotificationTypes: {
type: 'array',
nullable: true, optional: false,
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
achievements: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
unlockedAt: {
type: 'number',
nullable: false, optional: false,
},
},
},
},
loggedInDays: {
type: 'number',
nullable: false, optional: false,
},
policies: {
type: 'object',
nullable: false, optional: false,
properties: {
gtlAvailable: {
type: 'boolean',
nullable: false, optional: false,
},
ltlAvailable: {
type: 'boolean',
nullable: false, optional: false,
},
canPublicNote: {
type: 'boolean',
nullable: false, optional: false,
},
canInvite: {
type: 'boolean',
nullable: false, optional: false,
},
inviteLimit: {
type: 'number',
nullable: false, optional: false,
},
inviteLimitCycle: {
type: 'number',
nullable: false, optional: false,
},
inviteExpirationTime: {
type: 'number',
nullable: false, optional: false,
},
canManageCustomEmojis: {
type: 'boolean',
nullable: false, optional: false,
},
canManageAvatarDecorations: {
type: 'boolean',
nullable: false, optional: false,
},
canSearchNotes: {
type: 'boolean',
nullable: false, optional: false,
},
canUseTranslator: {
type: 'boolean',
nullable: false, optional: false,
},
canHideAds: {
type: 'boolean',
nullable: false, optional: false,
},
driveCapacityMb: {
type: 'number',
nullable: false, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: false, optional: false,
},
pinLimit: {
type: 'number',
nullable: false, optional: false,
},
antennaLimit: {
type: 'number',
nullable: false, optional: false,
},
wordMuteLimit: {
type: 'number',
nullable: false, optional: false,
},
webhookLimit: {
type: 'number',
nullable: false, optional: false,
},
clipLimit: {
type: 'number',
nullable: false, optional: false,
},
noteEachClipsLimit: {
type: 'number',
nullable: false, optional: false,
},
userListLimit: {
type: 'number',
nullable: false, optional: false,
},
userEachUserListsLimit: {
type: 'number',
nullable: false, optional: false,
},
rateLimitFactor: {
type: 'number',
nullable: false, optional: false,
},
},
},
//#region secrets
email: {
type: 'string',
@@ -450,6 +689,23 @@ export const packedMeDetailedOnlySchema = {
items: {
type: 'object',
nullable: false, optional: false,
properties: {
id: {
type: 'string',
nullable: false, optional: false,
format: 'id',
example: 'xxxxxxxxxx',
},
name: {
type: 'string',
nullable: false, optional: false,
},
lastUsed: {
type: 'string',
nullable: false, optional: false,
format: 'date-time',
},
},
},
},
//#endregion
@@ -511,5 +767,13 @@ export const packedUserSchema = {
type: 'object',
ref: 'UserDetailed',
},
{
type: 'object',
ref: 'UserDetailedNotMe',
},
{
type: 'object',
ref: 'MeDetailed',
},
],
} as const;

View File

@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as crypto from 'node:crypto';
import { IncomingMessage } from 'node:http';
import { Inject, Injectable } from '@nestjs/common';
import fastifyAccepts from '@fastify/accepts';
@@ -10,6 +11,7 @@ import httpSignature from '@peertube/http-signature';
import { Brackets, In, IsNull, LessThan, Not } from 'typeorm';
import accepts from 'accepts';
import vary from 'vary';
import secureJson from 'secure-json-parse';
import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
import * as url from '@/misc/prelude/url.js';
@@ -27,7 +29,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { IActivity } from '@/core/activitypub/type.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
import type { FindOptionsWhere } from 'typeorm';
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
@@ -108,7 +110,58 @@ export class ActivityPubServerService {
return;
}
// TODO: request.bodyのバリデーション
if (signature.params.headers.indexOf('host') === -1
|| request.headers.host !== this.config.host) {
// Host not specified or not match.
reply.code(401);
return;
}
if (signature.params.headers.indexOf('digest') === -1) {
// Digest not found.
reply.code(401);
} else {
const digest = request.headers.digest;
if (typeof digest !== 'string') {
// Huh?
reply.code(401);
return;
}
const re = /^([a-zA-Z0-9\-]+)=(.+)$/;
const match = digest.match(re);
if (match == null) {
// Invalid digest
reply.code(401);
return;
}
const algo = match[1];
const digestValue = match[2];
if (algo !== 'SHA-256') {
// Unsupported digest algorithm
reply.code(401);
return;
}
if (request.rawBody == null) {
// Bad request
reply.code(400);
return;
}
const hash = crypto.createHash('sha256').update(request.rawBody).digest('base64');
if (hash !== digestValue) {
// Invalid digest
reply.code(401);
return;
}
}
this.queueService.inbox(request.body as IActivity, signature);
reply.code(202);
@@ -317,8 +370,9 @@ export class ActivityPubServerService {
order: { id: 'DESC' },
});
const pinnedNotes = await Promise.all(pinings.map(pining =>
this.notesRepository.findOneByOrFail({ id: pining.noteId })));
const pinnedNotes = (await Promise.all(pinings.map(pining =>
this.notesRepository.findOneByOrFail({ id: pining.noteId }))))
.filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility));
const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note)));
@@ -460,9 +514,28 @@ export class ActivityPubServerService {
},
});
const almostDefaultJsonParser: FastifyBodyParser<Buffer> = function (request, rawBody, done) {
if (rawBody.length === 0) {
const err = new Error('Body cannot be empty!') as any;
err.statusCode = 400;
return done(err);
}
try {
const json = secureJson.parse(rawBody.toString('utf8'), null, {
protoAction: 'ignore',
constructorAction: 'ignore',
});
done(null, json);
} catch (err: any) {
err.statusCode = 400;
return done(err);
}
};
fastify.register(fastifyAccepts);
fastify.addContentTypeParser('application/activity+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
fastify.addContentTypeParser('application/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
fastify.addContentTypeParser('application/activity+json', { parseAs: 'buffer' }, almostDefaultJsonParser);
fastify.addContentTypeParser('application/ld+json', { parseAs: 'buffer' }, almostDefaultJsonParser);
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Access-Control-Allow-Headers', 'Accept');
@@ -474,8 +547,8 @@ export class ActivityPubServerService {
//#region Routing
// inbox (limit: 64kb)
fastify.post('/inbox', { bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
fastify.post('/users/:user/inbox', { bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
fastify.post('/inbox', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
fastify.post('/users/:user/inbox', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
// note
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {

View File

@@ -96,6 +96,11 @@ export class NodeinfoServerService {
metadata: {
nodeName: meta.name,
nodeDescription: meta.description,
nodeAdmins: [{
name: meta.maintainerName,
email: meta.maintainerEmail,
}],
// deprecated
maintainer: {
name: meta.maintainerName,
email: meta.maintainerEmail,

View File

@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import Fastify, { FastifyInstance } from 'fastify';
import fastifyStatic from '@fastify/static';
import fastifyRawBody from 'fastify-raw-body';
import { IsNull } from 'typeorm';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Config } from '@/config.js';
@@ -86,6 +87,13 @@ export class ServerService implements OnApplicationShutdown {
});
}
// Register raw-body parser for ActivityPub HTTP signature validation.
await fastify.register(fastifyRawBody, {
global: false,
encoding: null,
runFirst: true,
});
// Register non-serving static server so that the child services can use reply.sendFile.
// `root` here is just a placeholder and each call must use its own `rootPath`.
fastify.register(fastifyStatic, {
@@ -111,8 +119,8 @@ export class ServerService implements OnApplicationShutdown {
return;
}
const name = path.split('@')[0].replace('.webp', '');
const host = path.split('@')[1]?.replace('.webp', '');
const name = path.split('@')[0].replace(/\.webp$/i, '');
const host = path.split('@')[1]?.replace(/\.webp$/i, '');
const emoji = await this.emojisRepository.findOneBy({
// `@.` is the spec of ReactionService.decodeReaction

View File

@@ -10,6 +10,7 @@ import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
@@ -23,6 +24,8 @@ import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-d
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
@@ -368,6 +371,7 @@ const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_m
const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default };
const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default };
const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default };
const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default };
const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default };
@@ -381,6 +385,8 @@ const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-de
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
@@ -730,6 +736,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_abuseUserReports,
$admin_accounts_create,
$admin_accounts_delete,
$admin_accounts_findByEmail,
$admin_ad_create,
$admin_ad_delete,
$admin_ad_list,
@@ -743,6 +750,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_avatarDecorations_list,
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_files,
@@ -1086,6 +1095,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_abuseUserReports,
$admin_accounts_create,
$admin_accounts_delete,
$admin_accounts_findByEmail,
$admin_ad_create,
$admin_ad_delete,
$admin_ad_list,
@@ -1099,6 +1109,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_avatarDecorations_list,
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_files,

View File

@@ -126,7 +126,7 @@ export class SignupApiService {
code: invitationCode,
});
if (ticket == null) {
if (ticket == null || ticket.usedById != null) {
reply.code(400);
return;
}

View File

@@ -10,6 +10,7 @@ import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
@@ -23,6 +24,8 @@ import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-d
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
@@ -366,6 +369,7 @@ const eps = [
['admin/abuse-user-reports', ep___admin_abuseUserReports],
['admin/accounts/create', ep___admin_accounts_create],
['admin/accounts/delete', ep___admin_accounts_delete],
['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
['admin/ad/create', ep___admin_ad_create],
['admin/ad/delete', ep___admin_ad_delete],
['admin/ad/list', ep___admin_ad_list],
@@ -379,6 +383,8 @@ const eps = [
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
['admin/unset-user-banner', ep___admin_unsetUserBanner],
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
['admin/drive/cleanup', ep___admin_drive_cleanup],
['admin/drive/files', ep___admin_drive_files],

View File

@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
errors: {
userNotFound: {
message: 'No such user who has the email address.',
code: 'USER_NOT_FOUND',
id: 'cb865949-8af5-4062-a88c-ef55e8786d1d',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
email: { type: 'string' },
},
required: ['email'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const profile = await this.userProfilesRepository.findOne({
where: { email: ps.email },
relations: ['user'],
});
if (profile == null) {
throw new ApiError(meta.errors.userNotFound);
}
const res = await this.userEntityService.pack(profile.user!, null, {
detail: true,
});
return res;
});
}
}

View File

@@ -22,7 +22,7 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
publishing: { type: 'boolean', default: false },
publishing: { type: 'boolean', default: null, nullable: true },
},
required: [],
} as const;
@@ -37,8 +37,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
if (ps.publishing) {
if (ps.publishing === true) {
query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() });
} else if (ps.publishing === false) {
query.andWhere('ad.expiresAt <= :now', { now: new Date() }).orWhere('ad.startsAt > :now', { now: new Date() });
}
const ads = await query.limit(ps.limit).getMany();

View File

@@ -6,11 +6,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { EmojisRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import { DI } from '@/di-symbols.js';
import { DriveService } from '@/core/DriveService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { ApiError } from '../../../error.js';
@@ -26,6 +25,11 @@ export const meta = {
code: 'NO_SUCH_EMOJI',
id: 'e2785b66-dca3-4087-9cac-b93c541cc425',
},
duplicateName: {
message: 'Duplicate name.',
code: 'DUPLICATE_NAME',
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
},
},
res: {
@@ -56,15 +60,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private emojiEntityService: EmojiEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private customEmojiService: CustomEmojiService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
const emoji = await this.emojisRepository.findOneBy({ id: ps.emojiId });
if (emoji == null) {
throw new ApiError(meta.errors.noSuchEmoji);
}
@@ -75,28 +76,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Create file
driveFile = await this.driveService.uploadFromUrl({ url: emoji.originalUrl, user: null, force: true });
} catch (e) {
// TODO: need to return Drive Error
throw new ApiError();
}
const copied = await this.emojisRepository.insert({
id: this.idService.gen(),
updatedAt: new Date(),
// Duplication Check
const isDuplicate = await this.customEmojiService.checkDuplicate(emoji.name);
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
const addedEmoji = await this.customEmojiService.add({
driveFile,
name: emoji.name,
category: emoji.category,
aliases: emoji.aliases,
host: null,
aliases: [],
originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type,
license: emoji.license,
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
}, me);
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(copied.id),
});
return {
id: copied.id,
};
return this.emojiEntityService.packDetailed(addedEmoji);
});
}
}

View File

@@ -33,13 +33,7 @@ export const meta = {
items: {
type: 'object',
optional: false, nullable: false,
properties: {
code: {
type: 'string',
optional: false, nullable: false,
example: 'GR6S02ERUA5VR',
},
},
ref: 'InviteCode',
},
},
} as const;

View File

@@ -21,6 +21,7 @@ export const meta = {
items: {
type: 'object',
optional: false, nullable: false,
ref: 'InviteCode',
},
},
} as const;

View File

@@ -267,6 +267,14 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
enableVerifymailApi: {
type: 'boolean',
optional: false, nullable: false,
},
verifymailAuthKey: {
type: 'string',
optional: false, nullable: true,
},
enableChartsForRemoteUser: {
type: 'boolean',
optional: false, nullable: false,
@@ -295,6 +303,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
enableFanoutTimelineDbFallback: {
type: 'boolean',
optional: false, nullable: false,
},
perLocalUserUserTimelineCacheMax: {
type: 'number',
optional: false, nullable: false,
@@ -315,6 +327,82 @@ export const meta = {
type: 'number',
optional: false, nullable: false,
},
backgroundImageUrl: {
type: 'string',
optional: false, nullable: true,
},
deeplAuthKey: {
type: 'string',
optional: false, nullable: true,
},
deeplIsPro: {
type: 'boolean',
optional: false, nullable: false,
},
defaultDarkTheme: {
type: 'string',
optional: false, nullable: true,
},
defaultLightTheme: {
type: 'string',
optional: false, nullable: true,
},
description: {
type: 'string',
optional: false, nullable: true,
},
disableRegistration: {
type: 'boolean',
optional: false, nullable: false,
},
impressumUrl: {
type: 'string',
optional: false, nullable: true,
},
maintainerEmail: {
type: 'string',
optional: false, nullable: true,
},
maintainerName: {
type: 'string',
optional: false, nullable: true,
},
name: {
type: 'string',
optional: false, nullable: true,
},
objectStorageS3ForcePathStyle: {
type: 'boolean',
optional: false, nullable: false,
},
privacyPolicyUrl: {
type: 'string',
optional: false, nullable: true,
},
repositoryUrl: {
type: 'string',
optional: false, nullable: false,
},
summalyProxy: {
type: 'string',
optional: false, nullable: true,
},
themeColor: {
type: 'string',
optional: false, nullable: true,
},
tosUrl: {
type: 'string',
optional: false, nullable: true,
},
uri: {
type: 'string',
optional: false, nullable: false,
},
version: {
type: 'string',
optional: false, nullable: false,
},
},
},
} as const;
@@ -417,6 +505,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
deeplIsPro: instance.deeplIsPro,
enableIpLogging: instance.enableIpLogging,
enableActiveEmailValidation: instance.enableActiveEmailValidation,
enableVerifymailApi: instance.enableVerifymailApi,
verifymailAuthKey: instance.verifymailAuthKey,
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
enableServerMachineStats: instance.enableServerMachineStats,
@@ -424,6 +514,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
policies: { ...DEFAULT_POLICIES, ...instance.policies },
manifestJsonOverride: instance.manifestJsonOverride,
enableFanoutTimeline: instance.enableFanoutTimeline,
enableFanoutTimelineDbFallback: instance.enableFanoutTimelineDbFallback,
perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax,
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,

View File

@@ -13,6 +13,12 @@ export const meta = {
requireCredential: true,
requireAdmin: true,
res: {
type: 'object',
optional: false, nullable: false,
ref: 'Role',
},
} as const;
export const paramDef = {

View File

@@ -14,6 +14,16 @@ export const meta = {
requireCredential: true,
requireModerator: true,
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Role',
},
},
} as const;
export const paramDef = {

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