Merge remote-tracking branch 'mattyateaFork/develop' into develop
# Conflicts: # CHANGELOG.md # README.md # locales/index.d.ts # locales/ja-JP.yml # package.json # packages/backend/src/core/activitypub/models/ApNoteService.ts # packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts # packages/backend/src/server/api/endpoints/get-avatar-decorations.ts # packages/backend/test/unit/entities/UserEntityService.ts # packages/frontend/src/components/MkFollowButton.vue # packages/frontend/src/components/MkTimeline.vue # packages/frontend/src/pages/about.vue # packages/frontend/src/pages/emoji-edit-dialog.vue # packages/frontend/src/ui/universal.vue
This commit is contained in:
commit
71382a6f85
|
@ -252,7 +252,9 @@ id: 'aidx'
|
|||
#outgoingAddressFamily: ipv4
|
||||
|
||||
# Proxy for HTTP/HTTPS
|
||||
#proxy: http://127.0.0.1:3128
|
||||
#
|
||||
|
||||
|
||||
|
||||
proxyBypassHosts:
|
||||
- api.deepl.com
|
||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -6,7 +6,7 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: misskey/misskey
|
||||
REGISTRY_IMAGE: mattyacocacora/prsmsk-msk
|
||||
TAGS: |
|
||||
type=edge
|
||||
type=ref,event=pr
|
||||
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -16,19 +16,7 @@
|
|||
### Server
|
||||
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||
- Enhance: エンドポイント`clips/update`の必須項目を`clipId`のみに
|
||||
- Enhance: エンドポイント`admin/roles/update`の必須項目を`roleId`のみに
|
||||
- Enhance: エンドポイント`pages/update`の必須項目を`pageId`のみに
|
||||
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
||||
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
||||
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
||||
- Fix: 空文字列のリアクションはフォールバックされるように
|
||||
- Fix: リノートにリアクションできないように
|
||||
|
||||
## 2024.5.0
|
||||
|
||||
|
|
0
CHANGELOG_CHERRYPICK.md
Normal file
0
CHANGELOG_CHERRYPICK.md
Normal file
33
README.md
33
README.md
|
@ -15,35 +15,22 @@
|
|||
<a href="https://type4ny-hub.net/docs/for-admin/install/guides/">
|
||||
<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
|
||||
|
||||
<a href="./CONTRIBUTING.md">
|
||||
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a>
|
||||
|
||||
<a href="https://discord.gg/Wp8gVStHW3">
|
||||
<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a>
|
||||
|
||||
<a href="https://www.patreon.com/syuilo">
|
||||
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
|
||||
|
||||
---
|
||||
</div>
|
||||
|
||||
## Thanks
|
||||
# 当フォークについて
|
||||
|
||||
<a href="https://sentry.io/"><img src="https://github.com/misskey-dev/misskey/assets/4439005/98576556-222f-467a-94be-e98dbda1d852" height="30" alt="Sentry" /></a>
|
||||
当フォークは PrisMisskey.space で使用しているフォークになります。
|
||||
このコードを一部でも使用する場合はAbout内にクレジット表示をお願いします。
|
||||
|
||||
Thanks to [Sentry](https://sentry.io/) for providing the error tracking platform that helps us catch unexpected errors.
|
||||
# Special Thanks
|
||||
|
||||
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
|
||||
[mkkey source](https://github.com/emtkmkk/mkkey)
|
||||
|
||||
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
||||
[mkkey Server](https://mkkey.net)
|
||||
|
||||
<a href="https://about.codecov.io/for/open-source/"><img src="https://about.codecov.io/wp-content/themes/codecov/assets/brand/sentry-cobranding/logos/codecov-by-sentry-logo.svg" height="30" alt="Codecov" /></a>
|
||||
[MisskeyIO source](https://github.com/MisskeyIO/misskey)
|
||||
|
||||
Thanks to [Codecov](https://about.codecov.io/for/open-source/) for providing the code coverage platform that helps us improve our test coverage.
|
||||
[MisskeyIO Server](https://Misskey.io)
|
||||
|
||||
<a href="https://crowdin.com/"><img src="https://user-images.githubusercontent.com/20679825/230709597-1299a011-171a-4294-a91e-355a9b37c672.svg" height="30" alt="Crowdin" /></a>
|
||||
|
||||
Thanks to [Crowdin](https://crowdin.com/) for providing the localization platform that helps us translate Misskey into many languages.
|
||||
|
||||
<a href="https://hub.docker.com/"><img src="https://user-images.githubusercontent.com/20679825/230148221-f8e73a32-a49b-47c3-9029-9a15c3824f92.png" height="30" alt="Docker" /></a>
|
||||
|
||||
Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.
|
||||
[CherryPick source](https://github.com/kokonect-link/cherrypick)
|
||||
|
|
|
@ -9,6 +9,8 @@ notifications: "Notifications"
|
|||
username: "Username"
|
||||
password: "Password"
|
||||
forgotPassword: "Forgot password"
|
||||
setDefaultProfileConfirm: "Do you want to make this profile the default?"
|
||||
emojiPickerProfile: "Emoji picker profile"
|
||||
fetchingAsApObject: "Fetching from the Fediverse..."
|
||||
ok: "OK"
|
||||
gotIt: "Got it!"
|
||||
|
@ -175,6 +177,8 @@ flagAsCat: "Mark this account as a cat"
|
|||
flagAsCatDescription: "Enable this option to mark this account as a cat."
|
||||
flagShowTimelineReplies: "Show replies in timeline"
|
||||
flagShowTimelineRepliesDescription: "Shows replies of users to notes of other users in the timeline if turned on."
|
||||
showMediaTimeline: "Show Media timeline"
|
||||
showMediaTimelineInfo: "When on, the media timeline is displayed on the top bar. When turned off, it will not be displayed."
|
||||
autoAcceptFollowed: "Automatically approve follow requests from users you're following"
|
||||
addAccount: "Add account"
|
||||
reloadAccountsList: "Reload account list"
|
||||
|
@ -305,6 +309,8 @@ location: "Location"
|
|||
theme: "Themes"
|
||||
themeForLightMode: "Theme to use in Light Mode"
|
||||
themeForDarkMode: "Theme to use in Dark Mode"
|
||||
gamingMode: "Gaming Mode"
|
||||
gamingModeInfo: "It makes a nice gradation of buttons and other decorations. There is no intense blinking, etc."
|
||||
light: "Light"
|
||||
dark: "Dark"
|
||||
lightThemes: "Light themes"
|
||||
|
@ -1072,6 +1078,9 @@ videos: "Videos"
|
|||
audio: "Audio"
|
||||
audioFiles: "Audio"
|
||||
dataSaver: "Data Saver"
|
||||
cellularWithDataSaver: "Turn on Data Saver in Mobile Data Communications"
|
||||
UltimatedataSaver: "Ultimate Data Saver"
|
||||
cellularWithUltimateDataSaver: "Turn on Ultimate Data Saver in Mobile Data Communications"
|
||||
accountMigration: "Account Migration"
|
||||
accountMoved: "This user has moved to a new account:"
|
||||
accountMovedShort: "This account has been migrated."
|
||||
|
@ -1811,6 +1820,7 @@ _aboutType4ny:
|
|||
contributors: "Main contributors"
|
||||
allContributors: "All contributors"
|
||||
source: "Source code"
|
||||
forksource: "Source code for this fork"
|
||||
original: "Original"
|
||||
thisIsModifiedVersion: "{name} uses a modified version of the original Misskey."
|
||||
translation: "Translate Misskey"
|
||||
|
@ -1852,6 +1862,7 @@ _wordMute:
|
|||
muteWords: "Muted words"
|
||||
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
|
||||
muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
|
||||
hideMutedNotes: "Hide notes containing muted words"
|
||||
_instanceMute:
|
||||
instanceMuteDescription: "This will mute any notes/renotes from the listed instances, including those of users replying to a user from a muted instance."
|
||||
instanceMuteDescription2: "Separate with newlines"
|
||||
|
@ -1966,6 +1977,17 @@ _time:
|
|||
minute: "Minute(s)"
|
||||
hour: "Hour(s)"
|
||||
day: "Day(s)"
|
||||
_timelineTutorial:
|
||||
title: "How to use Misskey"
|
||||
step1_1: "This is the \"timeline\". All \"notes\" submitted on {name} will be chronologically displayed here."
|
||||
step1_2: "There are a few different timelines. For example, the \"Home timeline\" will contain notes of users you follow, and the \"Local timeline\" will contain notes from all users of {name}."
|
||||
step1_3: 'Besides these two, "Social Timeline" is like Home TL + Local TL, and "Media Timeline" is a stream of notes posted with some file at {name}.'
|
||||
step2_1: "Let's try posting a note next. You can do so by pressing the button with a pencil icon."
|
||||
step2_2: "How about writing a self-introduction, or just \"Hello {name}!\" if you don't feel like it?"
|
||||
step3_1: "Finished posting your first note?"
|
||||
step3_2: "Your first note should now be displayed on your timeline."
|
||||
step4_1: "You can also attach \"Reactions\" to notes."
|
||||
step4_2: "To attach a reaction, press the \"+\" mark on a note and choose an emoji you'd like to react with."
|
||||
_2fa:
|
||||
alreadyRegistered: "You have already registered a 2-factor authentication device."
|
||||
registerTOTP: "Register authenticator app"
|
||||
|
@ -2238,6 +2260,7 @@ _instanceCharts:
|
|||
_timelines:
|
||||
home: "Home"
|
||||
local: "Local"
|
||||
media: "Media"
|
||||
social: "Social"
|
||||
global: "Global"
|
||||
_play:
|
||||
|
|
462
locales/index.d.ts
vendored
462
locales/index.d.ts
vendored
|
@ -60,6 +60,46 @@ export interface Locale extends ILocale {
|
|||
* OK
|
||||
*/
|
||||
"ok": string;
|
||||
/**
|
||||
* ノートの投稿フォームを開き直した際に、下書きを復元しないようにします。
|
||||
*/
|
||||
"disableNoteDraftingDescription": string;
|
||||
/**
|
||||
* このプロファイルをデフォルトにしますか?
|
||||
*/
|
||||
"setDefaultProfileConfirm": string;
|
||||
/**
|
||||
* 絵文字ピッカーのプロファイル
|
||||
*/
|
||||
"emojiPickerProfile": string;
|
||||
/**
|
||||
* 通知のインジケーターの数字を表示する
|
||||
*/
|
||||
"notificationIndicator": string;
|
||||
/**
|
||||
* アイコンとバナーを反転させる
|
||||
*/
|
||||
"hanntenn": string;
|
||||
/**
|
||||
* ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。
|
||||
*/
|
||||
"hanntennInfo": string;
|
||||
/**
|
||||
* ルビ
|
||||
*/
|
||||
"ruby": string;
|
||||
/**
|
||||
* ノートの下書きの復元を無効化
|
||||
*/
|
||||
"disableNoteDrafting": string;
|
||||
/**
|
||||
* 隠れ家
|
||||
*/
|
||||
"kakuregaFeature": string;
|
||||
/**
|
||||
* ピン留めされたチャンネル
|
||||
*/
|
||||
"pinnedChannel": string;
|
||||
/**
|
||||
* わかった
|
||||
*/
|
||||
|
@ -68,6 +108,10 @@ export interface Locale extends ILocale {
|
|||
* キャンセル
|
||||
*/
|
||||
"cancel": string;
|
||||
/**
|
||||
* 自分の作成したリスト
|
||||
*/
|
||||
"myLists": string;
|
||||
/**
|
||||
* やめておく
|
||||
*/
|
||||
|
@ -76,6 +120,30 @@ export interface Locale extends ILocale {
|
|||
* ユーザー名を入力
|
||||
*/
|
||||
"enterUsername": string;
|
||||
/**
|
||||
* グローバルタイムラインを表示する
|
||||
*/
|
||||
"showGlobalTimeline": string;
|
||||
/**
|
||||
* ホームタイムラインを表示する
|
||||
*/
|
||||
"showHomeTimeline": string;
|
||||
/**
|
||||
* ローカルタイムラインを表示する
|
||||
*/
|
||||
"showLocalTimeline": string;
|
||||
/**
|
||||
* トップバーのカスタムをする
|
||||
*/
|
||||
"topbarCustom": string;
|
||||
/**
|
||||
* ソーシャルタイムラインを表示する
|
||||
*/
|
||||
"showSocialTimeline": string;
|
||||
/**
|
||||
* 上のバーにTLの名前を省略して表示する
|
||||
*/
|
||||
"topBarNameShown": string;
|
||||
/**
|
||||
* {user}がリノート
|
||||
*/
|
||||
|
@ -100,6 +168,14 @@ export interface Locale extends ILocale {
|
|||
* 通知の設定
|
||||
*/
|
||||
"notificationSettings": string;
|
||||
/**
|
||||
* このサーバーの公開のリスト
|
||||
*/
|
||||
"localListList": string;
|
||||
/**
|
||||
* お気に入りのリスト
|
||||
*/
|
||||
"favoriteLists": string;
|
||||
/**
|
||||
* 基本設定
|
||||
*/
|
||||
|
@ -320,6 +396,22 @@ export interface Locale extends ILocale {
|
|||
* ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。
|
||||
*/
|
||||
"driveFileDeleteConfirm": ParameterizedString<"name">;
|
||||
/**
|
||||
* {name}つのファイルを削除しますか?このファイルを使用した一部のコンテンツも削除されます。
|
||||
*/
|
||||
"driveFilesDeleteConfirm": ParameterizedString<"name">;
|
||||
/**
|
||||
* {name}つのファイルをセンシティブにしますか?
|
||||
*/
|
||||
"driveFilesSensitiveonConfirm": ParameterizedString<"name">;
|
||||
/**
|
||||
* {name}つのファイルのセンシティブを解除しますか?
|
||||
*/
|
||||
"driveFilesSensitiveoffConfirm": ParameterizedString<"name">;
|
||||
/**
|
||||
* フォルダ「{name}」を削除しますか?このフォルダの中に存在するファイルを使用した一部のコンテンツも削除されます。
|
||||
*/
|
||||
"driveFolderDeleteConfirm": ParameterizedString<"name">;
|
||||
/**
|
||||
* {name}のフォローを解除しますか?
|
||||
*/
|
||||
|
@ -356,6 +448,10 @@ export interface Locale extends ILocale {
|
|||
* フォロワー
|
||||
*/
|
||||
"followers": string;
|
||||
/**
|
||||
* プリズム
|
||||
*/
|
||||
"points": string;
|
||||
/**
|
||||
* フォローされています
|
||||
*/
|
||||
|
@ -704,14 +800,30 @@ export interface Locale extends ILocale {
|
|||
* にゃああああああああああああああ!!!!!!!!!!!!
|
||||
*/
|
||||
"flagAsCat": string;
|
||||
/**
|
||||
* ウホウホウホホウホウホウホウホホホ!!!!!!!!!!!
|
||||
*/
|
||||
"flagAsGorilla": string;
|
||||
/**
|
||||
* にゃにゃにゃ??
|
||||
*/
|
||||
"flagAsCatDescription": string;
|
||||
/**
|
||||
* ウホウホウホ??
|
||||
*/
|
||||
"flagAsGorillaDescription": string;
|
||||
/**
|
||||
* タイムラインにノートへの返信を表示する
|
||||
*/
|
||||
"flagShowTimelineReplies": string;
|
||||
/**
|
||||
* メディアタイムラインを表示する
|
||||
*/
|
||||
"showMediaTimeline": string;
|
||||
/**
|
||||
* オンにするとメディアタイムラインを上のバーに表示します。 オフにすると表示しなくなります。
|
||||
*/
|
||||
"showMediaTimelineInfo": string;
|
||||
/**
|
||||
* オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。
|
||||
*/
|
||||
|
@ -1084,6 +1196,10 @@ export interface Locale extends ILocale {
|
|||
* 削除しました
|
||||
*/
|
||||
"removed": string;
|
||||
/**
|
||||
* 「{x}」のリクエストを承認しますか?
|
||||
*/
|
||||
"requestApprovalAreYouSure": ParameterizedString<"x">;
|
||||
/**
|
||||
* 「{x}」を削除しますか?
|
||||
*/
|
||||
|
@ -1092,6 +1208,10 @@ export interface Locale extends ILocale {
|
|||
* 「{x}」を削除しますか?
|
||||
*/
|
||||
"deleteAreYouSure": ParameterizedString<"x">;
|
||||
/**
|
||||
* 「{x}」をドラフト解除しますか?
|
||||
*/
|
||||
"undraftAreYouSure": ParameterizedString<"x">;
|
||||
/**
|
||||
* リセットしますか?
|
||||
*/
|
||||
|
@ -1116,6 +1236,10 @@ export interface Locale extends ILocale {
|
|||
* オリジナル画像を保持
|
||||
*/
|
||||
"keepOriginalUploading": string;
|
||||
/**
|
||||
* ホーム投稿で通知する
|
||||
*/
|
||||
"isNotifyIsHome": string;
|
||||
/**
|
||||
* 画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。
|
||||
*/
|
||||
|
@ -1236,6 +1360,14 @@ export interface Locale extends ILocale {
|
|||
* ダークモードで使うテーマ
|
||||
*/
|
||||
"themeForDarkMode": string;
|
||||
/**
|
||||
* ゲーミングモード
|
||||
*/
|
||||
"gamingMode": string;
|
||||
/**
|
||||
* ボタンなどの装飾をいい感じのグラデーションにします。 激しい点滅などはございません。
|
||||
*/
|
||||
"gamingModeInfo": string;
|
||||
/**
|
||||
* ライト
|
||||
*/
|
||||
|
@ -1248,6 +1380,10 @@ export interface Locale extends ILocale {
|
|||
* 明るいテーマ
|
||||
*/
|
||||
"lightThemes": string;
|
||||
/**
|
||||
* アイコンなどが正常に表示されない場合にここをクリックしてください。
|
||||
*/
|
||||
"remoteUserInfoUpdate": string;
|
||||
/**
|
||||
* 暗いテーマ
|
||||
*/
|
||||
|
@ -1304,6 +1440,10 @@ export interface Locale extends ILocale {
|
|||
* フォルダーを削除
|
||||
*/
|
||||
"deleteFolder": string;
|
||||
/**
|
||||
* フォルダー
|
||||
*/
|
||||
"Folder": string;
|
||||
/**
|
||||
* フォルダー
|
||||
*/
|
||||
|
@ -2120,6 +2260,10 @@ export interface Locale extends ILocale {
|
|||
* アカウント設定
|
||||
*/
|
||||
"accountSettings": string;
|
||||
/**
|
||||
* タイムラインのヘッダー
|
||||
*/
|
||||
"timelineHeader": string;
|
||||
/**
|
||||
* プロモーション
|
||||
*/
|
||||
|
@ -2228,6 +2372,14 @@ export interface Locale extends ILocale {
|
|||
* タイムライン上部に投稿フォームを表示する(チャンネル)
|
||||
*/
|
||||
"showFixedPostFormInChannel": string;
|
||||
/**
|
||||
* ユーザーのページで最新のノートを表示する
|
||||
*/
|
||||
"FeaturedOrNote": string;
|
||||
/**
|
||||
* ユーザーのページに行ったときにハイライトか最新のノートを表示するかを選択することができます。 オフでハイライト オンで最新のノート です
|
||||
*/
|
||||
"FeaturedOrNoteInfo": string;
|
||||
/**
|
||||
* フォローする際、デフォルトで返信をTLに含むようにする
|
||||
*/
|
||||
|
@ -2476,6 +2628,10 @@ export interface Locale extends ILocale {
|
|||
* アンケート
|
||||
*/
|
||||
"poll": string;
|
||||
/**
|
||||
* 予約投稿
|
||||
*/
|
||||
"schedulePost": string;
|
||||
/**
|
||||
* 内容を隠す
|
||||
*/
|
||||
|
@ -2568,6 +2724,10 @@ export interface Locale extends ILocale {
|
|||
* アクセストークンの発行
|
||||
*/
|
||||
"generateAccessToken": string;
|
||||
/**
|
||||
* アクセストークン
|
||||
*/
|
||||
"accessToken": string;
|
||||
/**
|
||||
* 権限
|
||||
*/
|
||||
|
@ -2704,6 +2864,10 @@ export interface Locale extends ILocale {
|
|||
* ログ
|
||||
*/
|
||||
"logs": string;
|
||||
/**
|
||||
* mfm 装飾
|
||||
*/
|
||||
"mfm": string;
|
||||
/**
|
||||
* 遅延
|
||||
*/
|
||||
|
@ -2756,6 +2920,14 @@ export interface Locale extends ILocale {
|
|||
* スペースで区切って複数設定できます。
|
||||
*/
|
||||
"setMultipleBySeparatingWithSpace": string;
|
||||
/**
|
||||
* 名前には英数字と_が利用できます。
|
||||
*/
|
||||
"emojiNameValidation": string;
|
||||
/**
|
||||
* センシティブ
|
||||
*/
|
||||
"isSensitive": string;
|
||||
/**
|
||||
* ファイルIDまたはURL
|
||||
*/
|
||||
|
@ -2816,6 +2988,14 @@ export interface Locale extends ILocale {
|
|||
* 送信
|
||||
*/
|
||||
"send": string;
|
||||
/**
|
||||
* ファイル付きのみ
|
||||
*/
|
||||
"fileAttachedOnly": string;
|
||||
/**
|
||||
* 通報されたノート
|
||||
*/
|
||||
"reportedNote": string;
|
||||
/**
|
||||
* 対応済みにする
|
||||
*/
|
||||
|
@ -2944,6 +3124,14 @@ export interface Locale extends ILocale {
|
|||
* アンケートに投票した数
|
||||
*/
|
||||
"pollVotesCount": string;
|
||||
/**
|
||||
* タイムラインの絞り込みを保存する
|
||||
*/
|
||||
"onlyAndWithSave": string;
|
||||
/**
|
||||
* ファイルのみ や リプライのみ などが保存されるようになります
|
||||
*/
|
||||
"onlyAndWithSaveInfo": string;
|
||||
/**
|
||||
* アンケートに投票された数
|
||||
*/
|
||||
|
@ -3432,6 +3620,18 @@ export interface Locale extends ILocale {
|
|||
* 低
|
||||
*/
|
||||
"low": string;
|
||||
/**
|
||||
* 一覧
|
||||
*/
|
||||
"list": string;
|
||||
/**
|
||||
* ゲーミングの光るスピードの調整
|
||||
*/
|
||||
"GamingSpeedChange": string;
|
||||
/**
|
||||
* 左にすれば早くなる、右にすれば遅くなる。それだけ。
|
||||
*/
|
||||
"GamingSpeedChangeInfo": string;
|
||||
/**
|
||||
* メールアドレスの設定がされていません。
|
||||
*/
|
||||
|
@ -3440,6 +3640,18 @@ export interface Locale extends ILocale {
|
|||
* 比率
|
||||
*/
|
||||
"ratio": string;
|
||||
/**
|
||||
* ノートの公開範囲を色付けする
|
||||
*/
|
||||
"showVisibilityColor": string;
|
||||
/**
|
||||
* 新しい絵文字
|
||||
*/
|
||||
"newEmojis": string;
|
||||
/**
|
||||
* 申請されている絵文字
|
||||
*/
|
||||
"draftEmojis": string;
|
||||
/**
|
||||
* 本文をプレビュー
|
||||
*/
|
||||
|
@ -3568,6 +3780,10 @@ export interface Locale extends ILocale {
|
|||
* アカウント登録にメールアドレスを必須にする
|
||||
*/
|
||||
"emailRequiredForSignup": string;
|
||||
/**
|
||||
* GDPRモードを有効にする
|
||||
*/
|
||||
"enableGDPRMode": string;
|
||||
/**
|
||||
* 未読
|
||||
*/
|
||||
|
@ -4052,6 +4268,10 @@ export interface Locale extends ILocale {
|
|||
* カスタム絵文字の管理
|
||||
*/
|
||||
"manageCustomEmojis": string;
|
||||
/**
|
||||
* カスタム絵文字のリクエスト
|
||||
*/
|
||||
"requestCustomEmojis": string;
|
||||
/**
|
||||
* アバターデコレーションの管理
|
||||
*/
|
||||
|
@ -4248,6 +4468,26 @@ export interface Locale extends ILocale {
|
|||
* ライセンス
|
||||
*/
|
||||
"license": string;
|
||||
/**
|
||||
* 申請中
|
||||
*/
|
||||
"requestPending": string;
|
||||
/**
|
||||
* 承認
|
||||
*/
|
||||
"approval": string;
|
||||
/**
|
||||
* リクエストされている絵文字
|
||||
*/
|
||||
"requestingEmojis": string;
|
||||
/**
|
||||
* ドラフト
|
||||
*/
|
||||
"draft": string;
|
||||
/**
|
||||
* ドラフト解除
|
||||
*/
|
||||
"undrafted": string;
|
||||
/**
|
||||
* お気に入り解除しますか?
|
||||
*/
|
||||
|
@ -4316,6 +4556,18 @@ export interface Locale extends ILocale {
|
|||
* データセーバー
|
||||
*/
|
||||
"dataSaver": string;
|
||||
/**
|
||||
* モバイルデータ通信でデータセーバーをオンにする
|
||||
*/
|
||||
"cellularWithDataSaver": string;
|
||||
/**
|
||||
* 究極のデータセーバー
|
||||
*/
|
||||
"UltimateDataSaver": string;
|
||||
/**
|
||||
* モバイルデータ通信で究極のデータセーバーをオンにする
|
||||
*/
|
||||
"cellularWithUltimateDataSaver": string;
|
||||
/**
|
||||
* アカウントの移行
|
||||
*/
|
||||
|
@ -4680,6 +4932,10 @@ export interface Locale extends ILocale {
|
|||
* リノートを表示
|
||||
*/
|
||||
"showRenotes": string;
|
||||
/**
|
||||
* CWを非表示
|
||||
*/
|
||||
"showCw": string;
|
||||
/**
|
||||
* 編集済み
|
||||
*/
|
||||
|
@ -4696,10 +4952,6 @@ export interface Locale extends ILocale {
|
|||
* フォロー中またはフォロワー
|
||||
*/
|
||||
"followingOrFollower": string;
|
||||
/**
|
||||
* ファイル付きのみ
|
||||
*/
|
||||
"fileAttachedOnly": string;
|
||||
/**
|
||||
* TLに他の人への返信を含める
|
||||
*/
|
||||
|
@ -4984,6 +5236,74 @@ export interface Locale extends ILocale {
|
|||
* お問い合わせ
|
||||
*/
|
||||
"inquiry": string;
|
||||
/**
|
||||
* ノートの自己消滅
|
||||
*/
|
||||
"scheduledNoteDelete": string;
|
||||
/**
|
||||
* このノートは{time}に消滅します
|
||||
*/
|
||||
"noteDeletationAt": ParameterizedString<"time">;
|
||||
/**
|
||||
* 1年以上先の日時を指定することはできません
|
||||
*/
|
||||
"cannotScheduleLaterThanOneYear": string;
|
||||
/**
|
||||
* アクティビティを非公開にする
|
||||
*/
|
||||
"hideActivity": string;
|
||||
/**
|
||||
* 自分のプロフィールのアクティビティ (概要/アクティビティタブ) を他人が見れないようにします。このオプションを有効にしても、自分であればプロフィールのアクティビティタブから引き続き閲覧できます。
|
||||
*/
|
||||
"hideActivityDescription": string;
|
||||
/**
|
||||
* このお知らせはチャンネルのタイムライン上部に表示されます。最初の1行がタイトルとして表示され、2行目以降はお知らせをタップすることで表示されるようになります。
|
||||
*/
|
||||
"channelAnnouncementDescription": string;
|
||||
/**
|
||||
* 投稿フォーム
|
||||
*/
|
||||
"postForm": string;
|
||||
/**
|
||||
* 投稿フォームの下部に表示される項目の並び替えが出来ます。項目をクリックすると削除できます。
|
||||
*/
|
||||
"postFormBottomSettingsDescription": string;
|
||||
/**
|
||||
* 投稿フォームをリセット
|
||||
*/
|
||||
"clearPost": string;
|
||||
/**
|
||||
* 絵文字ピッカーに追加
|
||||
*/
|
||||
"addToEmojiPicker": string;
|
||||
/**
|
||||
* リアクション数の非表示
|
||||
*/
|
||||
"hideReactionCount": string;
|
||||
/**
|
||||
* 誰がリアクションをしたのかを非表示にする
|
||||
*/
|
||||
"hideReactionUsers": string;
|
||||
/**
|
||||
* リアクションをホバーした際のユーザー一覧と、ノート詳細ページのリアクションタブにあるリアクションをしたユーザー一覧を非表示にします
|
||||
*/
|
||||
"hideReactionUsersDescription": string;
|
||||
/**
|
||||
* 下書き
|
||||
*/
|
||||
"drafts": string;
|
||||
/**
|
||||
* 下書きの保存に関する動作
|
||||
*/
|
||||
"draftSavingBehavior": string;
|
||||
/**
|
||||
* 下書きとして保存
|
||||
*/
|
||||
"saveAsDraft": string;
|
||||
/**
|
||||
* 下書きを適用すると現在入力されている内容はリセットされます。よろしいですか?
|
||||
*/
|
||||
"draftOverwriteConfirm": string;
|
||||
"_delivery": {
|
||||
/**
|
||||
* 配信状態
|
||||
|
@ -6550,6 +6870,10 @@ export interface Locale extends ILocale {
|
|||
* グローバルタイムラインの閲覧
|
||||
*/
|
||||
"gtlAvailable": string;
|
||||
/**
|
||||
* 絵文字ピッカーのプロファイルの上限数(最大5)
|
||||
*/
|
||||
"emojiPickerProfileLimit": string;
|
||||
/**
|
||||
* ローカルタイムラインの閲覧
|
||||
*/
|
||||
|
@ -6558,6 +6882,14 @@ export interface Locale extends ILocale {
|
|||
* パブリック投稿の許可
|
||||
*/
|
||||
"canPublicNote": string;
|
||||
/**
|
||||
* ノートの編集
|
||||
*/
|
||||
"canEditNote": string;
|
||||
/**
|
||||
* 予約投稿の許可
|
||||
*/
|
||||
"canScheduleNote": string;
|
||||
/**
|
||||
* ノート内の最大メンション数
|
||||
*/
|
||||
|
@ -6582,6 +6914,10 @@ export interface Locale extends ILocale {
|
|||
* カスタム絵文字の管理
|
||||
*/
|
||||
"canManageCustomEmojis": string;
|
||||
/**
|
||||
* カスタム絵文字のリクエスト
|
||||
*/
|
||||
"canRequestCustomEmojis": string;
|
||||
/**
|
||||
* アバターデコレーションの管理
|
||||
*/
|
||||
|
@ -6650,6 +6986,14 @@ export interface Locale extends ILocale {
|
|||
* アイコンデコレーションの最大取付個数
|
||||
*/
|
||||
"avatarDecorationLimit": string;
|
||||
/**
|
||||
* ピン留めリストの最大数
|
||||
*/
|
||||
"listPinnedLimit": string;
|
||||
/**
|
||||
* 他鯖のローカルTL除けるやつ(最大値5)
|
||||
*/
|
||||
"localTimelineAnyLimit": string;
|
||||
};
|
||||
"_condition": {
|
||||
/**
|
||||
|
@ -7203,6 +7547,10 @@ export interface Locale extends ILocale {
|
|||
* キーワードをスラッシュで囲むと正規表現になります。
|
||||
*/
|
||||
"muteWordsDescription2": string;
|
||||
/**
|
||||
* ミュートされた単語を含むノートを非表示にする
|
||||
*/
|
||||
"hideMutedNotes": string;
|
||||
};
|
||||
"_instanceMute": {
|
||||
/**
|
||||
|
@ -7644,6 +7992,48 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"day": string;
|
||||
};
|
||||
"_timelineTutorial": {
|
||||
/**
|
||||
* Misskeyの使い方
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
* この画面は「タイムライン」です。{name}に投稿された「ノート」が時系列で表示されます。
|
||||
*/
|
||||
"step1_1": ParameterizedString<"name">;
|
||||
/**
|
||||
* タイムラインにはいくつか種類があり、例えば「ホームタイムライン」にはあなたがフォローしている人のノートが流れ、「ローカルタイムライン」には{name}全体のノートが流れます。
|
||||
*/
|
||||
"step1_2": ParameterizedString<"name">;
|
||||
/**
|
||||
* この2つ以外にも、「ソーシャルタイムライン」は ホームTL + ローカルTL のようなもので、 「メディアタイムライン」 には{name}で何かしらのファイル付きで投稿されたノートが流れます。
|
||||
*/
|
||||
"step1_3": ParameterizedString<"name">;
|
||||
/**
|
||||
* 試しに、何かノートを投稿してみましょう。画面上にある鉛筆マークのボタンを押すとフォームが開きます。
|
||||
*/
|
||||
"step2_1": string;
|
||||
/**
|
||||
* 初めてのノートの内容は、あなたの自己紹介や「{name}始めました」などがおすすめです。
|
||||
*/
|
||||
"step2_2": ParameterizedString<"name">;
|
||||
/**
|
||||
* 投稿できましたか?
|
||||
*/
|
||||
"step3_1": string;
|
||||
/**
|
||||
* あなたのノートがタイムラインに表示されていれば成功です。
|
||||
*/
|
||||
"step3_2": string;
|
||||
/**
|
||||
* ノートには、「リアクション」を付けることができます。
|
||||
*/
|
||||
"step4_1": string;
|
||||
/**
|
||||
* リアクションを付けるには、ノートの「+」マークをクリックして、好きな絵文字を選択します。
|
||||
*/
|
||||
"step4_2": string;
|
||||
};
|
||||
"_2fa": {
|
||||
/**
|
||||
* 既に設定は完了しています。
|
||||
|
@ -8203,6 +8593,14 @@ export interface Locale extends ILocale {
|
|||
* 通知
|
||||
*/
|
||||
"notifications": string;
|
||||
/**
|
||||
* ゲーミングモード
|
||||
*/
|
||||
"gamingMode": string;
|
||||
/**
|
||||
* 反転モード
|
||||
*/
|
||||
"gyakubariMode": string;
|
||||
/**
|
||||
* タイムライン
|
||||
*/
|
||||
|
@ -8697,6 +9095,10 @@ export interface Locale extends ILocale {
|
|||
* ローカル
|
||||
*/
|
||||
"local": string;
|
||||
/**
|
||||
* メディア
|
||||
*/
|
||||
"media": string;
|
||||
/**
|
||||
* ソーシャル
|
||||
*/
|
||||
|
@ -9037,6 +9439,10 @@ export interface Locale extends ILocale {
|
|||
* 実績を獲得
|
||||
*/
|
||||
"achievementEarned": string;
|
||||
/**
|
||||
* ログインボーナス
|
||||
*/
|
||||
"loginbonus": string;
|
||||
/**
|
||||
* 通知テスト
|
||||
*/
|
||||
|
@ -9126,6 +9532,10 @@ export interface Locale extends ILocale {
|
|||
* 実績の獲得
|
||||
*/
|
||||
"achievementEarned": string;
|
||||
/**
|
||||
* ログインボーナス
|
||||
*/
|
||||
"loginbonus": string;
|
||||
/**
|
||||
* 連携アプリからの通知
|
||||
*/
|
||||
|
@ -9758,6 +10168,40 @@ export interface Locale extends ILocale {
|
|||
};
|
||||
};
|
||||
};
|
||||
"_schedulePost": {
|
||||
/**
|
||||
* 予約投稿一覧
|
||||
*/
|
||||
"list": string;
|
||||
/**
|
||||
* 日付
|
||||
*/
|
||||
"postDate": string;
|
||||
/**
|
||||
* 時刻
|
||||
*/
|
||||
"postTime": string;
|
||||
/**
|
||||
* 端末に設定されているタイムゾーンの時刻で投稿されます。
|
||||
*/
|
||||
"localTime": string;
|
||||
/**
|
||||
* 予約設定
|
||||
*/
|
||||
"addSchedule": string;
|
||||
/**
|
||||
* {date}に投稿予約しました。
|
||||
*/
|
||||
"willBePostedAtX": ParameterizedString<"date">;
|
||||
/**
|
||||
* 予約投稿を削除しますか?
|
||||
*/
|
||||
"deleteAreYouSure": string;
|
||||
/**
|
||||
* 予約投稿を削除して編集しますか?
|
||||
*/
|
||||
"deleteAndEditConfirm": string;
|
||||
};
|
||||
"_dataSaver": {
|
||||
"_media": {
|
||||
/**
|
||||
|
@ -10070,6 +10514,16 @@ export interface Locale extends ILocale {
|
|||
* その他の貢献者
|
||||
*/
|
||||
"etcContributor": string;
|
||||
"_draftSavingBehavior": {
|
||||
/**
|
||||
* 自動的に保存する
|
||||
*/
|
||||
"auto": string;
|
||||
/**
|
||||
* 都度確認する
|
||||
*/
|
||||
"manual": string;
|
||||
};
|
||||
}
|
||||
declare const locales: {
|
||||
[lang: string]: Locale;
|
||||
|
|
|
@ -11,16 +11,36 @@ password: "パスワード"
|
|||
forgotPassword: "パスワードを忘れた"
|
||||
fetchingAsApObject: "連合に照会中"
|
||||
ok: "OK"
|
||||
|
||||
disableNoteDraftingDescription: "ノートの投稿フォームを開き直した際に、下書きを復元しないようにします。"
|
||||
setDefaultProfileConfirm: "このプロファイルをデフォルトにしますか?"
|
||||
emojiPickerProfile: "絵文字ピッカーのプロファイル"
|
||||
notificationIndicator: "通知のインジケーターの数字を表示する"
|
||||
hanntenn: "アイコンとバナーを反転させる"
|
||||
hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。"
|
||||
ruby: "ルビ"
|
||||
disableNoteDrafting: "ノートの下書きの復元を無効化"
|
||||
kakuregaFeature: "隠れ家"
|
||||
pinnedChannel: "ピン留めされたチャンネル"
|
||||
gotIt: "わかった"
|
||||
cancel: "キャンセル"
|
||||
myLists: "自分の作成したリスト"
|
||||
noThankYou: "やめておく"
|
||||
enterUsername: "ユーザー名を入力"
|
||||
showGlobalTimeline: "グローバルタイムラインを表示する"
|
||||
showHomeTimeline: "ホームタイムラインを表示する"
|
||||
showLocalTimeline: "ローカルタイムラインを表示する"
|
||||
topbarCustom: "トップバーのカスタムをする"
|
||||
showSocialTimeline: "ソーシャルタイムラインを表示する"
|
||||
topBarNameShown: "上のバーにTLの名前を省略して表示する"
|
||||
renotedBy: "{user}がリノート"
|
||||
noNotes: "ノートはありません"
|
||||
noNotifications: "通知はありません"
|
||||
instance: "サーバー"
|
||||
settings: "設定"
|
||||
notificationSettings: "通知の設定"
|
||||
localListList: "このサーバーの公開のリスト"
|
||||
favoriteLists: "お気に入りのリスト"
|
||||
basicSettings: "基本設定"
|
||||
otherSettings: "その他の設定"
|
||||
openInWindow: "ウィンドウで開く"
|
||||
|
@ -76,6 +96,10 @@ export: "エクスポート"
|
|||
files: "ファイル"
|
||||
download: "ダウンロード"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
||||
driveFilesDeleteConfirm: "{name}つのファイルを削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
||||
driveFilesSensitiveonConfirm: "{name}つのファイルをセンシティブにしますか?"
|
||||
driveFilesSensitiveoffConfirm: "{name}つのファイルのセンシティブを解除しますか?"
|
||||
driveFolderDeleteConfirm: "フォルダ「{name}」を削除しますか?このフォルダの中に存在するファイルを使用した一部のコンテンツも削除されます。"
|
||||
unfollowConfirm: "{name}のフォローを解除しますか?"
|
||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
||||
|
@ -85,6 +109,7 @@ note: "ノート"
|
|||
notes: "ノート"
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
points: "プリズム"
|
||||
followsYou: "フォローされています"
|
||||
createList: "リスト作成"
|
||||
manageLists: "リストの管理"
|
||||
|
@ -172,8 +197,12 @@ cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リ
|
|||
flagAsBot: "Botとして設定"
|
||||
flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Type4nyのシステム上での扱いがBotに合ったものになります。"
|
||||
flagAsCat: "にゃああああああああああああああ!!!!!!!!!!!!"
|
||||
flagAsGorilla: "ウホウホウホホウホウホウホウホホホ!!!!!!!!!!!"
|
||||
flagAsCatDescription: "にゃにゃにゃ??"
|
||||
flagAsGorillaDescription: "ウホウホウホ??"
|
||||
flagShowTimelineReplies: "タイムラインにノートへの返信を表示する"
|
||||
showMediaTimeline: "メディアタイムラインを表示する"
|
||||
showMediaTimelineInfo: "オンにするとメディアタイムラインを上のバーに表示します。 オフにすると表示しなくなります。"
|
||||
flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。"
|
||||
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
|
||||
addAccount: "アカウントを追加"
|
||||
|
@ -267,14 +296,17 @@ announcements: "お知らせ"
|
|||
imageUrl: "画像URL"
|
||||
remove: "削除"
|
||||
removed: "削除しました"
|
||||
requestApprovalAreYouSure: "「{x}」のリクエストを承認しますか?"
|
||||
removeAreYouSure: "「{x}」を削除しますか?"
|
||||
deleteAreYouSure: "「{x}」を削除しますか?"
|
||||
undraftAreYouSure: "「{x}」をドラフト解除しますか?"
|
||||
resetAreYouSure: "リセットしますか?"
|
||||
areYouSure: "よろしいですか?"
|
||||
saved: "保存しました"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
keepOriginalUploading: "オリジナル画像を保持"
|
||||
isNotifyIsHome: "ホーム投稿で通知する"
|
||||
keepOriginalUploadingDescription: "画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。"
|
||||
fromDrive: "ドライブから"
|
||||
fromUrl: "URLから"
|
||||
|
@ -305,9 +337,12 @@ location: "場所"
|
|||
theme: "テーマ"
|
||||
themeForLightMode: "ライトモードで使うテーマ"
|
||||
themeForDarkMode: "ダークモードで使うテーマ"
|
||||
gamingMode: 'ゲーミングモード'
|
||||
gamingModeInfo: "ボタンなどの装飾をいい感じのグラデーションにします。 激しい点滅などはございません。"
|
||||
light: "ライト"
|
||||
dark: "ダーク"
|
||||
lightThemes: "明るいテーマ"
|
||||
remoteUserInfoUpdate: "アイコンなどが正常に表示されない場合にここをクリックしてください。"
|
||||
darkThemes: "暗いテーマ"
|
||||
syncDeviceDarkMode: "デバイスのダークモードと同期する"
|
||||
drive: "ドライブ"
|
||||
|
@ -322,6 +357,7 @@ folderName: "フォルダー名"
|
|||
createFolder: "フォルダーを作成"
|
||||
renameFolder: "フォルダー名を変更"
|
||||
deleteFolder: "フォルダーを削除"
|
||||
Folder: "フォルダー"
|
||||
folder: "フォルダー"
|
||||
addFile: "ファイルを追加"
|
||||
emptyDrive: "ドライブは空です"
|
||||
|
@ -526,6 +562,7 @@ dayOverDayChanges: "前日比"
|
|||
appearance: "アピアランス"
|
||||
clientSettings: "クライアント設定"
|
||||
accountSettings: "アカウント設定"
|
||||
timelineHeader: "タイムラインのヘッダー"
|
||||
promotion: "プロモーション"
|
||||
promote: "プロモート"
|
||||
numberOfDays: "日数"
|
||||
|
@ -553,6 +590,8 @@ serverLogs: "サーバーログ"
|
|||
deleteAll: "全て削除"
|
||||
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
|
||||
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
|
||||
FeaturedOrNote: "ユーザーのページで最新のノートを表示する"
|
||||
FeaturedOrNoteInfo: "ユーザーのページに行ったときにハイライトか最新のノートを表示するかを選択することができます。 オフでハイライト オンで最新のノート です"
|
||||
withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする"
|
||||
newNoteRecived: "新しいノートがあります"
|
||||
sounds: "サウンド"
|
||||
|
@ -615,6 +654,7 @@ invisibleNote: "非公開の投稿"
|
|||
enableInfiniteScroll: "自動でもっと見る"
|
||||
visibility: "公開範囲"
|
||||
poll: "アンケート"
|
||||
schedulePost: "予約投稿"
|
||||
useCw: "内容を隠す"
|
||||
enablePlayer: "プレイヤーを開く"
|
||||
disablePlayer: "プレイヤーを閉じる"
|
||||
|
@ -638,6 +678,7 @@ large: "大"
|
|||
medium: "中"
|
||||
small: "小"
|
||||
generateAccessToken: "アクセストークンの発行"
|
||||
accessToken: "アクセストークン"
|
||||
permission: "権限"
|
||||
adminPermission: "管理者権限"
|
||||
enableAll: "全て有効にする"
|
||||
|
@ -672,6 +713,7 @@ copy: "コピー"
|
|||
metrics: "メトリクス"
|
||||
overview: "概要"
|
||||
logs: "ログ"
|
||||
mfm: 'mfm 装飾'
|
||||
delayed: "遅延"
|
||||
database: "データベース"
|
||||
channel: "チャンネル"
|
||||
|
@ -685,6 +727,8 @@ regenerateLoginToken: "ログイントークンを再生成"
|
|||
regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
|
||||
theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を検索する時のキーワードになります。"
|
||||
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
|
||||
emojiNameValidation: "名前には英数字と_が利用できます。"
|
||||
isSensitive: "センシティブ"
|
||||
fileIdOrUrl: "ファイルIDまたはURL"
|
||||
behavior: "動作"
|
||||
sample: "サンプル"
|
||||
|
@ -700,6 +744,8 @@ reporterOrigin: "通報元"
|
|||
forwardReport: "リモートサーバーに通報を転送する"
|
||||
forwardReportIsAnonymous: "リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。"
|
||||
send: "送信"
|
||||
fileAttachedOnly: "ファイル付きのみ"
|
||||
reportedNote: "通報されたノート"
|
||||
abuseMarkAsResolved: "対応済みにする"
|
||||
openInNewTab: "新しいタブで開く"
|
||||
openInSideView: "サイドビューで開く"
|
||||
|
@ -732,6 +778,8 @@ followersCount: "フォロワー数"
|
|||
sentReactionsCount: "リアクションした数"
|
||||
receivedReactionsCount: "リアクションされた数"
|
||||
pollVotesCount: "アンケートに投票した数"
|
||||
onlyAndWithSave: "タイムラインの絞り込みを保存する"
|
||||
onlyAndWithSaveInfo: "ファイルのみ や リプライのみ などが保存されるようになります"
|
||||
pollVotedCount: "アンケートに投票された数"
|
||||
yes: "はい"
|
||||
no: "いいえ"
|
||||
|
@ -854,8 +902,14 @@ priority: "優先度"
|
|||
high: "高"
|
||||
middle: "中"
|
||||
low: "低"
|
||||
list: "一覧"
|
||||
GamingSpeedChange: "ゲーミングの光るスピードの調整"
|
||||
GamingSpeedChangeInfo: "左にすれば早くなる、右にすれば遅くなる。それだけ。"
|
||||
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
|
||||
ratio: "比率"
|
||||
showVisibilityColor: "ノートの公開範囲を色付けする"
|
||||
newEmojis: "新しい絵文字"
|
||||
draftEmojis: "申請されている絵文字"
|
||||
previewNoteText: "本文をプレビュー"
|
||||
customCss: "カスタムCSS"
|
||||
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
|
||||
|
@ -888,6 +942,7 @@ itsOff: "オフになっています"
|
|||
on: "オン"
|
||||
off: "オフ"
|
||||
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
||||
enableGDPRMode: "GDPRモードを有効にする"
|
||||
unread: "未読"
|
||||
filter: "フィルタ"
|
||||
controlPanel: "コントロールパネル"
|
||||
|
@ -1009,6 +1064,7 @@ assign: "アサイン"
|
|||
unassign: "アサインを解除"
|
||||
color: "色"
|
||||
manageCustomEmojis: "カスタム絵文字の管理"
|
||||
requestCustomEmojis: "カスタム絵文字のリクエスト"
|
||||
manageAvatarDecorations: "アバターデコレーションの管理"
|
||||
youCannotCreateAnymore: "これ以上作成することはできません。"
|
||||
cannotPerformTemporary: "一時的に利用できません"
|
||||
|
@ -1058,6 +1114,11 @@ hiddenTags: "非表示ハッシュタグ"
|
|||
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
|
||||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||
license: "ライセンス"
|
||||
requestPending: "申請中"
|
||||
approval: "承認"
|
||||
requestingEmojis: "リクエストされている絵文字"
|
||||
draft: "ドラフト"
|
||||
undrafted: "ドラフト解除"
|
||||
unfavoriteConfirm: "お気に入り解除しますか?"
|
||||
myClips: "自分のクリップ"
|
||||
drivecleaner: "ドライブクリーナー"
|
||||
|
@ -1075,6 +1136,9 @@ videos: "動画"
|
|||
audio: "音声"
|
||||
audioFiles: "音声"
|
||||
dataSaver: "データセーバー"
|
||||
cellularWithDataSaver: "モバイルデータ通信でデータセーバーをオンにする"
|
||||
UltimateDataSaver: "究極のデータセーバー"
|
||||
cellularWithUltimateDataSaver: "モバイルデータ通信で究極のデータセーバーをオンにする"
|
||||
accountMigration: "アカウントの移行"
|
||||
accountMoved: "このユーザーは新しいアカウントに移行しました:"
|
||||
accountMovedShort: "このアカウントは移行されています"
|
||||
|
@ -1166,11 +1230,11 @@ authentication: "認証"
|
|||
authenticationRequiredToContinue: "続けるには認証を行ってください"
|
||||
dateAndTime: "日時"
|
||||
showRenotes: "リノートを表示"
|
||||
showCw: "CWを非表示"
|
||||
edited: "編集済み"
|
||||
notificationRecieveConfig: "通知の受信設定"
|
||||
mutualFollow: "相互フォロー"
|
||||
followingOrFollower: "フォロー中またはフォロワー"
|
||||
fileAttachedOnly: "ファイル付きのみ"
|
||||
showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
|
||||
hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
|
||||
showRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返信を含めるようにする"
|
||||
|
@ -1242,6 +1306,23 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
|
|||
noDescription: "説明文はありません"
|
||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||
inquiry: "お問い合わせ"
|
||||
scheduledNoteDelete: "ノートの自己消滅"
|
||||
noteDeletationAt: "このノートは{time}に消滅します"
|
||||
cannotScheduleLaterThanOneYear: "1年以上先の日時を指定することはできません"
|
||||
hideActivity: "アクティビティを非公開にする"
|
||||
hideActivityDescription: "自分のプロフィールのアクティビティ (概要/アクティビティタブ) を他人が見れないようにします。このオプションを有効にしても、自分であればプロフィールのアクティビティタブから引き続き閲覧できます。"
|
||||
channelAnnouncementDescription: "このお知らせはチャンネルのタイムライン上部に表示されます。最初の1行がタイトルとして表示され、2行目以降はお知らせをタップすることで表示されるようになります。"
|
||||
postForm: "投稿フォーム"
|
||||
postFormBottomSettingsDescription: "投稿フォームの下部に表示される項目の並び替えが出来ます。項目をクリックすると削除できます。"
|
||||
clearPost: "投稿フォームをリセット"
|
||||
addToEmojiPicker: "絵文字ピッカーに追加"
|
||||
hideReactionCount: "リアクション数の非表示"
|
||||
hideReactionUsers: "誰がリアクションをしたのかを非表示にする"
|
||||
hideReactionUsersDescription: "リアクションをホバーした際のユーザー一覧と、ノート詳細ページのリアクションタブにあるリアクションをしたユーザー一覧を非表示にします"
|
||||
drafts: "下書き"
|
||||
draftSavingBehavior: "下書きの保存に関する動作"
|
||||
saveAsDraft: "下書きとして保存"
|
||||
draftOverwriteConfirm: "下書きを適用すると現在入力されている内容はリセットされます。よろしいですか?"
|
||||
|
||||
_delivery:
|
||||
status: "配信状態"
|
||||
|
@ -1694,14 +1775,18 @@ _role:
|
|||
high: "高"
|
||||
_options:
|
||||
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||
emojiPickerProfileLimit: "絵文字ピッカーのプロファイルの上限数(最大5)"
|
||||
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||
canPublicNote: "パブリック投稿の許可"
|
||||
canEditNote: "ノートの編集"
|
||||
canScheduleNote: "予約投稿の許可"
|
||||
mentionMax: "ノート内の最大メンション数"
|
||||
canInvite: "サーバー招待コードの発行"
|
||||
inviteLimit: "招待コードの作成可能数"
|
||||
inviteLimitCycle: "招待コードの発行間隔"
|
||||
inviteExpirationTime: "招待コードの有効期限"
|
||||
canManageCustomEmojis: "カスタム絵文字の管理"
|
||||
canRequestCustomEmojis: "カスタム絵文字のリクエスト"
|
||||
canManageAvatarDecorations: "アバターデコレーションの管理"
|
||||
driveCapacity: "ドライブ容量"
|
||||
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
||||
|
@ -1719,6 +1804,8 @@ _role:
|
|||
canSearchNotes: "ノート検索の利用"
|
||||
canUseTranslator: "翻訳機能の利用"
|
||||
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
||||
listPinnedLimit: "ピン留めリストの最大数"
|
||||
localTimelineAnyLimit: "他鯖のローカルTL除けるやつ(最大値5)"
|
||||
_condition:
|
||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||
isLocal: "ローカルユーザー"
|
||||
|
@ -1842,6 +1929,7 @@ _aboutType4ny:
|
|||
source: "ソースコード"
|
||||
original: "オリジナル"
|
||||
thisIsModifiedVersion: "{name}はオリジナルのType4nyを改変したバージョンを使用しています。"
|
||||
forksource: "当フォークのソースコード"
|
||||
translation: "Type4nyを翻訳"
|
||||
donate: "Type4nyに寄付"
|
||||
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
|
||||
|
@ -1887,6 +1975,7 @@ _wordMute:
|
|||
muteWords: "ミュートするワード"
|
||||
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
|
||||
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
|
||||
hideMutedNotes: "ミュートされた単語を含むノートを非表示にする"
|
||||
|
||||
_instanceMute:
|
||||
instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。"
|
||||
|
@ -2010,6 +2099,18 @@ _time:
|
|||
hour: "時間"
|
||||
day: "日"
|
||||
|
||||
_timelineTutorial:
|
||||
title: "Misskeyの使い方"
|
||||
step1_1: "この画面は「タイムライン」です。{name}に投稿された「ノート」が時系列で表示されます。"
|
||||
step1_2: "タイムラインにはいくつか種類があり、例えば「ホームタイムライン」にはあなたがフォローしている人のノートが流れ、「ローカルタイムライン」には{name}全体のノートが流れます。"
|
||||
step1_3: "この2つ以外にも、「ソーシャルタイムライン」は ホームTL + ローカルTL のようなもので、 「メディアタイムライン」 には{name}で何かしらのファイル付きで投稿されたノートが流れます。"
|
||||
step2_1: "試しに、何かノートを投稿してみましょう。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
|
||||
step2_2: "初めてのノートの内容は、あなたの自己紹介や「{name}始めました」などがおすすめです。"
|
||||
step3_1: "投稿できましたか?"
|
||||
step3_2: "あなたのノートがタイムラインに表示されていれば成功です。"
|
||||
step4_1: "ノートには、「リアクション」を付けることができます。"
|
||||
step4_2: "リアクションを付けるには、ノートの「+」マークをクリックして、好きな絵文字を選択します。"
|
||||
|
||||
_2fa:
|
||||
alreadyRegistered: "既に設定は完了しています。"
|
||||
registerTOTP: "認証アプリの設定を開始"
|
||||
|
@ -2158,6 +2259,8 @@ _widgets:
|
|||
instanceInfo: "サーバー情報"
|
||||
memo: "付箋"
|
||||
notifications: "通知"
|
||||
gamingMode: "ゲーミングモード"
|
||||
gyakubariMode: "反転モード"
|
||||
timeline: "タイムライン"
|
||||
calendar: "カレンダー"
|
||||
trends: "トレンド"
|
||||
|
@ -2296,6 +2399,7 @@ _instanceCharts:
|
|||
_timelines:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
media: "メディア"
|
||||
social: "ソーシャル"
|
||||
global: "グローバル"
|
||||
|
||||
|
@ -2389,6 +2493,7 @@ _notification:
|
|||
roleAssigned: "ロールが付与されました"
|
||||
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
|
||||
achievementEarned: "実績を獲得"
|
||||
loginbonus: "ログインボーナス"
|
||||
testNotification: "通知テスト"
|
||||
checkNotificationBehavior: "通知の表示を確かめる"
|
||||
sendTestNotification: "テスト通知を送信する"
|
||||
|
@ -2413,6 +2518,7 @@ _notification:
|
|||
followRequestAccepted: "フォローが受理された"
|
||||
roleAssigned: "ロールが付与された"
|
||||
achievementEarned: "実績の獲得"
|
||||
loginbonus: "ログインボーナス"
|
||||
app: "連携アプリからの通知"
|
||||
|
||||
_actions:
|
||||
|
@ -2597,6 +2703,16 @@ _externalResourceInstaller:
|
|||
title: "テーマのインストールに失敗しました"
|
||||
description: "テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。"
|
||||
|
||||
_schedulePost:
|
||||
list: "予約投稿一覧"
|
||||
postDate: "日付"
|
||||
postTime: "時刻"
|
||||
localTime: "端末に設定されているタイムゾーンの時刻で投稿されます。"
|
||||
addSchedule: "予約設定"
|
||||
willBePostedAtX: "{date}に投稿予約しました。"
|
||||
deleteAreYouSure: "予約投稿を削除しますか?"
|
||||
deleteAndEditConfirm: "予約投稿を削除して編集しますか?"
|
||||
|
||||
_dataSaver:
|
||||
_media:
|
||||
title: "メディアの読み込みを無効化"
|
||||
|
@ -2686,3 +2802,7 @@ _mediaControls:
|
|||
loop: "ループ再生"
|
||||
|
||||
etcContributor: "その他の貢献者"
|
||||
|
||||
_draftSavingBehavior:
|
||||
auto: "自動的に保存する"
|
||||
manual: "都度確認する"
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 0179793ec891856d6f37a3be16ba4c22f67a81b5
|
||||
Subproject commit cf3ce27b2eb8417233072e3d6d2fb7c5356c2364
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "type4ny",
|
||||
"version": "2024.5.0",
|
||||
"version": "2024.5.0-mattyatea4",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export class AddEmojiDraftFlag1684236161625 {
|
||||
name = 'AddEmojiDraftFlag1684236161625'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "draft" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "draft"`);
|
||||
}
|
||||
}
|
16
packages/backend/migration/1696044626209-noteEditHistory.js
Normal file
16
packages/backend/migration/1696044626209-noteEditHistory.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NoteEditHistory1696044626209 {
|
||||
name = 'NoteEditHistory1696044626209'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "noteEditHistory" varchar(3000) array DEFAULT '{}'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP "noteEditHistory"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NoteUpdateAtHistory1696318192428 {
|
||||
name = 'NoteUpdateAtHistory1696318192428'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAtHistory" TIMESTAMP WITH TIME ZONE array`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP "updatedAtHistory"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class revertrevertnoteeditting1696604429200 {
|
||||
name = 'revertrevertnoteeditting1696604429200'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
|
||||
}
|
||||
}
|
12
packages/backend/migration/1696604572677-poll_vote_poll.js
Normal file
12
packages/backend/migration/1696604572677-poll_vote_poll.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export class PollVotePoll1696604572677 {
|
||||
name = 'PollVotePoll1696604572677';
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "poll_vote" ADD CONSTRAINT "FK_poll_vote_poll" FOREIGN KEY ("noteId") REFERENCES "poll"("noteId") ON DELETE CASCADE`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "poll_vote" DROP CONSTRAINT "FK_poll_vote_poll"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export class DiscordWebhookUrl1697641012204 {
|
||||
name = 'DiscordWebhookUrl1697641012204'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "DiscordWebhookUrl" character varying(1024)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "DiscordWebhookUrl"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1697642704514-EmojiBotToken.js
Normal file
11
packages/backend/migration/1697642704514-EmojiBotToken.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class EmojiBotToken1697642704514 {
|
||||
name = 'EmojiBotToken1697642704514'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "EmojiBotToken" character varying(1024)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "EmojiBotToken"`);
|
||||
}
|
||||
}
|
15
packages/backend/migration/1697645425687-BaseUrl.js
Normal file
15
packages/backend/migration/1697645425687-BaseUrl.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export class BaseUrl1697645425687 {
|
||||
name = 'BaseUrl1697645425687'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "ApiBase" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
|
||||
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" SET NOT NULL`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "flash" ALTER COLUMN "visibility" DROP NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "ApiBase"`);
|
||||
}
|
||||
}
|
13
packages/backend/migration/1698131657286-EmojiRequest.js
Normal file
13
packages/backend/migration/1698131657286-EmojiRequest.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class EmojiRequest1698131657286 {
|
||||
name = 'EmojiRequest1698131657286'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "emoji_request" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "name" character varying(128) NOT NULL, "category" character varying(128), "originalUrl" character varying(512) NOT NULL, "publicUrl" character varying(512) NOT NULL DEFAULT '', "type" character varying(64), "aliases" character varying(128) array NOT NULL DEFAULT '{}', "license" character varying(1024), "fileId" character varying(1024) NOT NULL, "localOnly" boolean NOT NULL DEFAULT false, "isSensitive" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_3c74521e048dc744f0c7eb65f4a" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ea1d771e867e9843300f09d02c" ON "emoji_request" ("name") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_ea1d771e867e9843300f09d02c"`);
|
||||
await queryRunner.query(`DROP TABLE "emoji_request"`);
|
||||
}
|
||||
}
|
13
packages/backend/migration/1698907074200-gorillamode.js
Normal file
13
packages/backend/migration/1698907074200-gorillamode.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class Gorillamode1698907074200 {
|
||||
name = 'Gorillamode1698907074200'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "isGorilla" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isGorilla" IS 'Whether the User is a gorilla.'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isGorilla" IS 'Whether the User is a gorilla.'`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isGorilla"`);
|
||||
}
|
||||
}
|
12
packages/backend/migration/1699437894737-schedulenote.js
Normal file
12
packages/backend/migration/1699437894737-schedulenote.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export class Schedulenote1699437894737 {
|
||||
name = 'Schedulenote1699437894737'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e798958c40009bf0cdef4f28b5" ON "note_schedule" ("userId") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP TABLE "note_schedule"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1699949373507-schedulenote2.js
Normal file
11
packages/backend/migration/1699949373507-schedulenote2.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class Schedulenote21699949373507 {
|
||||
name = 'Schedulenote21699949373507'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note_schedule" RENAME COLUMN "expiresAt" TO "scheduledAt"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note_schedule" RENAME COLUMN "scheduledAt" TO "expiresAt"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1702149469508-abusenoteselect.js
Normal file
11
packages/backend/migration/1702149469508-abusenoteselect.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class Abusenoteselect1702149469508 {
|
||||
name = 'Abusenoteselect1702149469508'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "notes" jsonb NOT NULL DEFAULT '[]'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "notes"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1703294653915-requestemoji.js
Normal file
11
packages/backend/migration/1703294653915-requestemoji.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class Requestemoji1703294653915 {
|
||||
name = 'Requestemoji1703294653915'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "requestEmojiAllOk" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "requestEmojiAllOk"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1703704097603-GDPRMode.js
Normal file
11
packages/backend/migration/1703704097603-GDPRMode.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class GDPRMode1703704097603 {
|
||||
name = 'GDPRMode1703704097603'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableGDPRMode" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {;
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableGDPRMode"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1704005554275-abusenoteIds.js
Normal file
11
packages/backend/migration/1704005554275-abusenoteIds.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class AbusenoteId1704005554275 {
|
||||
name = 'AbusenoteId1704005554275'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "noteIds" jsonb NOT NULL DEFAULT '[]'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "noteIds"`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1704206095136-avatardecoration.js
Normal file
11
packages/backend/migration/1704206095136-avatardecoration.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class Avatardecoration1704206095136 {
|
||||
name = 'Avatardecoration1704206095136'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "category" character varying(256) NOT NULL DEFAULT ''`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "category"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
export class AvatardecorationFed1704343998612 {
|
||||
name = 'AvatardecorationFed1704343998612'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" character varying(256)`);
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" ALTER COLUMN "category" SET DEFAULT ''`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" ALTER COLUMN "category" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
|
||||
}
|
||||
}
|
13
packages/backend/migration/1707888646527-proxycheckio.js
Normal file
13
packages/backend/migration/1707888646527-proxycheckio.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class Proxycheckio1707888646527 {
|
||||
name = 'Proxycheckio1707888646527'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableProxyCheckio" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyCheckioApiKey" character varying(32)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyCheckioApiKey"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableProxyCheckio"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export class Discordwebohookwordbrock1708081353629 {
|
||||
name = 'Discordwebohookwordbrock1708081353629'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "DiscordWebhookUrlWordBlock" character varying(1024)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "DiscordWebhookUrlWordBlock"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
export class Outsideprismisskey1714831133155 {
|
||||
name = 'Outsideprismisskey1714831133155'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "bannerDark" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "bannerLight" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "iconDark" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "iconLight" character varying(1024)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "iconLight"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "iconDark"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannerLight"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannerDark"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export class AvatardecorationFed1714831133156 {
|
||||
name = 'AvatardecorationFed1714831133156'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" DROP COLUMN "host"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "avatar_decoration" ADD "host" character varying(256)`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1715791271605-loginbonus1.js
Normal file
11
packages/backend/migration/1715791271605-loginbonus1.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class Loginbonus11715791271605 {
|
||||
name = 'Loginbonus11715791271605'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "getPoints" integer NOT NULL DEFAULT '0'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "getPoints" integer NOT NULL DEFAULT '0'`);
|
||||
}
|
||||
}
|
11
packages/backend/migration/1716911535226-gapikey.js
Normal file
11
packages/backend/migration/1716911535226-gapikey.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class Gapikey1716911535226 {
|
||||
name = 'Gapikey1716911535226'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsId" character varying(1024)`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsId"`);
|
||||
}
|
||||
}
|
16
packages/backend/migration/1718857094676-emojimore.js
Normal file
16
packages/backend/migration/1718857094676-emojimore.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
export class Emojimore1718857094676 {
|
||||
name = 'Emojimore1718857094676'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "IDX_ad0c221b25672daf2df320a817"`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a7751b74317122d11575bff31c" ON "note_reaction" ("userId", "noteId", "reaction") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_a7751b74317122d11575bff31c"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_ad0c221b25672daf2df320a817"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId") `);
|
||||
|
||||
}
|
||||
}
|
|
@ -154,6 +154,7 @@
|
|||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
"proxycheck-ts": "^0.0.9",
|
||||
"pug": "3.0.2",
|
||||
"punycode": "2.3.1",
|
||||
"qrcode": "1.5.3",
|
||||
|
@ -180,6 +181,7 @@
|
|||
"typescript": "5.5.2",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"web-push": "3.6.7",
|
||||
"ws": "8.17.0",
|
||||
"xev": "3.0.2"
|
||||
|
@ -225,6 +227,7 @@
|
|||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/tmp": "0.2.6",
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/w3c-xmlserializer": "^2.0.4",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||
|
|
|
@ -130,7 +130,7 @@ export class CacheService implements OnApplicationShutdown {
|
|||
case 'userChangeSuspendedState':
|
||||
case 'userChangeDeletedState':
|
||||
case 'remoteUserUpdated':
|
||||
case 'localUserUpdated': {
|
||||
{
|
||||
const user = await this.usersRepository.findOneBy({ id: body.id });
|
||||
if (user == null) {
|
||||
this.userByIdCache.delete(body.id);
|
||||
|
|
|
@ -40,6 +40,7 @@ import { MetaService } from './MetaService.js';
|
|||
import { MfmService } from './MfmService.js';
|
||||
import { ModerationLogService } from './ModerationLogService.js';
|
||||
import { NoteCreateService } from './NoteCreateService.js';
|
||||
import { NoteUpdateService } from './NoteUpdateService.js';
|
||||
import { NoteDeleteService } from './NoteDeleteService.js';
|
||||
import { NotePiningService } from './NotePiningService.js';
|
||||
import { NoteReadService } from './NoteReadService.js';
|
||||
|
@ -101,6 +102,7 @@ import { ClipEntityService } from './entities/ClipEntityService.js';
|
|||
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
|
||||
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
|
||||
import { EmojiEntityService } from './entities/EmojiEntityService.js';
|
||||
import { EmojiRequestsEntityService } from './entities/EmojiRequestsEntityService.js';
|
||||
import { FollowingEntityService } from './entities/FollowingEntityService.js';
|
||||
import { FollowRequestEntityService } from './entities/FollowRequestEntityService.js';
|
||||
import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js';
|
||||
|
@ -181,6 +183,7 @@ const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaServic
|
|||
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
||||
const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService };
|
||||
const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService };
|
||||
const $NoteUpdateService: Provider = { provide: 'NoteUpdateService', useExisting: NoteUpdateService };
|
||||
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
|
||||
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
|
||||
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
|
||||
|
@ -245,6 +248,7 @@ const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting
|
|||
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
||||
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
|
||||
const $EmojiEntityService: Provider = { provide: 'EmojiEntityService', useExisting: EmojiEntityService };
|
||||
const $EmojiRequestsEntityService: Provider = { provide: 'EmojiRequestsEntityService', useExisting: EmojiRequestsEntityService };
|
||||
const $FollowingEntityService: Provider = { provide: 'FollowingEntityService', useExisting: FollowingEntityService };
|
||||
const $FollowRequestEntityService: Provider = { provide: 'FollowRequestEntityService', useExisting: FollowRequestEntityService };
|
||||
const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService', useExisting: GalleryLikeEntityService };
|
||||
|
@ -327,6 +331,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
MfmService,
|
||||
ModerationLogService,
|
||||
NoteCreateService,
|
||||
NoteUpdateService,
|
||||
NoteDeleteService,
|
||||
NotePiningService,
|
||||
NoteReadService,
|
||||
|
@ -391,6 +396,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
EmojiEntityService,
|
||||
EmojiRequestsEntityService,
|
||||
FollowingEntityService,
|
||||
FollowRequestEntityService,
|
||||
GalleryLikeEntityService,
|
||||
|
@ -469,6 +475,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$MfmService,
|
||||
$ModerationLogService,
|
||||
$NoteCreateService,
|
||||
$NoteUpdateService,
|
||||
$NoteDeleteService,
|
||||
$NotePiningService,
|
||||
$NoteReadService,
|
||||
|
@ -533,6 +540,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
$EmojiEntityService,
|
||||
$EmojiRequestsEntityService,
|
||||
$FollowingEntityService,
|
||||
$FollowRequestEntityService,
|
||||
$GalleryLikeEntityService,
|
||||
|
@ -612,6 +620,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
MfmService,
|
||||
ModerationLogService,
|
||||
NoteCreateService,
|
||||
NoteUpdateService,
|
||||
NoteDeleteService,
|
||||
NotePiningService,
|
||||
NoteReadService,
|
||||
|
@ -675,6 +684,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
EmojiEntityService,
|
||||
EmojiRequestsEntityService,
|
||||
FollowingEntityService,
|
||||
FollowRequestEntityService,
|
||||
GalleryLikeEntityService,
|
||||
|
@ -753,6 +763,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$MfmService,
|
||||
$ModerationLogService,
|
||||
$NoteCreateService,
|
||||
$NoteUpdateService,
|
||||
$NoteDeleteService,
|
||||
$NotePiningService,
|
||||
$NoteReadService,
|
||||
|
@ -816,6 +827,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
$EmojiEntityService,
|
||||
$EmojiRequestsEntityService,
|
||||
$FollowingEntityService,
|
||||
$FollowRequestEntityService,
|
||||
$GalleryLikeEntityService,
|
||||
|
|
|
@ -12,13 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiEmoji } from '@/models/Emoji.js';
|
||||
import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
|
||||
import type { EmojisRepository, EmojiRequestsRepository, MiRole, MiUser } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { query } from '@/misc/prelude/url.js';
|
||||
import type { Serialized } from '@/types.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
|
||||
const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
|
||||
|
||||
|
@ -34,6 +34,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private idService: IdService,
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
|
@ -56,6 +59,41 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async request(data: {
|
||||
driveFile: MiDriveFile;
|
||||
name: string;
|
||||
category: string | null;
|
||||
aliases: string[];
|
||||
license: string | null;
|
||||
isSensitive: boolean;
|
||||
localOnly: boolean;
|
||||
}, me?: MiUser): Promise<MiEmojiRequest> {
|
||||
const emoji = await this.emojiRequestsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
updatedAt: new Date(),
|
||||
name: data.name,
|
||||
category: data.category,
|
||||
aliases: data.aliases,
|
||||
originalUrl: data.driveFile.url,
|
||||
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
|
||||
type: data.driveFile.webpublicType ?? data.driveFile.type,
|
||||
license: data.license,
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
fileId: data.driveFile.id,
|
||||
}).then(x => this.emojiRequestsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
if (me) {
|
||||
this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
}
|
||||
|
||||
return emoji;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async add(data: {
|
||||
driveFile: MiDriveFile;
|
||||
|
@ -159,6 +197,36 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async updateRequest(id: MiEmojiRequest['id'], data: {
|
||||
driveFile?: MiDriveFile;
|
||||
name?: string;
|
||||
category?: string | null;
|
||||
aliases?: string[];
|
||||
license?: string | null;
|
||||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
}, moderator?: MiUser): Promise<void> {
|
||||
const emoji = await this.emojiRequestsRepository.findOneByOrFail({ id: id });
|
||||
const sameNameEmoji = await this.emojiRequestsRepository.findOneBy({ name: data.name });
|
||||
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
||||
|
||||
await this.emojiRequestsRepository.update(emoji.id, {
|
||||
updatedAt: new Date(),
|
||||
name: data.name,
|
||||
category: data.category,
|
||||
aliases: data.aliases,
|
||||
license: data.license,
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
|
||||
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||
});
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
|
||||
const emojis = await this.emojisRepository.findBy({
|
||||
|
@ -267,6 +335,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteRequest(id: MiEmojiRequest['id']) {
|
||||
const emoji = await this.emojiRequestsRepository.findOneByOrFail({ id: id });
|
||||
|
||||
await this.emojiRequestsRepository.delete(emoji.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
|
||||
const emojis = await this.emojisRepository.findBy({
|
||||
|
@ -389,11 +464,21 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
return this.emojisRepository.exists({ where: { name, host: IsNull() } });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public checkRequestDuplicate(name: string): Promise<boolean> {
|
||||
return this.emojiRequestsRepository.exist({ where: { name } });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getEmojiById(id: string): Promise<MiEmoji | null> {
|
||||
return this.emojisRepository.findOneBy({ id });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getEmojiRequestById(id: string): Promise<MiEmojiRequest | null> {
|
||||
return this.emojiRequestsRepository.findOneBy({ id });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getEmojiByName(name: string): Promise<MiEmoji | null> {
|
||||
return this.emojisRepository.findOneBy({ name, host: IsNull() });
|
||||
|
|
|
@ -213,6 +213,17 @@ export class EmailService {
|
|||
reason: validated.reason ? formatReason[validated.reason] ?? null : null,
|
||||
};
|
||||
}
|
||||
if (meta.enableActiveEmailValidation) {
|
||||
const dispose = await this.httpRequestService.send('https://raw.githubusercontent.com/mattyatea/disposable-email-domains/master/disposable_email_blocklist.conf', {
|
||||
method: 'GET',
|
||||
});
|
||||
const disposableEmailDomains = (await dispose.text()).split('\n');
|
||||
const domain = emailAddress.split('@')[1];
|
||||
console.log(domain)
|
||||
if (disposableEmailDomains.includes(domain)) {
|
||||
validated = { valid: false, reason: 'disposable' };
|
||||
}
|
||||
}
|
||||
|
||||
const emailDomain: string = emailAddress.split('@')[1];
|
||||
const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
|
||||
|
|
|
@ -96,6 +96,7 @@ export class FanoutTimelineEndpointService {
|
|||
|
||||
if (ps.me) {
|
||||
const me = ps.me;
|
||||
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingRenotes,
|
||||
|
|
|
@ -119,7 +119,7 @@ export interface NoteEventTypes {
|
|||
};
|
||||
updated: {
|
||||
cw: string | null;
|
||||
text: string;
|
||||
text: string | null;
|
||||
};
|
||||
reacted: {
|
||||
reaction: string;
|
||||
|
@ -160,6 +160,8 @@ export interface AdminEventTypes {
|
|||
targetUserId: MiUser['id'],
|
||||
reporterId: MiUser['id'],
|
||||
comment: string;
|
||||
notes: any[];
|
||||
noteIds: string[];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
import { URL } from 'node:url';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as parse5 from 'parse5';
|
||||
import { Window, XMLSerializer } from 'happy-dom';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import serialize from 'w3c-xmlserializer';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { intersperse } from '@/misc/prelude/array.js';
|
||||
|
@ -243,7 +244,7 @@ export class MfmService {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { window } = new Window();
|
||||
const { window } = new JSDOM() as unknown as { window: Window };
|
||||
|
||||
const doc = window.document;
|
||||
|
||||
|
@ -461,6 +462,6 @@ export class MfmService {
|
|||
|
||||
appendChildren(nodes, body);
|
||||
|
||||
return new XMLSerializer().serializeToString(body);
|
||||
return serialize(body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,19 +15,16 @@ import { extractHashtags } from '@/misc/extract-hashtags.js';
|
|||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiApp } from '@/models/App.js';
|
||||
import { concat } from '@/misc/prelude/array.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||
import type { IPoll } from '@/models/Poll.js';
|
||||
import { MiPoll } from '@/models/Poll.js';
|
||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||
import { checkWordMute } from '@/misc/check-word-mute.js';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import { MemorySingleCache } from '@/misc/cache.js';
|
||||
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||
import type { MiNoteCreateOption as Option, MiMinimumUser as MinimumUser } from '@/types.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@ -128,14 +125,17 @@ type MinimumUser = {
|
|||
|
||||
type Option = {
|
||||
createdAt?: Date | null;
|
||||
updatedAt?: Date | null;
|
||||
name?: string | null;
|
||||
text?: string | null;
|
||||
reply?: MiNote | null;
|
||||
renote?: MiNote | null;
|
||||
files?: MiDriveFile[] | null;
|
||||
poll?: IPoll | null;
|
||||
event?: IEvent | null;
|
||||
localOnly?: boolean | null;
|
||||
reactionAcceptance?: MiNote['reactionAcceptance'];
|
||||
disableRightClick?: boolean | null;
|
||||
cw?: string | null;
|
||||
visibility?: string;
|
||||
visibleUsers?: MinimumUser[] | null;
|
||||
|
@ -227,6 +227,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
host: MiUser['host'];
|
||||
isBot: MiUser['isBot'];
|
||||
isCat: MiUser['isCat'];
|
||||
isGorilla: MiUser['isGorilla'];
|
||||
}, data: Option, silent = false): Promise<MiNote> {
|
||||
// チャンネル外にリプライしたら対象のスコープに合わせる
|
||||
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
||||
|
@ -262,13 +263,50 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
const hasProhibitedWords = await this.checkProhibitedWordsContain({
|
||||
cw: data.cw,
|
||||
text: data.text,
|
||||
pollChoices: data.poll?.choices,
|
||||
}, meta.prohibitedWords);
|
||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
|
||||
const { DiscordWebhookUrlWordBlock } = (await this.metaService.fetch());
|
||||
const regexpregexp = /^\/(.+)\/(.*)$/;
|
||||
let matchedString = '';
|
||||
for (const filter of meta.prohibitedWords) {
|
||||
// represents RegExp
|
||||
const regexp = filter.match(regexpregexp);
|
||||
// This should never happen due to input sanitisation.
|
||||
if (!regexp) {
|
||||
const words = filter.split(' ');
|
||||
const foundWord = words.find(keyword => (data.cw ?? data.text ?? '').includes(keyword));
|
||||
if (foundWord) {
|
||||
matchedString = foundWord;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const match = new RE2(regexp[1], regexp[2]).exec(data.cw ?? data.text ?? '');
|
||||
if (match) {
|
||||
matchedString = match[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProhibitedWords) {
|
||||
if (DiscordWebhookUrlWordBlock) {
|
||||
const data_disc = { 'username': 'ノートブロックお知らせ',
|
||||
'content':
|
||||
'ユーザー名 :' + user.username + '\n' +
|
||||
'url : ' + user.host + '\n' +
|
||||
'contents : ' + data.text + '\n' +
|
||||
'引っかかったワード :' + matchedString,
|
||||
'allowed_mentions': {
|
||||
'parse': [],
|
||||
},
|
||||
};
|
||||
|
||||
await fetch(DiscordWebhookUrlWordBlock, {
|
||||
'method': 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data_disc),
|
||||
});
|
||||
}
|
||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||
}
|
||||
|
||||
|
@ -364,6 +402,15 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens);
|
||||
}
|
||||
|
||||
const willCauseNotification = mentionedUsers.filter(u => u.host === null).length > 0 || data.reply?.userHost === null || data.renote?.userHost === null;
|
||||
|
||||
if (user.host !== null && willCauseNotification) {
|
||||
const userEntity = await this.usersRepository.findOneBy({ id: user.id });
|
||||
if ((userEntity?.followersCount ?? 0) === 0) {
|
||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||
}
|
||||
}
|
||||
|
||||
tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
|
||||
|
||||
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
|
||||
|
@ -962,6 +1009,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
|
||||
}
|
||||
}
|
||||
if (note.visibility === 'public' && note.userHost !== null) {
|
||||
this.fanoutTimelineService.push(`remoteLocalTimeline:${note.userHost}`, note.id, 1000, r);
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
|
|
297
packages/backend/src/core/NoteUpdateService.ts
Normal file
297
packages/backend/src/core/NoteUpdateService.ts
Normal file
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setImmediate } from 'node:timers/promises';
|
||||
import util from 'util';
|
||||
import { In, DataSource } from 'typeorm';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import * as mfm from 'mfm-js';
|
||||
import type { IMentionedRemoteUsers } from '@/models/Note.js';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
import type { NotesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
|
||||
import { RelayService } from '@/core/RelayService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import { MiDriveFile } from '@/models/_.js';
|
||||
import { MiPoll, IPoll } from '@/models/Poll.js';
|
||||
import { concat } from '@/misc/prelude/array.js';
|
||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||
|
||||
type MinimumUser = {
|
||||
id: MiUser['id'];
|
||||
host: MiUser['host'];
|
||||
username: MiUser['username'];
|
||||
uri: MiUser['uri'];
|
||||
};
|
||||
|
||||
type Option = {
|
||||
updatedAt?: Date | null;
|
||||
files?: MiDriveFile[] | null;
|
||||
name?: string | null;
|
||||
text?: string | null;
|
||||
cw?: string | null;
|
||||
apHashtags?: string[] | null;
|
||||
apEmojis?: string[] | null;
|
||||
poll?: IPoll | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class NoteUpdateService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.db)
|
||||
private db: DataSource,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private relayService: RelayService,
|
||||
private apDeliverManagerService: ApDeliverManagerService,
|
||||
private apRendererService: ApRendererService,
|
||||
private searchService: SearchService,
|
||||
private activeUsersChart: ActiveUsersChart,
|
||||
) { }
|
||||
|
||||
@bindThis
|
||||
public async update(user: {
|
||||
id: MiUser['id'];
|
||||
username: MiUser['username'];
|
||||
host: MiUser['host'];
|
||||
isBot: MiUser['isBot'];
|
||||
}, data: Option, note: MiNote, silent = false): Promise<MiNote | null> {
|
||||
if (data.updatedAt == null) data.updatedAt = new Date();
|
||||
|
||||
if (data.text) {
|
||||
if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
|
||||
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
||||
}
|
||||
data.text = data.text.trim();
|
||||
} else {
|
||||
data.text = null;
|
||||
}
|
||||
|
||||
let tags = data.apHashtags;
|
||||
let emojis = data.apEmojis;
|
||||
|
||||
// Parse MFM if needed
|
||||
if (!tags || !emojis) {
|
||||
const tokens = data.text ? mfm.parse(data.text)! : [];
|
||||
const cwTokens = data.cw ? mfm.parse(data.cw)! : [];
|
||||
const choiceTokens = data.poll && data.poll.choices
|
||||
? concat(data.poll.choices.map(choice => mfm.parse(choice)!))
|
||||
: [];
|
||||
|
||||
const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens);
|
||||
|
||||
tags = data.apHashtags ?? extractHashtags(combinedTokens);
|
||||
|
||||
emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens);
|
||||
}
|
||||
|
||||
tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32);
|
||||
|
||||
const updatedNote = await this.updateNote(user, note, data, tags, emojis);
|
||||
|
||||
if (updatedNote) {
|
||||
setImmediate('post updated', { signal: this.#shutdownController.signal }).then(
|
||||
() => this.postNoteUpdated(updatedNote, user, silent),
|
||||
() => { /* aborted, ignore this */ },
|
||||
);
|
||||
}
|
||||
|
||||
return updatedNote;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async updateNote(user: {
|
||||
id: MiUser['id']; host: MiUser['host'];
|
||||
}, note: MiNote, data: Option, tags: string[], emojis: string[]): Promise<MiNote | null> {
|
||||
const updatedAtHistory = note.updatedAtHistory ? note.updatedAtHistory : [];
|
||||
|
||||
const values = new MiNote({
|
||||
updatedAt: data.updatedAt!,
|
||||
fileIds: data.files ? data.files.map(file => file.id) : [],
|
||||
text: data.text,
|
||||
hasPoll: data.poll != null,
|
||||
cw: data.cw ?? null,
|
||||
tags: tags.map(tag => normalizeForSearch(tag)),
|
||||
emojis,
|
||||
attachedFileTypes: data.files ? data.files.map(file => file.type) : [],
|
||||
updatedAtHistory: [...updatedAtHistory, new Date()],
|
||||
noteEditHistory: [...note.noteEditHistory, (note.cw ? note.cw + '\n' : '') + note.text!],
|
||||
});
|
||||
|
||||
// 投稿を更新
|
||||
try {
|
||||
if (note.hasPoll && values.hasPoll) {
|
||||
// Start transaction
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
await transactionalEntityManager.update(MiNote, { id: note.id }, values);
|
||||
|
||||
if (values.hasPoll) {
|
||||
const old_poll = await transactionalEntityManager.findOneBy(MiPoll, { noteId: note.id });
|
||||
if (old_poll!.choices.toString() !== data.poll!.choices.toString() || old_poll!.multiple !== data.poll!.multiple) {
|
||||
await transactionalEntityManager.delete(MiPoll, { noteId: note.id });
|
||||
const poll = new MiPoll({
|
||||
noteId: note.id,
|
||||
choices: data.poll!.choices,
|
||||
expiresAt: data.poll!.expiresAt,
|
||||
multiple: data.poll!.multiple,
|
||||
votes: new Array(data.poll!.choices.length).fill(0),
|
||||
noteVisibility: note.visibility,
|
||||
userId: user.id,
|
||||
userHost: user.host,
|
||||
});
|
||||
await transactionalEntityManager.insert(MiPoll, poll);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (!note.hasPoll && values.hasPoll) {
|
||||
// Start transaction
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
await transactionalEntityManager.update(MiNote, { id: note.id }, values);
|
||||
|
||||
if (values.hasPoll) {
|
||||
const poll = new MiPoll({
|
||||
noteId: note.id,
|
||||
choices: data.poll!.choices,
|
||||
expiresAt: data.poll!.expiresAt,
|
||||
multiple: data.poll!.multiple,
|
||||
votes: new Array(data.poll!.choices.length).fill(0),
|
||||
noteVisibility: note.visibility,
|
||||
userId: user.id,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
await transactionalEntityManager.insert(MiPoll, poll);
|
||||
}
|
||||
});
|
||||
} else if (note.hasPoll && !values.hasPoll) {
|
||||
// Start transaction
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
await transactionalEntityManager.update(MiNote, { id: note.id }, values);
|
||||
|
||||
if (!values.hasPoll) {
|
||||
await transactionalEntityManager.delete(MiPoll, { noteId: note.id });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this.notesRepository.update({ id: note.id }, values);
|
||||
}
|
||||
|
||||
return await this.notesRepository.findOneBy({ id: note.id });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async postNoteUpdated(note: MiNote, user: {
|
||||
id: MiUser['id'];
|
||||
username: MiUser['username'];
|
||||
host: MiUser['host'];
|
||||
isBot: MiUser['isBot'];
|
||||
}, silent: boolean) {
|
||||
if (!silent) {
|
||||
if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user);
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'updated', { cw: note.cw, text: note.text });
|
||||
|
||||
//#region AP deliver
|
||||
if (this.userEntityService.isLocalUser(user)) {
|
||||
await (async () => {
|
||||
// @ts-ignore
|
||||
const noteActivity = await this.renderNoteActivity(note, user);
|
||||
|
||||
await this.deliverToConcerned(user, note, noteActivity);
|
||||
})();
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
// Register to search database
|
||||
this.reIndex(note);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async renderNoteActivity(note: MiNote, user: MiUser) {
|
||||
const content = this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user);
|
||||
|
||||
return this.apRendererService.addContext(content);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async getMentionedRemoteUsers(note: MiNote) {
|
||||
const where = [] as any[];
|
||||
|
||||
// mention / reply / dm
|
||||
const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
|
||||
if (uris.length > 0) {
|
||||
where.push(
|
||||
{ uri: In(uris) },
|
||||
);
|
||||
}
|
||||
|
||||
// renote / quote
|
||||
if (note.renoteUserId) {
|
||||
where.push({
|
||||
id: note.renoteUserId,
|
||||
});
|
||||
}
|
||||
|
||||
if (where.length === 0) return [];
|
||||
|
||||
return await this.usersRepository.find({
|
||||
where,
|
||||
}) as MiRemoteUser[];
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) {
|
||||
console.log('deliverToConcerned', util.inspect(content, { depth: null }));
|
||||
await this.apDeliverManagerService.deliverToFollowers(user, content);
|
||||
await this.relayService.deliverToRelays(user, content);
|
||||
const remoteUsers = await this.getMentionedRemoteUsers(note);
|
||||
for (const remoteUser of remoteUsers) {
|
||||
await this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private reIndex(note: MiNote) {
|
||||
if (note.text == null && note.cw == null) return;
|
||||
|
||||
this.searchService.unindexNote(note);
|
||||
this.searchService.indexNote(note);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import type { Provider } from '@nestjs/common';
|
|||
|
||||
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||
export type ScheduleNotePostQueue = Bull.Queue<ScheduleNotePostJobData>;
|
||||
export type DeliverQueue = Bull.Queue<DeliverJobData>;
|
||||
export type InboxQueue = Bull.Queue<InboxJobData>;
|
||||
export type DbQueue = Bull.Queue;
|
||||
|
@ -41,6 +42,12 @@ const $endedPollNotification: Provider = {
|
|||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $scheduleNotePost: Provider = {
|
||||
provide: 'queue:scheduleNotePost',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.SCHEDULE_NOTE_POST, baseQueueOptions(config, QUEUE.SCHEDULE_NOTE_POST)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $deliver: Provider = {
|
||||
provide: 'queue:deliver',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
|
||||
|
@ -89,6 +96,7 @@ const $systemWebhookDeliver: Provider = {
|
|||
providers: [
|
||||
$system,
|
||||
$endedPollNotification,
|
||||
$scheduleNotePost,
|
||||
$deliver,
|
||||
$inbox,
|
||||
$db,
|
||||
|
@ -100,6 +108,7 @@ const $systemWebhookDeliver: Provider = {
|
|||
exports: [
|
||||
$system,
|
||||
$endedPollNotification,
|
||||
$scheduleNotePost,
|
||||
$deliver,
|
||||
$inbox,
|
||||
$db,
|
||||
|
@ -113,6 +122,7 @@ export class QueueModule implements OnApplicationShutdown {
|
|||
constructor(
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
|
@ -129,6 +139,7 @@ export class QueueModule implements OnApplicationShutdown {
|
|||
await Promise.all([
|
||||
this.systemQueue.close(),
|
||||
this.endedPollNotificationQueue.close(),
|
||||
this.scheduleNotePostQueue.close(),
|
||||
this.deliverQueue.close(),
|
||||
this.inboxQueue.close(),
|
||||
this.dbQueue.close(),
|
||||
|
|
|
@ -14,6 +14,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
|
||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||
|
||||
import type {
|
||||
DbJobData,
|
||||
DeliverJobData,
|
||||
|
@ -32,6 +33,7 @@ import type {
|
|||
SystemQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
SystemWebhookDeliverQueue,
|
||||
ScheduleNotePostQueue
|
||||
} from './QueueModule.js';
|
||||
import type httpSignature from '@peertube/http-signature';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
@ -44,6 +46,7 @@ export class QueueService {
|
|||
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:scheduleNotePost') public ScheduleNotePostQueue: ScheduleNotePostQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
|
|
|
@ -166,29 +166,56 @@ export class ReactionService {
|
|||
userId: user.id,
|
||||
reaction,
|
||||
};
|
||||
if (user.host == null) {
|
||||
const exists = await this.noteReactionsRepository.findOneBy({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
reaction: record.reaction,
|
||||
});
|
||||
|
||||
// Create reaction
|
||||
try {
|
||||
await this.noteReactionsRepository.insert(record);
|
||||
} catch (e) {
|
||||
if (isDuplicateKeyValueError(e)) {
|
||||
const exists = await this.noteReactionsRepository.findOneByOrFail({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
});
|
||||
const count = await this.noteReactionsRepository.countBy({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (exists.reaction !== reaction) {
|
||||
// 別のリアクションがすでにされていたら置き換える
|
||||
await this.delete(user, note);
|
||||
if (count > 3) {
|
||||
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
|
||||
}
|
||||
|
||||
if (exists == null) {
|
||||
if (user.host == null) {
|
||||
await this.noteReactionsRepository.insert(record);
|
||||
} else {
|
||||
// 同じリアクションがすでにされていたらエラー
|
||||
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
// 同じリアクションがすでにされていたらエラー
|
||||
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await this.noteReactionsRepository.insert(record);
|
||||
} catch (e) {
|
||||
if (isDuplicateKeyValueError(e)) {
|
||||
const exists = await this.noteReactionsRepository.findOneByOrFail({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (exists.reaction !== reaction) {
|
||||
// 別のリアクションがすでにされていたら置き換える
|
||||
await this.delete(user, note);
|
||||
await this.noteReactionsRepository.insert(record);
|
||||
} else {
|
||||
// 同じリアクションがすでにされていたらエラー
|
||||
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create reaction
|
||||
|
||||
// Increment reactions count
|
||||
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
||||
|
@ -281,17 +308,24 @@ export class ReactionService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote) {
|
||||
public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, reaction?: string) {
|
||||
// if already unreacted
|
||||
const exist = await this.noteReactionsRepository.findOneBy({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
let exist;
|
||||
if (reaction == null) {
|
||||
exist = await this.noteReactionsRepository.findOneBy({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
});
|
||||
} else {
|
||||
exist = await this.noteReactionsRepository.findOneBy({
|
||||
noteId: note.id,
|
||||
userId: user.id,
|
||||
reaction: reaction.replace(/@./, ''),
|
||||
});
|
||||
}
|
||||
if (exist == null) {
|
||||
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
|
||||
}
|
||||
|
||||
// Delete reaction
|
||||
const result = await this.noteReactionsRepository.delete(exist.id);
|
||||
|
||||
|
|
|
@ -35,12 +35,15 @@ export type RolePolicies = {
|
|||
gtlAvailable: boolean;
|
||||
ltlAvailable: boolean;
|
||||
canPublicNote: boolean;
|
||||
canEditNote: boolean;
|
||||
canScheduleNote: boolean;
|
||||
mentionLimit: number;
|
||||
canInvite: boolean;
|
||||
inviteLimit: number;
|
||||
inviteLimitCycle: number;
|
||||
inviteExpirationTime: number;
|
||||
canManageCustomEmojis: boolean;
|
||||
canRequestCustomEmojis: boolean;
|
||||
canManageAvatarDecorations: boolean;
|
||||
canSearchNotes: boolean;
|
||||
canUseTranslator: boolean;
|
||||
|
@ -57,6 +60,9 @@ export type RolePolicies = {
|
|||
userEachUserListsLimit: number;
|
||||
rateLimitFactor: number;
|
||||
avatarDecorationLimit: number;
|
||||
emojiPickerProfileLimit: number;
|
||||
listPinnedLimit: number;
|
||||
localTimelineAnyLimit: number;
|
||||
};
|
||||
|
||||
export const DEFAULT_POLICIES: RolePolicies = {
|
||||
|
@ -64,11 +70,14 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
ltlAvailable: true,
|
||||
canPublicNote: true,
|
||||
mentionLimit: 20,
|
||||
canEditNote: true,
|
||||
canScheduleNote: true,
|
||||
canInvite: false,
|
||||
inviteLimit: 0,
|
||||
inviteLimitCycle: 60 * 24 * 7,
|
||||
inviteExpirationTime: 0,
|
||||
canManageCustomEmojis: false,
|
||||
canRequestCustomEmojis: false,
|
||||
canManageAvatarDecorations: false,
|
||||
canSearchNotes: false,
|
||||
canUseTranslator: true,
|
||||
|
@ -85,6 +94,9 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
userEachUserListsLimit: 50,
|
||||
rateLimitFactor: 1,
|
||||
avatarDecorationLimit: 1,
|
||||
emojiPickerProfileLimit: 2,
|
||||
listPinnedLimit: 2,
|
||||
localTimelineAnyLimit: 3,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
@ -364,13 +376,17 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||
canScheduleNote: calc('canScheduleNote', vs => vs.some(v => v === true)),
|
||||
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
|
||||
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
|
||||
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
||||
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
||||
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
||||
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
|
||||
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
|
||||
canRequestCustomEmojis: calc('canRequestCustomEmojis', vs => vs.some(v => v === true)),
|
||||
canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)),
|
||||
canRequestCustomEmojis: calc('canRequestCustomEmojis', vs => vs.some(v => v === true)),
|
||||
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
|
||||
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
||||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||
|
@ -386,6 +402,9 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
|
||||
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
|
||||
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
|
||||
emojiPickerProfileLimit: calc('emojiPickerProfileLimit', vs => Math.max(...vs)),
|
||||
listPinnedLimit: calc('listPinnedLimit', vs => Math.max(...vs)),
|
||||
localTimelineAnyLimit: calc('localTimelineAnyLimit', vs => Math.max(...vs)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { NotePiningService } from '@/core/NotePiningService.js';
|
|||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import { NoteUpdateService } from '@/core/NoteUpdateService.js';
|
||||
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
|
@ -73,6 +74,7 @@ export class ApInboxService {
|
|||
private notePiningService: NotePiningService,
|
||||
private userBlockingService: UserBlockingService,
|
||||
private noteCreateService: NoteCreateService,
|
||||
private noteUpdateService: NoteUpdateService,
|
||||
private noteDeleteService: NoteDeleteService,
|
||||
private appLockService: AppLockService,
|
||||
private apResolverService: ApResolverService,
|
||||
|
@ -751,11 +753,13 @@ export class ApInboxService {
|
|||
|
||||
@bindThis
|
||||
private async update(actor: MiRemoteUser, activity: IUpdate): Promise<string> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
if (actor.uri !== activity.actor) {
|
||||
return 'skip: invalid actor';
|
||||
}
|
||||
|
||||
this.logger.debug('Update');
|
||||
this.logger.debug(`Update: ${uri}`);
|
||||
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
|
||||
|
@ -767,14 +771,51 @@ export class ApInboxService {
|
|||
if (isActor(object)) {
|
||||
await this.apPersonService.updatePerson(actor.uri, resolver, object);
|
||||
return 'ok: Person updated';
|
||||
} else if (getApType(object) === 'Question') {
|
||||
} /*else if (getApType(object) === 'Question') {
|
||||
await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err));
|
||||
return 'ok: Question updated';
|
||||
}*/ else if (getApType(object) === 'Note' || getApType(object) === 'Question') {
|
||||
await this.updateNote(resolver, actor, object, false, activity);
|
||||
return 'ok: Note updated';
|
||||
} else {
|
||||
return `skip: Unknown type: ${getApType(object)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async updateNote(resolver: Resolver, actor: MiRemoteUser, note: IObject, silent = false, activity?: IUpdate): Promise<string> {
|
||||
const uri = getApId(note);
|
||||
|
||||
if (typeof note === 'object') {
|
||||
if (actor.uri !== note.attributedTo) {
|
||||
return 'skip: actor.uri !== note.attributedTo';
|
||||
}
|
||||
|
||||
if (typeof note.id === 'string') {
|
||||
if (this.utilityService.extractDbHost(actor.uri) !== this.utilityService.extractDbHost(note.id)) {
|
||||
return 'skip: host in actor.uri !== note.id';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unlock = await this.appLockService.getApLock(uri);
|
||||
|
||||
try {
|
||||
const target = await this.notesRepository.findOneBy({uri: uri});
|
||||
if (!target) return `skip: target note not located: ${uri}`;
|
||||
await this.apNoteService.updateNote(note, target, resolver, silent);
|
||||
return 'ok';
|
||||
} catch (err) {
|
||||
if (err instanceof StatusError && err.isClientError) {
|
||||
return `skip ${err.statusCode}`;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async move(actor: MiRemoteUser, activity: IMove): Promise<string> {
|
||||
// fetch the new and old accounts
|
||||
|
|
|
@ -108,6 +108,7 @@ export class ApRendererService {
|
|||
actor: this.userEntityService.genLocalUserUri(note.userId),
|
||||
type: 'Announce',
|
||||
published: this.idService.parse(note.id).date.toISOString(),
|
||||
updated: note.updatedAt?.toISOString() ?? undefined,
|
||||
to,
|
||||
cc,
|
||||
object,
|
||||
|
@ -438,6 +439,7 @@ export class ApRendererService {
|
|||
_misskey_quote: quote,
|
||||
quoteUrl: quote,
|
||||
published: this.idService.parse(note.id).date.toISOString(),
|
||||
updated: note.updatedAt?.toISOString() ?? undefined,
|
||||
to,
|
||||
cc,
|
||||
inReplyTo,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project , Type4ny-project, cherrypick contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
|
||||
import type { PollsRepository, EmojisRepository, NotesRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiRemoteUser } from '@/models/User.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
@ -24,7 +25,8 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
import { NoteUpdateService } from '@/core/NoteUpdateService.js';
|
||||
import { getApId, getApType, getOneApHrefNullable, getOneApId, isEmoji, validPost } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApMfmService } from '../ApMfmService.js';
|
||||
import { ApDbResolverService } from '../ApDbResolverService.js';
|
||||
|
@ -52,6 +54,9 @@ export class ApNoteService {
|
|||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private apMfmService: ApMfmService,
|
||||
private apResolverService: ApResolverService,
|
||||
|
@ -69,6 +74,7 @@ export class ApNoteService {
|
|||
private appLockService: AppLockService,
|
||||
private pollService: PollService,
|
||||
private noteCreateService: NoteCreateService,
|
||||
private noteUpdateService: NoteUpdateService,
|
||||
private apDbResolverService: ApDbResolverService,
|
||||
private apLoggerService: ApLoggerService,
|
||||
) {
|
||||
|
@ -295,6 +301,7 @@ export class ApNoteService {
|
|||
try {
|
||||
return await this.noteCreateService.create(actor, {
|
||||
createdAt: note.published ? new Date(note.published) : null,
|
||||
updatedAt: note.updated ? new Date(note.updated) : null,
|
||||
files,
|
||||
reply,
|
||||
renote: quote,
|
||||
|
@ -324,6 +331,85 @@ export class ApNoteService {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async updateNote(value: string | IObject, target: MiNote, resolver?: Resolver, silent = false): Promise<MiNote | null> {
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(value);
|
||||
const entryUri = getApId(value);
|
||||
|
||||
const err = this.validateNote(object, entryUri);
|
||||
if (err) {
|
||||
this.logger.error(err.message, {
|
||||
resolver: { history: resolver.getHistory() },
|
||||
value,
|
||||
object,
|
||||
});
|
||||
throw new Error('invalid note');
|
||||
}
|
||||
|
||||
const note = object as IPost;
|
||||
|
||||
// 投稿者をフェッチ
|
||||
if (note.attributedTo == null) {
|
||||
throw new Error('invalid note.attributedTo: ' + note.attributedTo);
|
||||
}
|
||||
|
||||
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser;
|
||||
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
}
|
||||
|
||||
const limit = promiseLimit<MiDriveFile>(2);
|
||||
const files = (await Promise.all(toArray(note.attachment).map(attach => (
|
||||
limit(() => this.apImageService.resolveImage(actor, {
|
||||
...attach,
|
||||
sensitive: note.sensitive, // Noteがsensitiveなら添付もsensitiveにする
|
||||
}))
|
||||
))));
|
||||
|
||||
const cw = note.summary === '' ? null : note.summary;
|
||||
|
||||
// テキストのパース
|
||||
let text: string | null = null;
|
||||
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
|
||||
text = note.source.content;
|
||||
} else if (typeof note._misskey_content !== 'undefined') {
|
||||
text = note._misskey_content;
|
||||
} else if (typeof note.content === 'string') {
|
||||
text = this.apMfmService.htmlToMfm(note.content, note.tag);
|
||||
}
|
||||
|
||||
const apHashtags = extractApHashtags(note.tag);
|
||||
|
||||
const emojis = await this.extractEmojis(note.tag ?? [], actor.host).catch(e => {
|
||||
this.logger.info(`extractEmojis: ${e}`);
|
||||
return [];
|
||||
});
|
||||
|
||||
const apEmojis = emojis.map(emoji => emoji.name);
|
||||
|
||||
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
|
||||
|
||||
try {
|
||||
return await this.noteUpdateService.update(actor, {
|
||||
updatedAt: note.updated ? new Date(note.updated) : null,
|
||||
files,
|
||||
name: note.name,
|
||||
cw,
|
||||
text,
|
||||
apHashtags,
|
||||
apEmojis,
|
||||
poll,
|
||||
}, target, silent);
|
||||
} catch (err: any) {
|
||||
this.logger.warn(`note update failed: ${err}`);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Noteを解決します。
|
||||
*
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface IObject {
|
|||
summary?: string;
|
||||
_misskey_summary?: string;
|
||||
published?: string;
|
||||
updated?: string;
|
||||
cc?: ApObject;
|
||||
to?: ApObject;
|
||||
attributedTo?: ApObject;
|
||||
|
|
|
@ -65,21 +65,21 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
|||
this.followingsRepository.createQueryBuilder('following')
|
||||
.select('COUNT(DISTINCT following.followeeHost)')
|
||||
.where('following.followeeHost IS NOT NULL')
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
this.followingsRepository.createQueryBuilder('following')
|
||||
.select('COUNT(DISTINCT following.followerHost)')
|
||||
.where('following.followerHost IS NOT NULL')
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
this.followingsRepository.createQueryBuilder('following')
|
||||
.select('COUNT(DISTINCT following.followeeHost)')
|
||||
.where('following.followeeHost IS NOT NULL')
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
|
||||
.setParameters(pubsubSubQuery.getParameters())
|
||||
|
@ -88,7 +88,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
|||
this.instancesRepository.createQueryBuilder('instance')
|
||||
.select('COUNT(instance.id)')
|
||||
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere('instance.suspensionState = \'none\'')
|
||||
.andWhere('instance.isNotResponding = false')
|
||||
.getRawOne()
|
||||
|
@ -96,7 +96,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
|||
this.instancesRepository.createQueryBuilder('instance')
|
||||
.select('COUNT(instance.id)')
|
||||
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere('instance.suspensionState = \'none\'')
|
||||
.andWhere('instance.isNotResponding = false')
|
||||
.getRawOne()
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AbuseUserReportsRepository } from '@/models/_.js';
|
||||
import type { AbuseUserReportsRepository, NotesRepository } from '@/models/_.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
|
@ -19,7 +22,11 @@ export class AbuseUserReportEntityService {
|
|||
@Inject(DI.abuseUserReportsRepository)
|
||||
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
}
|
||||
|
@ -34,11 +41,27 @@ export class AbuseUserReportEntityService {
|
|||
},
|
||||
) {
|
||||
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
|
||||
const notes = [];
|
||||
|
||||
if (report.noteIds && report.noteIds.length > 0) {
|
||||
for (const x of report.noteIds) {
|
||||
const exists = await this.notesRepository.countBy({ id: x });
|
||||
if (exists === 0) {
|
||||
notes.push('deleted');
|
||||
continue;
|
||||
}
|
||||
notes.push(await this.noteEntityService.pack(x));
|
||||
}
|
||||
} else if (report.notes.length > 0) {
|
||||
notes.push(...(report.notes));
|
||||
}
|
||||
|
||||
console.log(report.notes.length, null, notes);
|
||||
return await awaitAll({
|
||||
id: report.id,
|
||||
createdAt: this.idService.parse(report.id).date.toISOString(),
|
||||
comment: report.comment,
|
||||
notes,
|
||||
resolved: report.resolved,
|
||||
reporterId: report.reporterId,
|
||||
targetUserId: report.targetUserId,
|
||||
|
|
|
@ -132,7 +132,10 @@ export class DriveFileEntityService {
|
|||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getFromUrl(url: string): Promise<MiDriveFile | null> {
|
||||
return this.driveFilesRepository.findOneBy({ url: url });
|
||||
}
|
||||
@bindThis
|
||||
public async calcDriveUsageOf(user: MiUser['id'] | { id: MiUser['id'] }): Promise<number> {
|
||||
const id = typeof user === 'object' ? user.id : user;
|
||||
|
|
|
@ -34,6 +34,7 @@ export class EmojiEntityService {
|
|||
localOnly: emoji.localOnly ? true : undefined,
|
||||
isSensitive: emoji.isSensitive ? true : undefined,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
|
||||
draft: emoji.draft,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -62,6 +63,7 @@ export class EmojiEntityService {
|
|||
isSensitive: emoji.isSensitive,
|
||||
localOnly: emoji.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
draft: emoji.draft,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 type { EmojiRequestsRepository } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
|
||||
@Injectable()
|
||||
export class EmojiRequestsEntityService {
|
||||
constructor(
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packSimple(
|
||||
src: MiEmojiRequest['id'] | MiEmojiRequest,
|
||||
): Promise<Packed<'EmojiRequestSimple'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojiRequestsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url: emoji.publicUrl,
|
||||
isSensitive: emoji.isSensitive ? true : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packSimpleMany(
|
||||
emojis: any[],
|
||||
) {
|
||||
return Promise.all(emojis.map(x => this.packSimple(x)));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packDetailed(
|
||||
src: MiEmojiRequest['id'] | MiEmojiRequest,
|
||||
): Promise<Packed<'EmojiRequestDetailed'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojiRequestsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: emoji.id,
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
url: emoji.publicUrl,
|
||||
license: emoji.license,
|
||||
isSensitive: emoji.isSensitive,
|
||||
localOnly: emoji.localOnly,
|
||||
fileId: emoji.fileId,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packDetailedMany(
|
||||
emojis: any[],
|
||||
) {
|
||||
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,11 @@ export class MetaEntityService {
|
|||
inquiryUrl: instance.inquiryUrl,
|
||||
disableRegistration: instance.disableRegistration,
|
||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||
bannerDark: instance.bannerDark,
|
||||
bannerLight: instance.bannerLight,
|
||||
iconDark: instance.iconDark,
|
||||
iconLight: instance.iconLight,
|
||||
googleAnalyticsId: instance.googleAnalyticsId,
|
||||
enableHcaptcha: instance.enableHcaptcha,
|
||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||
enableMcaptcha: instance.enableMcaptcha,
|
||||
|
@ -85,6 +90,7 @@ export class MetaEntityService {
|
|||
bannerUrl: instance.bannerUrl,
|
||||
infoImageUrl: instance.infoImageUrl,
|
||||
serverErrorImageUrl: instance.serverErrorImageUrl,
|
||||
googleAnalyticsId: instance.googleAnalyticsId,
|
||||
notFoundImageUrl: instance.notFoundImageUrl,
|
||||
iconUrl: instance.iconUrl,
|
||||
backgroundImageUrl: instance.backgroundImageUrl,
|
||||
|
|
|
@ -208,6 +208,30 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
return undefined;
|
||||
}
|
||||
@bindThis
|
||||
public async populateMyReactions(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: {
|
||||
myReactions: Map<MiNote['id'], string | null>;
|
||||
}) {
|
||||
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
|
||||
|
||||
if (reactionsCount === 0) return undefined;
|
||||
|
||||
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
|
||||
if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const reactions = await this.noteReactionsRepository.findBy({
|
||||
userId: meId,
|
||||
noteId: note.id,
|
||||
});
|
||||
|
||||
if (reactions.length > 0) {
|
||||
return reactions.map(reaction => this.reactionService.convertLegacyReaction(reaction.reaction));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async isVisibleForMe(note: MiNote, meId: MiUser['id'] | null): Promise<boolean> {
|
||||
|
@ -324,6 +348,9 @@ export class NoteEntityService implements OnModuleInit {
|
|||
const packed: Packed<'Note'> = await awaitAll({
|
||||
id: note.id,
|
||||
createdAt: this.idService.parse(note.id).date.toISOString(),
|
||||
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
|
||||
updatedAtHistory: note.updatedAtHistory ? note.updatedAtHistory.map(x => x.toISOString()) : undefined,
|
||||
noteEditHistory: note.noteEditHistory.length ? note.noteEditHistory : undefined,
|
||||
userId: note.userId,
|
||||
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
|
||||
text: text,
|
||||
|
@ -378,6 +405,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
...(meId && Object.keys(note.reactions).length > 0 ? {
|
||||
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
||||
myReactions: this.populateMyReactions(note, meId, options?._hint_),
|
||||
} : {}),
|
||||
} : {}),
|
||||
});
|
||||
|
|
|
@ -162,6 +162,9 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
...(notification.type === 'achievementEarned' ? {
|
||||
achievement: notification.achievement,
|
||||
} : {}),
|
||||
...(notification.type === 'loginbonus' ? {
|
||||
loginbonus: notification.loginbonus,
|
||||
} : {}),
|
||||
...(notification.type === 'app' ? {
|
||||
body: notification.customBody,
|
||||
header: notification.customHeader,
|
||||
|
|
|
@ -470,9 +470,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
createdAt: this.idService.parse(announcement.id).date.toISOString(),
|
||||
...announcement,
|
||||
})) : null;
|
||||
|
||||
console.log(user.getPoints);
|
||||
const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null;
|
||||
|
||||
const packed = {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
|
@ -506,7 +505,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
iconUrl: r.iconUrl,
|
||||
displayOrder: r.displayOrder,
|
||||
}))) : undefined,
|
||||
|
||||
...(user.host == null ? { getPoints: user.getPoints } : {}),
|
||||
...(isDetailed ? {
|
||||
url: profile!.url,
|
||||
uri: user.uri,
|
||||
|
|
|
@ -66,7 +66,7 @@ export class ServerStatsService implements OnApplicationShutdown {
|
|||
if (log.length > 200) log.pop();
|
||||
};
|
||||
|
||||
tick();
|
||||
await tick();
|
||||
|
||||
this.intervalId = setInterval(tick, interval);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export const DI = {
|
|||
//#region Repositories
|
||||
usersRepository: Symbol('usersRepository'),
|
||||
notesRepository: Symbol('notesRepository'),
|
||||
scheduledNotesRepository: Symbol('scheduledNotesRepository'),
|
||||
announcementsRepository: Symbol('announcementsRepository'),
|
||||
announcementReadsRepository: Symbol('announcementReadsRepository'),
|
||||
appsRepository: Symbol('appsRepository'),
|
||||
|
@ -40,6 +41,7 @@ export const DI = {
|
|||
followRequestsRepository: Symbol('followRequestsRepository'),
|
||||
instancesRepository: Symbol('instancesRepository'),
|
||||
emojisRepository: Symbol('emojisRepository'),
|
||||
emojiRequestsRepository: Symbol('emojiRequestsRepository'),
|
||||
driveFilesRepository: Symbol('driveFilesRepository'),
|
||||
driveFoldersRepository: Symbol('driveFoldersRepository'),
|
||||
metasRepository: Symbol('metasRepository'),
|
||||
|
|
|
@ -33,7 +33,7 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
|
|||
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
|
||||
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
||||
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
|
||||
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
|
||||
import { packedEmojiDetailedSchema, packedEmojiRequestSimpleSchema, packedEmojiSimpleSchema, packedEmojiRequestDetailedSchema } 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';
|
||||
|
@ -94,7 +94,9 @@ export const refs = {
|
|||
FederationInstance: packedFederationInstanceSchema,
|
||||
GalleryPost: packedGalleryPostSchema,
|
||||
EmojiSimple: packedEmojiSimpleSchema,
|
||||
EmojiRequestSimple: packedEmojiRequestSimpleSchema,
|
||||
EmojiDetailed: packedEmojiDetailedSchema,
|
||||
EmojiRequestDetailed: packedEmojiRequestDetailedSchema,
|
||||
Flash: packedFlashSchema,
|
||||
Signin: packedSigninSchema,
|
||||
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
|
||||
|
|
|
@ -60,6 +60,16 @@ export class MiAbuseUserReport {
|
|||
})
|
||||
public comment: string;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public notes: any[];
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public noteIds: string[] | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
|
|
|
@ -31,6 +31,12 @@ export class MiAvatarDecoration {
|
|||
})
|
||||
public description: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
default: '',
|
||||
})
|
||||
public category: string;
|
||||
|
||||
// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
|
||||
@Column('varchar', {
|
||||
array: true, length: 128, default: '{}',
|
||||
|
|
|
@ -81,4 +81,10 @@ export class MiEmoji {
|
|||
array: true, length: 128, default: '{}',
|
||||
})
|
||||
public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
nullable: false,
|
||||
})
|
||||
public draft: boolean;
|
||||
}
|
||||
|
|
71
packages/backend/src/models/EmojiRequest.ts
Normal file
71
packages/backend/src/models/EmojiRequest.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
|
||||
@Entity('emoji_request')
|
||||
@Index(['name'], { unique: true })
|
||||
export class MiEmojiRequest {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
public category: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
})
|
||||
public originalUrl: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
default: '',
|
||||
})
|
||||
public publicUrl: string;
|
||||
|
||||
// publicUrlの方のtypeが入る
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true,
|
||||
})
|
||||
public type: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
array: true, length: 128, default: '{}',
|
||||
})
|
||||
public aliases: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: true,
|
||||
})
|
||||
public license: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: false,
|
||||
})
|
||||
public fileId: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public localOnly: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isSensitive: boolean;
|
||||
}
|
|
@ -139,6 +139,11 @@ export class MiMeta {
|
|||
nullable: true,
|
||||
})
|
||||
public serverErrorImageUrl: string | null;
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public googleAnalyticsId: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
|
@ -224,11 +229,33 @@ export class MiMeta {
|
|||
})
|
||||
public enableRecaptcha: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public DiscordWebhookUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public DiscordWebhookUrlWordBlock: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public EmojiBotToken: string | null;
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public recaptchaSiteKey: string | null;
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public ApiBase: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
|
@ -417,6 +444,30 @@ export class MiMeta {
|
|||
})
|
||||
public objectStorageBaseUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public bannerDark: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public bannerLight: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public iconDark: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
})
|
||||
public iconLight: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
nullable: true,
|
||||
|
@ -589,6 +640,27 @@ export class MiMeta {
|
|||
})
|
||||
public notesPerOneAd: number;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public requestEmojiAllOk: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableGDPRMode: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableProxyCheckio: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32,
|
||||
nullable: true,
|
||||
})
|
||||
public proxyCheckioApiKey: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
|
|
|
@ -14,6 +14,23 @@ import type { MiDriveFile } from './DriveFile.js';
|
|||
export class MiNote {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
@Column('timestamp with time zone', {
|
||||
default: null,
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
array: true,
|
||||
default: null,
|
||||
})
|
||||
public updatedAtHistory: Date[] | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 3000,
|
||||
array: true,
|
||||
default: '{}',
|
||||
})
|
||||
public noteEditHistory: string[];
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
|
|
|
@ -9,7 +9,8 @@ import { MiUser } from './User.js';
|
|||
import { MiNote } from './Note.js';
|
||||
|
||||
@Entity('note_reaction')
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
@Index(['userId', 'noteId', 'reaction'], { unique: true })
|
||||
@Index(['userId', 'noteId'])
|
||||
export class MiNoteReaction {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Provider } from '@nestjs/common';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import {
|
||||
MiAbuseReportNotificationRecipient,
|
||||
|
||||
MiAbuseUserReport,
|
||||
MiAccessToken,
|
||||
MiAd,
|
||||
|
@ -18,7 +18,6 @@ import {
|
|||
MiAuthSession,
|
||||
MiAvatarDecoration,
|
||||
MiBlocking,
|
||||
MiBubbleGameRecord,
|
||||
MiChannel,
|
||||
MiChannelFavorite,
|
||||
MiChannelFollowing,
|
||||
|
@ -28,10 +27,11 @@ import {
|
|||
MiDriveFile,
|
||||
MiDriveFolder,
|
||||
MiEmoji,
|
||||
MiEmojiRequest,
|
||||
MiFlash,
|
||||
MiFlashLike,
|
||||
MiFollowing,
|
||||
MiFollowRequest,
|
||||
MiFollowing,
|
||||
MiGalleryLike,
|
||||
MiGalleryPost,
|
||||
MiHashtag,
|
||||
|
@ -51,19 +51,19 @@ import {
|
|||
MiPollVote,
|
||||
MiPromoNote,
|
||||
MiPromoRead,
|
||||
MiRegistrationTicket,
|
||||
MiRegistryItem,
|
||||
MiRelay,
|
||||
MiRenoteMuting,
|
||||
MiRepository,
|
||||
miRepository,
|
||||
MiRetentionAggregation,
|
||||
MiRegistrationTicket,
|
||||
MiRegistryItem,
|
||||
MiReversiGame,
|
||||
MiSystemWebhook,
|
||||
MiRelay,
|
||||
MiRenoteMuting,
|
||||
MiRetentionAggregation,
|
||||
MiRole,
|
||||
MiRoleAssignment,
|
||||
MiSignin,
|
||||
MiSwSubscription,
|
||||
MiSystemWebhook,
|
||||
MiUsedUsername,
|
||||
MiUser,
|
||||
MiUserIp,
|
||||
|
@ -77,8 +77,10 @@ import {
|
|||
MiUserProfile,
|
||||
MiUserPublickey,
|
||||
MiUserSecurityKey,
|
||||
MiWebhook
|
||||
} from './_.js';
|
||||
MiWebhook,
|
||||
MiScheduledNote,
|
||||
MiBubbleGameRecord } from './_.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
const $usersRepository: Provider = {
|
||||
|
@ -93,6 +95,12 @@ const $notesRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $scheduledNotesRepository: Provider = {
|
||||
provide: DI.scheduledNotesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiScheduledNote),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $announcementsRepository: Provider = {
|
||||
provide: DI.announcementsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement).extend(miRepository as MiRepository<MiAnnouncement>),
|
||||
|
@ -243,6 +251,12 @@ const $emojisRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $emojiRequestsRepository: Provider = {
|
||||
provide: DI.emojiRequestsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiEmojiRequest),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $driveFilesRepository: Provider = {
|
||||
provide: DI.driveFilesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFile).extend(miRepository as MiRepository<MiDriveFile>),
|
||||
|
@ -500,6 +514,7 @@ const $reversiGamesRepository: Provider = {
|
|||
providers: [
|
||||
$usersRepository,
|
||||
$notesRepository,
|
||||
$scheduledNotesRepository,
|
||||
$announcementsRepository,
|
||||
$announcementReadsRepository,
|
||||
$appsRepository,
|
||||
|
@ -525,6 +540,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$followRequestsRepository,
|
||||
$instancesRepository,
|
||||
$emojisRepository,
|
||||
$emojiRequestsRepository,
|
||||
$driveFilesRepository,
|
||||
$driveFoldersRepository,
|
||||
$metasRepository,
|
||||
|
@ -571,6 +587,7 @@ const $reversiGamesRepository: Provider = {
|
|||
exports: [
|
||||
$usersRepository,
|
||||
$notesRepository,
|
||||
$scheduledNotesRepository,
|
||||
$announcementsRepository,
|
||||
$announcementReadsRepository,
|
||||
$appsRepository,
|
||||
|
@ -596,6 +613,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$followRequestsRepository,
|
||||
$instancesRepository,
|
||||
$emojisRepository,
|
||||
$emojiRequestsRepository,
|
||||
$driveFilesRepository,
|
||||
$driveFoldersRepository,
|
||||
$metasRepository,
|
||||
|
|
27
packages/backend/src/models/ScheduledNote.ts
Normal file
27
packages/backend/src/models/ScheduledNote.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||
import type { MiNoteCreateOption } from '@/types.js';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
@Entity('note_schedule')
|
||||
export class MiScheduledNote {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('jsonb')
|
||||
public note: MiNoteCreateOption;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 260,
|
||||
})
|
||||
public userId: MiUser['id'];
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public scheduledAt: Date;
|
||||
}
|
|
@ -25,6 +25,11 @@ export class MiUser {
|
|||
})
|
||||
public lastFetchedAt: Date | null;
|
||||
|
||||
@Column('integer', {
|
||||
default: '0',
|
||||
})
|
||||
public getPoints: number;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
|
@ -179,6 +184,12 @@ export class MiUser {
|
|||
})
|
||||
public isCat: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is a gorilla.',
|
||||
})
|
||||
public isGorilla: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is the root.',
|
||||
|
|
|
@ -29,6 +29,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
|||
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||
import { MiEmoji } from '@/models/Emoji.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
import { MiFollowing } from '@/models/Following.js';
|
||||
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
||||
|
@ -80,6 +81,7 @@ import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
|||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||
import { MiScheduledNote } from './ScheduledNote.js';
|
||||
|
||||
export interface MiRepository<T extends ObjectLiteral> {
|
||||
createTableColumnNames(this: Repository<T> & MiRepository<T>): string[];
|
||||
|
@ -144,6 +146,7 @@ export {
|
|||
MiDriveFile,
|
||||
MiDriveFolder,
|
||||
MiEmoji,
|
||||
MiEmojiRequest,
|
||||
MiFollowing,
|
||||
MiFollowRequest,
|
||||
MiGalleryLike,
|
||||
|
@ -159,6 +162,7 @@ export {
|
|||
MiNoteReaction,
|
||||
MiNoteThreadMuting,
|
||||
MiNoteUnread,
|
||||
MiScheduledNote,
|
||||
MiPage,
|
||||
MiPageLike,
|
||||
MiPasswordResetRequest,
|
||||
|
@ -215,6 +219,7 @@ export type ClipFavoritesRepository = Repository<MiClipFavorite> & MiRepository<
|
|||
export type DriveFilesRepository = Repository<MiDriveFile> & MiRepository<MiDriveFile>;
|
||||
export type DriveFoldersRepository = Repository<MiDriveFolder> & MiRepository<MiDriveFolder>;
|
||||
export type EmojisRepository = Repository<MiEmoji> & MiRepository<MiEmoji>;
|
||||
export type EmojiRequestsRepository = Repository<MiEmojiRequest>;
|
||||
export type FollowingsRepository = Repository<MiFollowing> & MiRepository<MiFollowing>;
|
||||
export type FollowRequestsRepository = Repository<MiFollowRequest> & MiRepository<MiFollowRequest>;
|
||||
export type GalleryLikesRepository = Repository<MiGalleryLike> & MiRepository<MiGalleryLike>;
|
||||
|
@ -230,6 +235,7 @@ export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<
|
|||
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
|
||||
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
|
||||
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
|
||||
export type ScheduledNotesRepository = Repository<MiScheduledNote>;
|
||||
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
|
||||
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
|
||||
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;
|
||||
|
|
|
@ -44,6 +44,40 @@ export const packedEmojiSimpleSchema = {
|
|||
format: 'id',
|
||||
},
|
||||
},
|
||||
draft: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
export const packedEmojiRequestSimpleSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -85,6 +119,10 @@ export const packedEmojiDetailedSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
draft: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -104,3 +142,51 @@ export const packedEmojiDetailedSchema = {
|
|||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedEmojiRequestDetailedSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
license: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
localOnly: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -17,6 +17,24 @@ export const packedNoteSchema = {
|
|||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
updatedAtHistory: {
|
||||
type: 'array',
|
||||
optional: true, nullable: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
noteEditHistory: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
deletedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
|
|
|
@ -155,6 +155,14 @@ export const packedUserLiteSchema = {
|
|||
onlineStatus: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
isGorilla: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
onlineStatus: {
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
nullable: true, optional: false,
|
||||
enum: ['unknown', 'online', 'active', 'offline'],
|
||||
},
|
||||
badgeRoles: {
|
||||
|
@ -180,7 +188,8 @@ export const packedUserLiteSchema = {
|
|||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
} as const
|
||||
|
||||
export const packedUserDetailedNotMeOnlySchema = {
|
||||
type: 'object',
|
||||
|
|
|
@ -28,6 +28,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
|||
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||
import { MiEmoji } from '@/models/Emoji.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
import { MiFollowing } from '@/models/Following.js';
|
||||
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
||||
|
@ -76,6 +77,7 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
|||
import { MiFlash } from '@/models/Flash.js';
|
||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||
import { MiScheduledNote } from '@/models/ScheduledNote.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
|
||||
|
@ -99,7 +101,7 @@ class MyCustomLogger implements Logger {
|
|||
|
||||
@bindThis
|
||||
public logQuery(query: string, parameters?: any[]) {
|
||||
sqlLogger.info(this.highlight(query).substring(0, 100));
|
||||
sqlLogger.info(this.highlight(query));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -153,6 +155,7 @@ export const entities = [
|
|||
MiRenoteMuting,
|
||||
MiBlocking,
|
||||
MiNote,
|
||||
MiScheduledNote,
|
||||
MiNoteFavorite,
|
||||
MiNoteReaction,
|
||||
MiNoteThreadMuting,
|
||||
|
@ -166,6 +169,7 @@ export const entities = [
|
|||
MiPoll,
|
||||
MiPollVote,
|
||||
MiEmoji,
|
||||
MiEmojiRequest,
|
||||
MiHashtag,
|
||||
MiSwSubscription,
|
||||
MiAbuseUserReport,
|
||||
|
|
|
@ -10,6 +10,7 @@ import { QueueLoggerService } from './QueueLoggerService.js';
|
|||
import { QueueProcessorService } from './QueueProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
|
||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||
|
@ -75,6 +76,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
|||
UserWebhookDeliverProcessorService,
|
||||
SystemWebhookDeliverProcessorService,
|
||||
EndedPollNotificationProcessorService,
|
||||
ScheduleNotePostProcessorService,
|
||||
DeliverProcessorService,
|
||||
InboxProcessorService,
|
||||
AggregateRetentionProcessorService,
|
||||
|
|
|
@ -13,6 +13,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
||||
|
@ -82,6 +83,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
private relationshipQueueWorker: Bull.Worker;
|
||||
private objectStorageQueueWorker: Bull.Worker;
|
||||
private endedPollNotificationQueueWorker: Bull.Worker;
|
||||
private schedulerNotePostQueueWorker: Bull.Worker;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
|
@ -91,6 +93,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
private userWebhookDeliverProcessorService: UserWebhookDeliverProcessorService,
|
||||
private systemWebhookDeliverProcessorService: SystemWebhookDeliverProcessorService,
|
||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||
private scheduleNotePostProcessorService: ScheduleNotePostProcessorService,
|
||||
private deliverProcessorService: DeliverProcessorService,
|
||||
private inboxProcessorService: InboxProcessorService,
|
||||
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
|
||||
|
@ -487,6 +490,13 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
}
|
||||
//#endregion
|
||||
|
||||
//#region schedule note post
|
||||
this.schedulerNotePostQueueWorker = new Bull.Worker(QUEUE.SCHEDULE_NOTE_POST, (job) => this.scheduleNotePostProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.SCHEDULE_NOTE_POST),
|
||||
autorun: false,
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region ended poll notification
|
||||
{
|
||||
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => {
|
||||
|
@ -515,6 +525,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.relationshipQueueWorker.run(),
|
||||
this.objectStorageQueueWorker.run(),
|
||||
this.endedPollNotificationQueueWorker.run(),
|
||||
this.schedulerNotePostQueueWorker.run(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -530,6 +541,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.relationshipQueueWorker.close(),
|
||||
this.objectStorageQueueWorker.close(),
|
||||
this.endedPollNotificationQueueWorker.close(),
|
||||
this.schedulerNotePostQueueWorker.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ export const QUEUE = {
|
|||
INBOX: 'inbox',
|
||||
SYSTEM: 'system',
|
||||
ENDED_POLL_NOTIFICATION: 'endedPollNotification',
|
||||
SCHEDULE_NOTE_POST: 'scheduleNotePost',
|
||||
DB: 'db',
|
||||
RELATIONSHIP: 'relationship',
|
||||
OBJECT_STORAGE: 'objectStorage',
|
||||
|
|
|
@ -103,6 +103,7 @@ export class ImportCustomEmojisProcessorService {
|
|||
isSensitive: emojiInfo.isSensitive,
|
||||
localOnly: emojiInfo.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||
draft: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import type { ScheduledNotesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { ScheduleNotePostJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class ScheduleNotePostProcessorService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.scheduledNotesRepository)
|
||||
private scheduledNotesRepository: ScheduledNotesRepository,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private noteCreateService: NoteCreateService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<ScheduleNotePostJobData>): Promise<void> {
|
||||
this.scheduledNotesRepository.findOneBy({ id: job.data.scheduledNoteId }).then(async (data) => {
|
||||
if (!data) {
|
||||
this.logger.warn(`Schedule note ${job.data.scheduledNoteId} not found`);
|
||||
} else {
|
||||
data.note.createdAt = new Date();
|
||||
const me = await this.usersRepository.findOneByOrFail({ id: data.userId });
|
||||
await this.noteCreateService.create(me, data.note);
|
||||
await this.scheduledNotesRepository.remove(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -6,9 +6,13 @@
|
|||
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import type { IActivity } from '@/core/activitypub/type.js';
|
||||
import { IPoll } from '@/models/Poll.js';
|
||||
import { MiScheduledNote } from '@/models/ScheduledNote.js';
|
||||
import { MiChannel } from '@/models/Channel.js';
|
||||
import { MiApp } from '@/models/App.js';
|
||||
import type httpSignature from '@peertube/http-signature';
|
||||
|
||||
export type DeliverJobData = {
|
||||
|
@ -106,6 +110,17 @@ export type EndedPollNotificationJobData = {
|
|||
noteId: MiNote['id'];
|
||||
};
|
||||
|
||||
export type ScheduleNotePostJobData = {
|
||||
scheduledNoteId: MiNote['id'];
|
||||
}
|
||||
|
||||
type MinimumUser = {
|
||||
id: MiUser['id'];
|
||||
host: MiUser['host'];
|
||||
username: MiUser['username'];
|
||||
uri: MiUser['uri'];
|
||||
};
|
||||
|
||||
export type SystemWebhookDeliverJobData = {
|
||||
type: string;
|
||||
content: unknown;
|
||||
|
|
|
@ -342,7 +342,8 @@ export class FileServerService {
|
|||
'avatar' in request.query ||
|
||||
'static' in request.query ||
|
||||
'preview' in request.query ||
|
||||
'badge' in request.query
|
||||
'badge' in request.query ||
|
||||
'datasaver' in request.query
|
||||
) {
|
||||
if (!isConvertibleImage) {
|
||||
// 画像でないなら404でお茶を濁す
|
||||
|
@ -361,7 +362,7 @@ export class FileServerService {
|
|||
} else {
|
||||
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
|
||||
.resize({
|
||||
height: 'emoji' in request.query ? 128 : 320,
|
||||
height: 'emoji' in request.query ? 64 : 128,
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.webp(webpDefault);
|
||||
|
@ -407,7 +408,28 @@ export class FileServerService {
|
|||
ext: 'png',
|
||||
type: 'image/png',
|
||||
};
|
||||
} else if (file.mime === 'image/svg+xml') {
|
||||
} else if ('datasaver' in request.query){
|
||||
if (!isAnimationConvertibleImage && !('static' in request.query)) {
|
||||
image = {
|
||||
data: fs.createReadStream(file.path),
|
||||
ext: file.ext,
|
||||
type: file.mime,
|
||||
};
|
||||
} else {
|
||||
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
|
||||
.resize({
|
||||
height: 32,
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.webp(webpDefault);
|
||||
|
||||
image = {
|
||||
data,
|
||||
ext: 'webp',
|
||||
type: 'image/webp',
|
||||
};
|
||||
}
|
||||
}else if (file.mime === 'image/svg+xml') {
|
||||
image = this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048);
|
||||
} else if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) {
|
||||
throw new StatusError('Rejected type', 403, 'Rejected type');
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import * as ep___users_lists_list_favorite from '@/server/api/endpoints/users/lists/list-favorite.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_list from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_show from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_create from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
|
||||
|
@ -36,18 +37,23 @@ import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
|||
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
||||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setlocalOnlyBulk from './endpoints/admin/emoji/set-localonly-bulk.js';
|
||||
import * as ep___admin_emoji_setisSensitiveBulk from './endpoints/admin/emoji/set-issensitive-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_addRequest from './endpoints/admin/emoji/add-request.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
|
||||
import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
|
||||
import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_listRequest from './endpoints/admin/emoji/list-request.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_emoji_updateRequest from './endpoints/admin/emoji/update-request.js';
|
||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||
|
@ -264,6 +270,7 @@ import * as ep___invite_list from './endpoints/invite/list.js';
|
|||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___emojiRequests from './endpoints/emoji-requests.js';
|
||||
import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
|
@ -278,13 +285,17 @@ import * as ep___notes_children from './endpoints/notes/children.js';
|
|||
import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
|
||||
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_update from './endpoints/notes/update.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
||||
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
||||
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
||||
import * as ep___notes_anyLocalTimeline from './endpoints/notes/any-local-timeline.js';
|
||||
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
||||
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
||||
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
||||
|
@ -349,6 +360,7 @@ import * as ep___users_following from './endpoints/users/following.js';
|
|||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
||||
import * as ep___users_user_stats from './endpoints/users/stats.js';
|
||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
|
@ -387,8 +399,9 @@ import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
|||
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||
import { GetterService } from './GetterService.js';
|
||||
import { ApiLoggerService } from './ApiLoggerService.js';
|
||||
import * as ep___admin_accounts_present_points from './endpoints/admin/accounts/present-points.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
import * as ep___emoji_speedtest from './endpoints/admin/emoji/speedtest.js';
|
||||
const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default };
|
||||
const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
|
||||
const $admin_abuseReport_notificationRecipient_list: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/list', useClass: ep___admin_abuseReport_notificationRecipient_list.default };
|
||||
|
@ -399,6 +412,7 @@ const $admin_abuseReport_notificationRecipient_delete: Provider = { provide: 'ep
|
|||
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_accounts_present_points: Provider = { provide: 'ep:admin/accounts/present-points', useClass: ep___admin_accounts_present_points.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 };
|
||||
|
@ -414,23 +428,29 @@ const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-de
|
|||
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 $emoji_speedtest: Provider = { provide: 'ep:emoji/speedtest', useClass: ep___emoji_speedtest.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 };
|
||||
const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
|
||||
const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
|
||||
const $admin_emoji_setlocalOnlyBulk: Provider = { provide: 'ep:admin/emoji/set-localonly-bulk', useClass: ep___admin_emoji_setlocalOnlyBulk.default };
|
||||
const $admin_emoji_setisSensitiveBulk: Provider = { provide: 'ep:admin/emoji/set-issensitive-bulk', useClass: ep___admin_emoji_setisSensitiveBulk.default };
|
||||
const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default };
|
||||
const $admin_emoji_addRequest: Provider = { provide: 'ep:admin/emoji/add-request', useClass: ep___admin_emoji_addRequest.default };
|
||||
const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default };
|
||||
const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default };
|
||||
const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default };
|
||||
const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default };
|
||||
const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default };
|
||||
const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default };
|
||||
const $admin_emoji_listRequest: Provider = { provide: 'ep:admin/emoji/list-request', useClass: ep___admin_emoji_listRequest.default };
|
||||
const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
|
||||
const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default };
|
||||
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
|
||||
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
|
||||
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
||||
const $admin_emoji_updateRequest: Provider = { provide: 'ep:admin/emoji/update-request', useClass: ep___admin_emoji_updateRequest.default };
|
||||
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
|
||||
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
|
||||
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
|
||||
|
@ -615,6 +635,7 @@ const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep_
|
|||
const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
|
||||
const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
|
||||
const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
|
||||
const $i_userstats: Provider = { provide: 'ep:i/stats', useClass: ep___users_user_stats.default };
|
||||
const $i_notificationsGrouped: Provider = { provide: 'ep:i/notifications-grouped', useClass: ep___i_notificationsGrouped.default };
|
||||
const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
|
||||
const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
|
||||
|
@ -647,6 +668,7 @@ const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invit
|
|||
const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
|
||||
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
|
||||
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
|
||||
const $emoji_requests: Provider = { provide: 'ep:emoji-requests', useClass: ep___emojiRequests.default };
|
||||
const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
|
||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||
|
@ -661,13 +683,17 @@ const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep__
|
|||
const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default };
|
||||
const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
|
||||
const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
|
||||
const $notes_schedule_delete: Provider = { provide: 'ep:notes/schedule/delete', useClass: ep___notes_schedule_delete.default };
|
||||
const $notes_schedule_list: Provider = { provide: 'ep:notes/schedule/list', useClass: ep___notes_schedule_list.default };
|
||||
const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
|
||||
const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default };
|
||||
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
|
||||
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
|
||||
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
|
||||
const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
|
||||
const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
|
||||
const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
|
||||
const $notes_anyLocalTimeline: Provider = { provide: 'ep:notes/any-local-timeline', useClass: ep___notes_anyLocalTimeline.default };
|
||||
const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
|
||||
const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
|
||||
const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
|
||||
|
@ -735,6 +761,7 @@ const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', use
|
|||
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
|
||||
const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
|
||||
const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
|
||||
const $users_lists_list_favorite: Provider = { provide: 'ep:users/lists/list-favorite', useClass: ep___users_lists_list_favorite.default };
|
||||
const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default };
|
||||
const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
|
||||
const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
|
||||
|
@ -786,6 +813,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_accounts_create,
|
||||
$admin_accounts_delete,
|
||||
$admin_accounts_findByEmail,
|
||||
$admin_accounts_present_points,
|
||||
$admin_ad_create,
|
||||
$admin_ad_delete,
|
||||
$admin_ad_list,
|
||||
|
@ -800,24 +828,30 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_avatarDecorations_update,
|
||||
$admin_deleteAllFilesOfAUser,
|
||||
$admin_unsetUserAvatar,
|
||||
$emoji_speedtest,
|
||||
$admin_unsetUserBanner,
|
||||
$admin_drive_cleanRemoteFiles,
|
||||
$admin_drive_cleanup,
|
||||
$admin_drive_files,
|
||||
$admin_drive_showFile,
|
||||
$admin_emoji_addAliasesBulk,
|
||||
$admin_emoji_setlocalOnlyBulk,
|
||||
$admin_emoji_setisSensitiveBulk,
|
||||
$admin_emoji_add,
|
||||
$admin_emoji_addRequest,
|
||||
$admin_emoji_copy,
|
||||
$admin_emoji_deleteBulk,
|
||||
$admin_emoji_delete,
|
||||
$admin_emoji_importZip,
|
||||
$admin_emoji_listRemote,
|
||||
$admin_emoji_list,
|
||||
$admin_emoji_listRequest,
|
||||
$admin_emoji_removeAliasesBulk,
|
||||
$admin_emoji_setAliasesBulk,
|
||||
$admin_emoji_setCategoryBulk,
|
||||
$admin_emoji_setLicenseBulk,
|
||||
$admin_emoji_update,
|
||||
$admin_emoji_updateRequest,
|
||||
$admin_federation_deleteAllFiles,
|
||||
$admin_federation_refreshRemoteInstanceMetadata,
|
||||
$admin_federation_removeAllFollowing,
|
||||
|
@ -890,6 +924,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$channels_timeline,
|
||||
$channels_unfollow,
|
||||
$channels_update,
|
||||
$i_userstats,
|
||||
$channels_favorite,
|
||||
$channels_unfavorite,
|
||||
$channels_myFavorites,
|
||||
|
@ -1034,6 +1069,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$invite_limit,
|
||||
$meta,
|
||||
$emojis,
|
||||
$emoji_requests,
|
||||
$emoji,
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
|
@ -1048,13 +1084,17 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$notes_clips,
|
||||
$notes_conversation,
|
||||
$notes_create,
|
||||
$notes_schedule_delete,
|
||||
$notes_schedule_list,
|
||||
$notes_delete,
|
||||
$notes_update,
|
||||
$notes_favorites_create,
|
||||
$notes_favorites_delete,
|
||||
$notes_featured,
|
||||
$notes_globalTimeline,
|
||||
$notes_hybridTimeline,
|
||||
$notes_localTimeline,
|
||||
$notes_anyLocalTimeline,
|
||||
$notes_mentions,
|
||||
$notes_polls_recommendation,
|
||||
$notes_polls_vote,
|
||||
|
@ -1122,6 +1162,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$users_lists_create,
|
||||
$users_lists_delete,
|
||||
$users_lists_list,
|
||||
$users_lists_list_favorite,
|
||||
$users_lists_pull,
|
||||
$users_lists_push,
|
||||
$users_lists_show,
|
||||
|
@ -1167,6 +1208,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_accounts_create,
|
||||
$admin_accounts_delete,
|
||||
$admin_accounts_findByEmail,
|
||||
$admin_accounts_present_points,
|
||||
$admin_ad_create,
|
||||
$admin_ad_delete,
|
||||
$admin_ad_list,
|
||||
|
@ -1182,24 +1224,31 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_deleteAllFilesOfAUser,
|
||||
$admin_unsetUserAvatar,
|
||||
$admin_unsetUserBanner,
|
||||
$emoji_speedtest,
|
||||
$admin_drive_cleanRemoteFiles,
|
||||
$admin_drive_cleanup,
|
||||
$admin_drive_files,
|
||||
$admin_drive_showFile,
|
||||
$admin_emoji_addAliasesBulk,
|
||||
$admin_emoji_add,
|
||||
$admin_emoji_addRequest,
|
||||
$admin_emoji_copy,
|
||||
$admin_emoji_deleteBulk,
|
||||
$admin_emoji_delete,
|
||||
$admin_emoji_importZip,
|
||||
$admin_emoji_listRemote,
|
||||
$admin_emoji_list,
|
||||
$admin_emoji_listRequest,
|
||||
$admin_emoji_removeAliasesBulk,
|
||||
$admin_emoji_setAliasesBulk,
|
||||
$admin_emoji_setCategoryBulk,
|
||||
$admin_emoji_setLicenseBulk,
|
||||
$admin_emoji_setlocalOnlyBulk,
|
||||
$admin_emoji_setisSensitiveBulk,
|
||||
$admin_emoji_update,
|
||||
$admin_emoji_updateRequest,
|
||||
$admin_federation_deleteAllFiles,
|
||||
$i_userstats,
|
||||
$admin_federation_refreshRemoteInstanceMetadata,
|
||||
$admin_federation_removeAllFollowing,
|
||||
$admin_federation_updateInstance,
|
||||
|
@ -1415,6 +1464,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$invite_limit,
|
||||
$meta,
|
||||
$emojis,
|
||||
$emoji_requests,
|
||||
$emoji,
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
|
@ -1429,13 +1479,17 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$notes_clips,
|
||||
$notes_conversation,
|
||||
$notes_create,
|
||||
$notes_schedule_delete,
|
||||
$notes_schedule_list,
|
||||
$notes_delete,
|
||||
$notes_update,
|
||||
$notes_favorites_create,
|
||||
$notes_favorites_delete,
|
||||
$notes_featured,
|
||||
$notes_globalTimeline,
|
||||
$notes_hybridTimeline,
|
||||
$notes_localTimeline,
|
||||
$notes_anyLocalTimeline,
|
||||
$notes_mentions,
|
||||
$notes_polls_recommendation,
|
||||
$notes_polls_vote,
|
||||
|
@ -1501,6 +1555,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$users_lists_create,
|
||||
$users_lists_delete,
|
||||
$users_lists_list,
|
||||
$users_lists_list_favorite,
|
||||
$users_lists_pull,
|
||||
$users_lists_push,
|
||||
$users_lists_show,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { IsNull } from 'typeorm';
|
||||
import ProxyCheck from 'proxycheck-ts';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -108,6 +109,24 @@ export class SignupApiService {
|
|||
const invitationCode = body['invitationCode'];
|
||||
const emailAddress = body['emailAddress'];
|
||||
|
||||
const { DiscordWebhookUrl } = (await this.metaService.fetch());
|
||||
if (DiscordWebhookUrl) {
|
||||
const data_disc = { 'username': 'ユーザー登録お知らせ',
|
||||
'content':
|
||||
'ユーザー名 :' + username + '\n' +
|
||||
'メールアドレス : ' + emailAddress + '\n' +
|
||||
'IPアドレス : ' + request.headers['x-real-ip'] ?? request.ip,
|
||||
};
|
||||
|
||||
await fetch(DiscordWebhookUrl, {
|
||||
'method': 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data_disc),
|
||||
});
|
||||
}
|
||||
|
||||
if (instance.emailRequiredForSignup) {
|
||||
if (emailAddress == null || typeof emailAddress !== 'string') {
|
||||
reply.code(400);
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
import { permissions } from 'misskey-js';
|
||||
import type { KeyOf, Schema } from '@/misc/json-schema.js';
|
||||
|
||||
import { RolePolicies } from '@/core/RoleService.js';
|
||||
import * as ep___admin_emoji_setlocalOnlyBulk from './endpoints/admin/emoji/set-localonly-bulk.js';
|
||||
import * as ep___admin_emoji_setisSensitiveBulk from './endpoints/admin/emoji/set-issensitive-bulk.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_list
|
||||
from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
|
||||
import * as ep___admin_abuseReport_notificationRecipient_show
|
||||
|
@ -21,6 +23,8 @@ import * as ep___admin_meta from './endpoints/admin/meta.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_accounts_present_points from './endpoints/admin/accounts/present-points.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';
|
||||
|
@ -42,17 +46,21 @@ import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
|||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_addRequest from './endpoints/admin/emoji/add-request.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
|
||||
import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
|
||||
import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_listRequest from './endpoints/admin/emoji/list-request.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_emoji_updateRequest from './endpoints/admin/emoji/update-request.js';
|
||||
import * as ep___emoji_speedtest from './endpoints/admin/emoji/speedtest.js';
|
||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata
|
||||
from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||
|
@ -264,12 +272,14 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
|||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
||||
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
||||
import * as ep___i_user_stats from './endpoints/users/stats.js';
|
||||
import * as ep___invite_create from './endpoints/invite/create.js';
|
||||
import * as ep___invite_delete from './endpoints/invite/delete.js';
|
||||
import * as ep___invite_list from './endpoints/invite/list.js';
|
||||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___emojiRequests from './endpoints/emoji-requests.js';
|
||||
import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
|
@ -284,13 +294,17 @@ import * as ep___notes_children from './endpoints/notes/children.js';
|
|||
import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
|
||||
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_update from './endpoints/notes/update.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
|
||||
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
|
||||
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
|
||||
import * as ep___notes_anyLocalTimeline from './endpoints/notes/any-local-timeline.js';
|
||||
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
|
||||
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
|
||||
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
|
||||
|
@ -358,6 +372,7 @@ import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
|||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
import * as ep___users_lists_list_favorite from './endpoints/users/lists/list-favorite.js';
|
||||
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||
import * as ep___users_lists_show from './endpoints/users/lists/show.js';
|
||||
|
@ -391,7 +406,6 @@ import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
|
|||
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
|
||||
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
||||
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||
|
||||
const eps = [
|
||||
['admin/meta', ep___admin_meta],
|
||||
['admin/abuse-user-reports', ep___admin_abuseUserReports],
|
||||
|
@ -403,6 +417,7 @@ const eps = [
|
|||
['admin/accounts/create', ep___admin_accounts_create],
|
||||
['admin/accounts/delete', ep___admin_accounts_delete],
|
||||
['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
|
||||
['admin/accounts/present-points', ep___admin_accounts_present_points],
|
||||
['admin/ad/create', ep___admin_ad_create],
|
||||
['admin/ad/delete', ep___admin_ad_delete],
|
||||
['admin/ad/list', ep___admin_ad_list],
|
||||
|
@ -424,17 +439,23 @@ const eps = [
|
|||
['admin/drive/show-file', ep___admin_drive_showFile],
|
||||
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
|
||||
['admin/emoji/add', ep___admin_emoji_add],
|
||||
['admin/emoji/add-request', ep___admin_emoji_addRequest],
|
||||
['admin/emoji/copy', ep___admin_emoji_copy],
|
||||
['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk],
|
||||
['admin/emoji/delete', ep___admin_emoji_delete],
|
||||
['admin/emoji/import-zip', ep___admin_emoji_importZip],
|
||||
['admin/emoji/list-remote', ep___admin_emoji_listRemote],
|
||||
['admin/emoji/list', ep___admin_emoji_list],
|
||||
['admin/emoji/list-request', ep___admin_emoji_listRequest],
|
||||
['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
|
||||
['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
|
||||
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
||||
['admin/emoji/set-localonly-bulk', ep___admin_emoji_setlocalOnlyBulk],
|
||||
['admin/emoji/set-issensitive-bulk', ep___admin_emoji_setisSensitiveBulk],
|
||||
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
||||
['admin/emoji/update', ep___admin_emoji_update],
|
||||
['admin/emoji/update-request', ep___admin_emoji_updateRequest],
|
||||
['emoji/speedtest', ep___emoji_speedtest],
|
||||
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
||||
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
||||
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
||||
|
@ -645,12 +666,14 @@ const eps = [
|
|||
['i/webhooks/show', ep___i_webhooks_show],
|
||||
['i/webhooks/update', ep___i_webhooks_update],
|
||||
['i/webhooks/delete', ep___i_webhooks_delete],
|
||||
['i/stats', ep___i_user_stats],
|
||||
['invite/create', ep___invite_create],
|
||||
['invite/delete', ep___invite_delete],
|
||||
['invite/list', ep___invite_list],
|
||||
['invite/limit', ep___invite_limit],
|
||||
['meta', ep___meta],
|
||||
['emojis', ep___emojis],
|
||||
['emoji-requests', ep___emojiRequests],
|
||||
['emoji', ep___emoji],
|
||||
['miauth/gen-token', ep___miauth_genToken],
|
||||
['mute/create', ep___mute_create],
|
||||
|
@ -665,13 +688,17 @@ const eps = [
|
|||
['notes/clips', ep___notes_clips],
|
||||
['notes/conversation', ep___notes_conversation],
|
||||
['notes/create', ep___notes_create],
|
||||
['notes/schedule/delete', ep___notes_schedule_delete],
|
||||
['notes/schedule/list', ep___notes_schedule_list],
|
||||
['notes/delete', ep___notes_delete],
|
||||
['notes/update', ep___notes_update],
|
||||
['notes/favorites/create', ep___notes_favorites_create],
|
||||
['notes/favorites/delete', ep___notes_favorites_delete],
|
||||
['notes/featured', ep___notes_featured],
|
||||
['notes/global-timeline', ep___notes_globalTimeline],
|
||||
['notes/hybrid-timeline', ep___notes_hybridTimeline],
|
||||
['notes/local-timeline', ep___notes_localTimeline],
|
||||
['notes/any-local-timeline', ep___notes_anyLocalTimeline],
|
||||
['notes/mentions', ep___notes_mentions],
|
||||
['notes/polls/recommendation', ep___notes_polls_recommendation],
|
||||
['notes/polls/vote', ep___notes_polls_vote],
|
||||
|
@ -739,6 +766,7 @@ const eps = [
|
|||
['users/lists/create', ep___users_lists_create],
|
||||
['users/lists/delete', ep___users_lists_delete],
|
||||
['users/lists/list', ep___users_lists_list],
|
||||
['users/lists/list-favorite', ep___users_lists_list_favorite],
|
||||
['users/lists/pull', ep___users_lists_pull],
|
||||
['users/lists/push', ep___users_lists_push],
|
||||
['users/lists/show', ep___users_lists_show],
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:account',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
points: { type: 'number' },
|
||||
},
|
||||
required: ['userId', 'points'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
private notificationService: NotificationService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
this.usersRepository.update( user.id, {
|
||||
getPoints: user.getPoints + ps.points,
|
||||
});
|
||||
this.notificationService.createNotification(user.id, 'loginbonus', {
|
||||
loginbonus: ps.points,
|
||||
});
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ export const paramDef = {
|
|||
roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
category: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['name', 'description', 'url'],
|
||||
} as const;
|
||||
|
@ -39,6 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
description: ps.description,
|
||||
url: ps.url,
|
||||
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
||||
category: ps.category ?? '',
|
||||
}, me);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
name: avatarDecoration.name,
|
||||
description: avatarDecoration.description,
|
||||
url: avatarDecoration.url,
|
||||
category: avatarDecoration.category,
|
||||
roleIdsThatCanBeUsedThisDecoration: avatarDecoration.roleIdsThatCanBeUsedThisDecoration,
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ export const paramDef = {
|
|||
roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
category: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['id'],
|
||||
} as const;
|
||||
|
@ -45,6 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
description: ps.description,
|
||||
url: ps.url,
|
||||
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
||||
category: ps.category ?? '',
|
||||
}, me);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import {MetaService} from "@/core/MetaService.js";
|
||||
import {DriveService} from "@/core/DriveService.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canRequestCustomEmojis',
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
||||
},
|
||||
duplicateName: {
|
||||
message: 'Duplicate name.',
|
||||
code: 'DUPLICATE_NAME',
|
||||
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean', nullable: true },
|
||||
localOnly: { type: 'boolean', nullable: true },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
isNotifyIsHome: { type: 'boolean', nullable: true },
|
||||
},
|
||||
required: ['name', 'fileId'],
|
||||
} as const;
|
||||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
||||
@Injectable()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
private metaService: MetaService,
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private driveService: DriveService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
|
||||
const isRequestDuplicate = await this.customEmojiService.checkRequestDuplicate(ps.name);
|
||||
|
||||
if (isDuplicate || isRequestDuplicate) throw new ApiError(meta.errors.duplicateName);
|
||||
let driveFile;
|
||||
let tmp = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
if (tmp == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
|
||||
try {
|
||||
driveFile = await this.driveService.uploadFromUrl({ url: tmp.url , user: null, force: true });
|
||||
} catch (e) {
|
||||
throw new ApiError();
|
||||
}
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
const {ApiBase,EmojiBotToken,DiscordWebhookUrl,requestEmojiAllOk} = (await this.metaService.fetch())
|
||||
let emoji;
|
||||
if (requestEmojiAllOk){
|
||||
emoji = await this.customEmojiService.add({
|
||||
driveFile,
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
license: ps.license ?? null,
|
||||
host: null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||
});
|
||||
}else{
|
||||
emoji = await this.customEmojiService.request({
|
||||
driveFile,
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
|
||||
if (EmojiBotToken){
|
||||
const data_Miss = {
|
||||
'i': EmojiBotToken,
|
||||
'visibility': ps.isNotifyIsHome ? 'home' : 'public',
|
||||
'text':
|
||||
'絵文字名 : :' + ps.name + ':\n' +
|
||||
'カテゴリ : ' + ps.category + '\n' +
|
||||
'ライセンス : ' + ps.license + '\n' +
|
||||
'タグ : ' + ps.aliases + '\n' +
|
||||
'追加したユーザー : ' + '@' + me.username + '\n'
|
||||
};
|
||||
await fetch(ApiBase+'/notes/create', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body:JSON.stringify( data_Miss)
|
||||
})
|
||||
}
|
||||
|
||||
if (DiscordWebhookUrl){
|
||||
const data_disc = {"username": "絵文字追加通知ちゃん",
|
||||
'content':
|
||||
'絵文字名 : :'+ ps.name +':\n' +
|
||||
'カテゴリ : ' + ps.category + '\n'+
|
||||
'ライセンス : '+ ps.license + '\n'+
|
||||
'タグ : '+ps.aliases+ '\n'+
|
||||
'追加したユーザー : ' + '@'+me.username + '\n'
|
||||
}
|
||||
await fetch(DiscordWebhookUrl, {
|
||||
'method':'post',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data_disc),
|
||||
})
|
||||
}
|
||||
return {
|
||||
id: emoji.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -47,15 +47,19 @@ export const paramDef = {
|
|||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
aliases: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['name', 'fileId'],
|
||||
} as const;
|
||||
|
@ -67,13 +71,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private customEmojiService: CustomEmojiService,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
|
||||
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
|
||||
|
|
|
@ -37,7 +37,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.customEmojiService.delete(ps.id, me);
|
||||
const emoji = await this.customEmojiService.getEmojiById(ps.id);
|
||||
const RequestEmoji = await this.customEmojiService.getEmojiRequestById(ps.id);
|
||||
if (emoji != null) {
|
||||
await this.customEmojiService.delete(ps.id, me);
|
||||
}
|
||||
if (RequestEmoji != null) {
|
||||
await this.customEmojiService.deleteRequest(ps.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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 { EmojiRequestsRepository } from '@/models/_.js';
|
||||
import type { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { EmojiRequestsEntityService } from '@/core/entities/EmojiRequestsEntityService.js';
|
||||
//import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', nullable: true, default: null },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
|
||||
private emojiRequestsEntityService: EmojiRequestsEntityService,
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const q = this.queryService.makePaginationQuery(this.emojiRequestsRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
|
||||
|
||||
let emojis: MiEmojiRequest[];
|
||||
|
||||
if (ps.query) {
|
||||
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
//const emojis = await q.limit(ps.limit).getMany();
|
||||
|
||||
emojis = await q.getMany();
|
||||
const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
|
||||
|
||||
if (queryarry) {
|
||||
emojis = emojis.filter(emoji =>
|
||||
queryarry.includes(`:${emoji.name}:`),
|
||||
);
|
||||
} else {
|
||||
emojis = emojis.filter(emoji =>
|
||||
emoji.name.includes(ps.query!) ||
|
||||
emoji.aliases.some(a => a.includes(ps.query!)) ||
|
||||
emoji.category?.includes(ps.query!));
|
||||
}
|
||||
emojis.splice(ps.limit + 1);
|
||||
} else {
|
||||
emojis = await q.limit(ps.limit).getMany();
|
||||
}
|
||||
|
||||
return this.emojiRequestsEntityService.packDetailedMany(emojis);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ export const paramDef = {
|
|||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', nullable: true, default: null },
|
||||
draft: { type: 'boolean', nullable: true, default: null },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
|
@ -87,6 +88,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
let emojis: MiEmoji[];
|
||||
|
||||
if (ps.draft !== null) {
|
||||
if (ps.draft) {
|
||||
q.andWhere('emoji.draft = TRUE');
|
||||
} else {
|
||||
q.andWhere('emoji.draft = FALSE');
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.query) {
|
||||
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
//const emojis = await q.limit(ps.limit).getMany();
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ids: { type: 'array', items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
nullable: false,
|
||||
description: 'Use `null` to reset the licence.',
|
||||
},
|
||||
},
|
||||
required: ['ids'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.customEmojiService.setisSensitiveBulk(ps.ids, ps.isSensitive ?? false);
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue