Compare commits
21 Commits
a92858944d
...
14cd43d0d8
Author | SHA1 | Date | |
---|---|---|---|
14cd43d0d8 | |||
b8dcaecd2e | |||
03c64c296b | |||
ab84c9afa0 | |||
4671fc4139 | |||
cda3d93db8 | |||
3e90f8995e | |||
|
410b36b5a0 | ||
|
7553e64faf | ||
|
57753225ea | ||
|
45faba3567 | ||
|
613e0a8aa3 | ||
|
7cc8c2a22b | ||
|
1328420f99 | ||
|
0f21232828 | ||
|
d4bbae8d45 | ||
|
62e801bf3a | ||
|
5c9ea07e2b | ||
|
557f45b9d1 | ||
|
fcfd004c38 | ||
|
2f4c48bbe6 |
@ -195,4 +195,4 @@ signToActivityPubGet: true
|
|||||||
#maxFileSize: 262144000
|
#maxFileSize: 262144000
|
||||||
|
|
||||||
# Value of Content-Security-Policy header
|
# Value of Content-Security-Policy header
|
||||||
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
|
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/ https://www.googletagmanager.com/; base-uri 'self'; object-src 'self';"
|
||||||
|
@ -227,4 +227,4 @@ signToActivityPubGet: true
|
|||||||
#pidFile: /tmp/misskey.pid
|
#pidFile: /tmp/misskey.pid
|
||||||
|
|
||||||
# Value of Content-Security-Policy header
|
# Value of Content-Security-Policy header
|
||||||
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
|
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/ https://www.googletagmanager.com/; base-uri 'self'; object-src 'self';"
|
||||||
|
@ -87,6 +87,8 @@
|
|||||||
- Fix: `/i/notifications`に `includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正
|
- Fix: `/i/notifications`に `includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正
|
||||||
- Fix: 複数idを指定する`users/show`が関係ないユーザを返すことがある問題を修正
|
- Fix: 複数idを指定する`users/show`が関係ないユーザを返すことがある問題を修正
|
||||||
- Fix: `/tags` と `/user-tags` が検索エンジンにインデックスされないように
|
- Fix: `/tags` と `/user-tags` が検索エンジンにインデックスされないように
|
||||||
|
- Fix: もともとセンシティブではないと連合されていたファイルがセンシティブとして連合された場合にセンシティブとしてそのファイルを扱うように
|
||||||
|
- センシティブとして連合したファイルは非センシティブとして連合されてもセンシティブとして扱われます
|
||||||
|
|
||||||
## 2024.3.1
|
## 2024.3.1
|
||||||
|
|
||||||
|
@ -1272,6 +1272,33 @@ keepOriginalFilenameDescription: "If you turn off this setting, files names will
|
|||||||
noDescription: "There is not the explanation"
|
noDescription: "There is not the explanation"
|
||||||
alwaysConfirmFollow: "Always confirm when following"
|
alwaysConfirmFollow: "Always confirm when following"
|
||||||
inquiry: "Contact"
|
inquiry: "Contact"
|
||||||
|
tryAgain: "Please try again later"
|
||||||
|
confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media"
|
||||||
|
sensitiveMediaRevealConfirm: "This might be a sensitive media. Are you sure to reveal?"
|
||||||
|
createdLists: "Created lists"
|
||||||
|
createdAntennas: "Created antennas"
|
||||||
|
fromX: "From {x}"
|
||||||
|
genEmbedCode: "Generate embed code"
|
||||||
|
noteOfThisUser: "Notes by this user"
|
||||||
|
clipNoteLimitExceeded: "No more notes can be added to this clip."
|
||||||
|
performance: "Performance"
|
||||||
|
modified: "Modified"
|
||||||
|
discard: "Discard"
|
||||||
|
thereAreNChanges: "There are {n} change(s)"
|
||||||
|
signinWithPasskey: "Sign in with Passkey"
|
||||||
|
unknownWebAuthnKey: "Unknown Passkey"
|
||||||
|
passkeyVerificationFailed: "Passkey verification has failed."
|
||||||
|
passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification has succeeded but password-less login is disabled."
|
||||||
|
messageToFollower: "Message to followers"
|
||||||
|
target: "Target"
|
||||||
|
testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\n<strong>Do not use in a production environment.</strong>"
|
||||||
|
prohibitedWordsForNameOfUser: "Prohibited words for user names"
|
||||||
|
prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction."
|
||||||
|
yourNameContainsProhibitedWords: "Your name contains prohibited words"
|
||||||
|
yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator."
|
||||||
|
thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require login to view"
|
||||||
|
lockdown: "Lockdown"
|
||||||
|
pleaseSelectAccount: "Select an account"
|
||||||
here: "here"
|
here: "here"
|
||||||
credits: "Closing Credits"
|
credits: "Closing Credits"
|
||||||
timeWillCome: "Will this have your name on it someday?"
|
timeWillCome: "Will this have your name on it someday?"
|
||||||
@ -1287,6 +1314,26 @@ autoRemoval: "Automatic note deletion"
|
|||||||
autoRemovalDescription: "You can delete your note when it exceeds period you set."
|
autoRemovalDescription: "You can delete your note when it exceeds period you set."
|
||||||
CheckedByHIBP: "In addition to ensuring your passwords are secure, HIBP scans for password leaks."
|
CheckedByHIBP: "In addition to ensuring your passwords are secure, HIBP scans for password leaks."
|
||||||
changeUserName: "Change name"
|
changeUserName: "Change name"
|
||||||
|
gtagConsentCustomize: "Data Collection and Privacy Settings"
|
||||||
|
gtagConsentCustomizeDescription: "You can customize the scope of data collected by {host}.\nHowever, you cannot disable the collection of security-related information such as authentication features, fraud prevention, and other user protections."
|
||||||
|
gtagConsentAnalytics: "Collection of Statistical Information"
|
||||||
|
gtagConsentAnalyticsDescription: "Enable the storage (cookies, etc.) of analytics-related information such as site visit duration."
|
||||||
|
gtagConsentFunctionality: "Collection of Feature and Setting Usage"
|
||||||
|
gtagConsentFunctionalityDescription: "Enable the storage of information that supports website or app features, such as language settings."
|
||||||
|
gtagConsentPersonalization: "Collection of Personalized Information"
|
||||||
|
gtagConsentPersonalizationDescription: "Enable the storage of personalization-related information such as recommended posts."
|
||||||
|
helpUsImproveUserExperience: "To build the future of Misskey,\nplease help us by agreeing to data collection!"
|
||||||
|
pleaseConsentToTracking: "{host} may collect information that may include personal data such as your IP address, usage data, and device information during your use, based on our [Privacy Policy]({privacyPolicyUrl}), for the purpose of providing and operating the service and improving the user experience.\n\nThe collected data will be used for future feature development, operational policy decisions, and identifying areas for service improvement."
|
||||||
|
consentEssential: "Allow Essential Items"
|
||||||
|
consentAll: "Allow All Items"
|
||||||
|
consentSelected: "Allow Selected Items"
|
||||||
|
normalize: "Normalize"
|
||||||
|
normalizeConfirm: "After normalization, the account will be irreversible. Are you sure you want to do this?"
|
||||||
|
normalizeDescription: "Normalization is a feature for bulk data wiping and account suspension of users, which has a similar effect to deleting an account and closes all reports after it is run. Please note that this is an irreversible action."
|
||||||
|
useNormalization: "Show the Normalize menu"
|
||||||
|
alsoMuteNotification: "Also mute notifications from this user"
|
||||||
|
muteNotification: "Mute notifications"
|
||||||
|
unmuteNotification: "Unmute notifications"
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "How to play"
|
howToPlay: "How to play"
|
||||||
hold: "Hold"
|
hold: "Hold"
|
||||||
@ -2205,8 +2252,11 @@ _auth:
|
|||||||
permissionAsk: "This application requests the following permissions"
|
permissionAsk: "This application requests the following permissions"
|
||||||
pleaseGoBack: "Please go back to the application"
|
pleaseGoBack: "Please go back to the application"
|
||||||
callback: "Returning to the application"
|
callback: "Returning to the application"
|
||||||
|
accepted: "Access granted"
|
||||||
denied: "Access denied"
|
denied: "Access denied"
|
||||||
|
scopeUser: "Operate as the following user"
|
||||||
pleaseLogin: "Please log in to authorize applications."
|
pleaseLogin: "Please log in to authorize applications."
|
||||||
|
byClickingYouWillBeRedirectedToThisUrl: "When access is granted, you will automatically be redirected to the following URL"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "All notes"
|
all: "All notes"
|
||||||
homeTimeline: "Notes from followed users"
|
homeTimeline: "Notes from followed users"
|
||||||
@ -2578,6 +2628,7 @@ _moderationLogTypes:
|
|||||||
deleteAvatarDecoration: "Avatar decoration deleted"
|
deleteAvatarDecoration: "Avatar decoration deleted"
|
||||||
unsetUserAvatar: "Unset this user's avatar"
|
unsetUserAvatar: "Unset this user's avatar"
|
||||||
unsetUserBanner: "Unset this user's banner"
|
unsetUserBanner: "Unset this user's banner"
|
||||||
|
normalize: "Normalization"
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "File details"
|
title: "File details"
|
||||||
type: "File type"
|
type: "File type"
|
||||||
|
222
locales/index.d.ts
vendored
222
locales/index.d.ts
vendored
@ -5164,6 +5164,118 @@ export interface Locale extends ILocale {
|
|||||||
* お問い合わせ
|
* お問い合わせ
|
||||||
*/
|
*/
|
||||||
"inquiry": string;
|
"inquiry": string;
|
||||||
|
/**
|
||||||
|
* もう一度お試しください。
|
||||||
|
*/
|
||||||
|
"tryAgain": string;
|
||||||
|
/**
|
||||||
|
* センシティブなメディアを表示するとき確認する
|
||||||
|
*/
|
||||||
|
"confirmWhenRevealingSensitiveMedia": string;
|
||||||
|
/**
|
||||||
|
* センシティブなメディアです。表示しますか?
|
||||||
|
*/
|
||||||
|
"sensitiveMediaRevealConfirm": string;
|
||||||
|
/**
|
||||||
|
* 作成したリスト
|
||||||
|
*/
|
||||||
|
"createdLists": string;
|
||||||
|
/**
|
||||||
|
* 作成したアンテナ
|
||||||
|
*/
|
||||||
|
"createdAntennas": string;
|
||||||
|
/**
|
||||||
|
* {x}から
|
||||||
|
*/
|
||||||
|
"fromX": ParameterizedString<"x">;
|
||||||
|
/**
|
||||||
|
* 埋め込みコードを生成
|
||||||
|
*/
|
||||||
|
"genEmbedCode": string;
|
||||||
|
/**
|
||||||
|
* このユーザーのノート一覧
|
||||||
|
*/
|
||||||
|
"noteOfThisUser": string;
|
||||||
|
/**
|
||||||
|
* これ以上このクリップにノートを追加できません。
|
||||||
|
*/
|
||||||
|
"clipNoteLimitExceeded": string;
|
||||||
|
/**
|
||||||
|
* パフォーマンス
|
||||||
|
*/
|
||||||
|
"performance": string;
|
||||||
|
/**
|
||||||
|
* 変更あり
|
||||||
|
*/
|
||||||
|
"modified": string;
|
||||||
|
/**
|
||||||
|
* 破棄
|
||||||
|
*/
|
||||||
|
"discard": string;
|
||||||
|
/**
|
||||||
|
* {n}件の変更があります
|
||||||
|
*/
|
||||||
|
"thereAreNChanges": ParameterizedString<"n">;
|
||||||
|
/**
|
||||||
|
* パスキーでログイン
|
||||||
|
*/
|
||||||
|
"signinWithPasskey": string;
|
||||||
|
/**
|
||||||
|
* 登録されていないパスキーです。
|
||||||
|
*/
|
||||||
|
"unknownWebAuthnKey": string;
|
||||||
|
/**
|
||||||
|
* パスキーの検証に失敗しました。
|
||||||
|
*/
|
||||||
|
"passkeyVerificationFailed": string;
|
||||||
|
/**
|
||||||
|
* パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。
|
||||||
|
*/
|
||||||
|
"passkeyVerificationSucceededButPasswordlessLoginDisabled": string;
|
||||||
|
/**
|
||||||
|
* フォロワーへのメッセージ
|
||||||
|
*/
|
||||||
|
"messageToFollower": string;
|
||||||
|
/**
|
||||||
|
* 対象
|
||||||
|
*/
|
||||||
|
"target": string;
|
||||||
|
/**
|
||||||
|
* CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>
|
||||||
|
*/
|
||||||
|
"testCaptchaWarning": string;
|
||||||
|
/**
|
||||||
|
* 禁止ワード(ユーザーの名前)
|
||||||
|
*/
|
||||||
|
"prohibitedWordsForNameOfUser": string;
|
||||||
|
/**
|
||||||
|
* このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。
|
||||||
|
*/
|
||||||
|
"prohibitedWordsForNameOfUserDescription": string;
|
||||||
|
/**
|
||||||
|
* 変更しようとした名前に禁止された文字列が含まれています
|
||||||
|
*/
|
||||||
|
"yourNameContainsProhibitedWords": string;
|
||||||
|
/**
|
||||||
|
* 名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。
|
||||||
|
*/
|
||||||
|
"yourNameContainsProhibitedWordsDescription": string;
|
||||||
|
/**
|
||||||
|
* 投稿者により、表示にはログインが必要と設定されています
|
||||||
|
*/
|
||||||
|
"thisContentsAreMarkedAsSigninRequiredByAuthor": string;
|
||||||
|
/**
|
||||||
|
* ロックダウン
|
||||||
|
*/
|
||||||
|
"lockdown": string;
|
||||||
|
/**
|
||||||
|
* アカウントを選択してください
|
||||||
|
*/
|
||||||
|
"pleaseSelectAccount": string;
|
||||||
|
/**
|
||||||
|
* 利用可能なロール
|
||||||
|
*/
|
||||||
|
"availableRoles": string;
|
||||||
/**
|
/**
|
||||||
* 通報の種類
|
* 通報の種類
|
||||||
*/
|
*/
|
||||||
@ -5312,6 +5424,90 @@ export interface Locale extends ILocale {
|
|||||||
* 名前を変更
|
* 名前を変更
|
||||||
*/
|
*/
|
||||||
"changeUserName": string;
|
"changeUserName": string;
|
||||||
|
/**
|
||||||
|
* 正常化
|
||||||
|
*/
|
||||||
|
"normalize": string;
|
||||||
|
/**
|
||||||
|
* 正常化すると元に戻せなくなり、これはアカウントの削除と同様の効力を持ちます。実行しますか?
|
||||||
|
*/
|
||||||
|
"normalizeConfirm": string;
|
||||||
|
/**
|
||||||
|
* 正常化は、ユーザーの一括的なデータ抹消やアカウント制裁が必要な場合に使用する機能です。アカウントを正常化した後は取り返しのつかないことに留意してください。
|
||||||
|
*/
|
||||||
|
"normalizeDescription": string;
|
||||||
|
/**
|
||||||
|
* 正規化機能を使用する
|
||||||
|
*/
|
||||||
|
"useNormalization": string;
|
||||||
|
/**
|
||||||
|
* データ収集とプライバシー設定
|
||||||
|
*/
|
||||||
|
"gtagConsentCustomize": string;
|
||||||
|
/**
|
||||||
|
* {host}が収集するデータの範囲をカスタマイズできます。
|
||||||
|
* ただし、認証機能、不正行為防止、その他のユーザー保護など、セキュリティに関連する情報の収集は無効化できません。
|
||||||
|
*/
|
||||||
|
"gtagConsentCustomizeDescription": ParameterizedString<"host">;
|
||||||
|
/**
|
||||||
|
* 統計情報の収集
|
||||||
|
*/
|
||||||
|
"gtagConsentAnalytics": string;
|
||||||
|
/**
|
||||||
|
* サイトの滞在時間など、分析に関連する情報の保存(Cookie など)を有効にします。
|
||||||
|
*/
|
||||||
|
"gtagConsentAnalyticsDescription": string;
|
||||||
|
/**
|
||||||
|
* 機能・設定の利用状況の収集
|
||||||
|
*/
|
||||||
|
"gtagConsentFunctionality": string;
|
||||||
|
/**
|
||||||
|
* 言語設定など、ウェブサイトやアプリの機能をサポートする情報の保存を有効にします。
|
||||||
|
*/
|
||||||
|
"gtagConsentFunctionalityDescription": string;
|
||||||
|
/**
|
||||||
|
* パーソナライズされた情報の収集
|
||||||
|
*/
|
||||||
|
"gtagConsentPersonalization": string;
|
||||||
|
/**
|
||||||
|
* おすすめの投稿など、パーソナライズに関連する情報の保存を有効にします。
|
||||||
|
*/
|
||||||
|
"gtagConsentPersonalizationDescription": string;
|
||||||
|
/**
|
||||||
|
* Misskeyの明日を作るために、
|
||||||
|
* データ収集にご協力ください!
|
||||||
|
*/
|
||||||
|
"helpUsImproveUserExperience": string;
|
||||||
|
/**
|
||||||
|
* {host}は[プライバシーポリシー]({privacyPolicyUrl})に基づき、サービスの提供・運営・ユーザー体験の向上のためにご利用中のIPアドレス、利用状況、デバイス情報等、個人情報を含む可能性のある情報を収集することがあります。
|
||||||
|
*
|
||||||
|
* 収集されたデータは今後の機能の開発、運営の方針の決定、サービスの改善点の特定に利用されます。
|
||||||
|
*/
|
||||||
|
"pleaseConsentToTracking": ParameterizedString<"host" | "privacyPolicyUrl">;
|
||||||
|
/**
|
||||||
|
* 必須項目のみ許可
|
||||||
|
*/
|
||||||
|
"consentEssential": string;
|
||||||
|
/**
|
||||||
|
* 全て許可
|
||||||
|
*/
|
||||||
|
"consentAll": string;
|
||||||
|
/**
|
||||||
|
* 選択した項目のみ許可
|
||||||
|
*/
|
||||||
|
"consentSelected": string;
|
||||||
|
/**
|
||||||
|
* このユーザーが送信する通知も一緒にミュートする
|
||||||
|
*/
|
||||||
|
"alsoMuteNotification": string;
|
||||||
|
/**
|
||||||
|
* 通知をミュートする
|
||||||
|
*/
|
||||||
|
"muteNotification": string;
|
||||||
|
/**
|
||||||
|
* 通知をミュート解除する
|
||||||
|
*/
|
||||||
|
"unmuteNotification": string;
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
/**
|
/**
|
||||||
* 遊び方
|
* 遊び方
|
||||||
@ -10283,6 +10479,10 @@ export interface Locale extends ILocale {
|
|||||||
* ユーザーのバナーを解除
|
* ユーザーのバナーを解除
|
||||||
*/
|
*/
|
||||||
"unsetUserBanner": string;
|
"unsetUserBanner": string;
|
||||||
|
/**
|
||||||
|
* 正 常 化
|
||||||
|
*/
|
||||||
|
"normalize": string;
|
||||||
};
|
};
|
||||||
"_fileViewer": {
|
"_fileViewer": {
|
||||||
/**
|
/**
|
||||||
@ -10880,6 +11080,28 @@ export interface Locale extends ILocale {
|
|||||||
*/
|
*/
|
||||||
"rolesDescription": string;
|
"rolesDescription": string;
|
||||||
};
|
};
|
||||||
|
"_selfXssPrevention": {
|
||||||
|
/**
|
||||||
|
* 警告
|
||||||
|
*/
|
||||||
|
"warning": string;
|
||||||
|
/**
|
||||||
|
* 「この画面に何か貼り付けろ」はすべて詐欺です。
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* ここに何かを貼り付けると、悪意のあるユーザーにアカウントを乗っ取られたり、個人情報を盗まれたりする可能性があります。
|
||||||
|
*/
|
||||||
|
"description1": string;
|
||||||
|
/**
|
||||||
|
* 貼り付けようとしているものが何なのかを正確に理解していない場合は、%c今すぐ作業を中止してこのウィンドウを閉じてください。
|
||||||
|
*/
|
||||||
|
"description2": string;
|
||||||
|
/**
|
||||||
|
* 詳しくはこちらをご確認ください。 {link}
|
||||||
|
*/
|
||||||
|
"description3": ParameterizedString<"link">;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
@ -1285,6 +1285,34 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
|
|||||||
noDescription: "説明文はありません"
|
noDescription: "説明文はありません"
|
||||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||||
inquiry: "お問い合わせ"
|
inquiry: "お問い合わせ"
|
||||||
|
tryAgain: "もう一度お試しください。"
|
||||||
|
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
|
||||||
|
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
|
||||||
|
createdLists: "作成したリスト"
|
||||||
|
createdAntennas: "作成したアンテナ"
|
||||||
|
fromX: "{x}から"
|
||||||
|
genEmbedCode: "埋め込みコードを生成"
|
||||||
|
noteOfThisUser: "このユーザーのノート一覧"
|
||||||
|
clipNoteLimitExceeded: "これ以上このクリップにノートを追加できません。"
|
||||||
|
performance: "パフォーマンス"
|
||||||
|
modified: "変更あり"
|
||||||
|
discard: "破棄"
|
||||||
|
thereAreNChanges: "{n}件の変更があります"
|
||||||
|
signinWithPasskey: "パスキーでログイン"
|
||||||
|
unknownWebAuthnKey: "登録されていないパスキーです。"
|
||||||
|
passkeyVerificationFailed: "パスキーの検証に失敗しました。"
|
||||||
|
passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。"
|
||||||
|
messageToFollower: "フォロワーへのメッセージ"
|
||||||
|
target: "対象"
|
||||||
|
testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>"
|
||||||
|
prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)"
|
||||||
|
prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。"
|
||||||
|
yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています"
|
||||||
|
yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。"
|
||||||
|
thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示にはログインが必要と設定されています"
|
||||||
|
lockdown: "ロックダウン"
|
||||||
|
pleaseSelectAccount: "アカウントを選択してください"
|
||||||
|
availableRoles: "利用可能なロール"
|
||||||
abuseReportCategory: "通報の種類"
|
abuseReportCategory: "通報の種類"
|
||||||
selectCategory: "カテゴリを選択"
|
selectCategory: "カテゴリを選択"
|
||||||
reportComplete: "通報完了"
|
reportComplete: "通報完了"
|
||||||
@ -1322,6 +1350,26 @@ dangerZone: "危険区域"
|
|||||||
dangerZoneDescription: "以下の機能を利用する際は、特にご注意ください。"
|
dangerZoneDescription: "以下の機能を利用する際は、特にご注意ください。"
|
||||||
checkedByHIBP: "パスワードの安全性に加え、HIBPを通じてパスワードの漏洩を検査します。"
|
checkedByHIBP: "パスワードの安全性に加え、HIBPを通じてパスワードの漏洩を検査します。"
|
||||||
changeUserName: "名前を変更"
|
changeUserName: "名前を変更"
|
||||||
|
normalize: "正常化"
|
||||||
|
normalizeConfirm: "正常化すると元に戻せなくなり、これはアカウントの削除と同様の効力を持ちます。実行しますか?"
|
||||||
|
normalizeDescription: "正常化は、ユーザーの一括的なデータ抹消やアカウント制裁が必要な場合に使用する機能です。アカウントを正常化した後は取り返しのつかないことに留意してください。"
|
||||||
|
useNormalization: "正規化機能を使用する"
|
||||||
|
gtagConsentCustomize: "データ収集とプライバシー設定"
|
||||||
|
gtagConsentCustomizeDescription: "{host}が収集するデータの範囲をカスタマイズできます。\nただし、認証機能、不正行為防止、その他のユーザー保護など、セキュリティに関連する情報の収集は無効化できません。"
|
||||||
|
gtagConsentAnalytics: "統計情報の収集"
|
||||||
|
gtagConsentAnalyticsDescription: "サイトの滞在時間など、分析に関連する情報の保存(Cookie など)を有効にします。"
|
||||||
|
gtagConsentFunctionality: "機能・設定の利用状況の収集"
|
||||||
|
gtagConsentFunctionalityDescription: "言語設定など、ウェブサイトやアプリの機能をサポートする情報の保存を有効にします。"
|
||||||
|
gtagConsentPersonalization: "パーソナライズされた情報の収集"
|
||||||
|
gtagConsentPersonalizationDescription: "おすすめの投稿など、パーソナライズに関連する情報の保存を有効にします。"
|
||||||
|
helpUsImproveUserExperience: "Misskeyの明日を作るために、\nデータ収集にご協力ください!"
|
||||||
|
pleaseConsentToTracking: "{host}は[プライバシーポリシー]({privacyPolicyUrl})に基づき、サービスの提供・運営・ユーザー体験の向上のためにご利用中のIPアドレス、利用状況、デバイス情報等、個人情報を含む可能性のある情報を収集することがあります。\n\n収集されたデータは今後の機能の開発、運営の方針の決定、サービスの改善点の特定に利用されます。"
|
||||||
|
consentEssential: "必須項目のみ許可"
|
||||||
|
consentAll: "全て許可"
|
||||||
|
consentSelected: "選択した項目のみ許可"
|
||||||
|
alsoMuteNotification: "このユーザーが送信する通知も一緒にミュートする"
|
||||||
|
muteNotification: "通知をミュートする"
|
||||||
|
unmuteNotification: "通知をミュート解除する"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
@ -2717,6 +2765,7 @@ _moderationLogTypes:
|
|||||||
deleteAvatarDecoration: "アイコンデコレーションを削除"
|
deleteAvatarDecoration: "アイコンデコレーションを削除"
|
||||||
unsetUserAvatar: "ユーザーのアイコンを解除"
|
unsetUserAvatar: "ユーザーのアイコンを解除"
|
||||||
unsetUserBanner: "ユーザーのバナーを解除"
|
unsetUserBanner: "ユーザーのバナーを解除"
|
||||||
|
normalize: "正 常 化"
|
||||||
|
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "ファイルの詳細"
|
title: "ファイルの詳細"
|
||||||
@ -2890,3 +2939,10 @@ _hideSensitiveInformation:
|
|||||||
roles: "ロール"
|
roles: "ロール"
|
||||||
rolesUse: "割り当てられたロールを非表示にする"
|
rolesUse: "割り当てられたロールを非表示にする"
|
||||||
rolesDescription: "このオプションを有効にすると、ユーザープロファイルにすべてのロールリストが表示されなくなります。"
|
rolesDescription: "このオプションを有効にすると、ユーザープロファイルにすべてのロールリストが表示されなくなります。"
|
||||||
|
|
||||||
|
_selfXssPrevention:
|
||||||
|
warning: "警告"
|
||||||
|
title: "「この画面に何か貼り付けろ」はすべて詐欺です。"
|
||||||
|
description1: "ここに何かを貼り付けると、悪意のあるユーザーにアカウントを乗っ取られたり、個人情報を盗まれたりする可能性があります。"
|
||||||
|
description2: "貼り付けようとしているものが何なのかを正確に理解していない場合は、%c今すぐ作業を中止してこのウィンドウを閉じてください。"
|
||||||
|
description3: "詳しくはこちらをご確認ください。 {link}"
|
||||||
|
@ -610,12 +610,12 @@ removeAllFollowingDescription: "{host} 서버의 모든 팔로잉을 해제합
|
|||||||
userSuspended: "이 계정은 정지된 상태입니다."
|
userSuspended: "이 계정은 정지된 상태입니다."
|
||||||
userLimited: "이 계정은 제한된 상태입니다."
|
userLimited: "이 계정은 제한된 상태입니다."
|
||||||
userSilenced: "이 계정은 사일런스된 상태입니다."
|
userSilenced: "이 계정은 사일런스된 상태입니다."
|
||||||
yourAccountSuspendedTitle: "계정이 정지되었습니다"
|
yourAccountSuspendedTitle: "당신의 계정이 정지되었습니다"
|
||||||
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
|
yourAccountSuspendedDescription: "당신의 계정이 규정 위반 또는 관리자 재량에 의해 정지되었습니다. 잘못되었다고 생각할 경우 관리자에게 문의해주십시오."
|
||||||
tokenRevoked: "유효하지 않은 토큰입니다"
|
tokenRevoked: "유효하지 않은 토큰입니다"
|
||||||
tokenRevokedDescription: "로그인 토큰이 비활성화되었습니다. 다시 로그인하여 주십시오."
|
tokenRevokedDescription: "다른 곳에서 비밀번호를 변경했거나, 다른 모든 세션을 종료했습니다. 계속하려면 다시 로그인하세요."
|
||||||
accountDeleted: "계정이 정지되었습니다"
|
accountDeleted: "당신의 계정이 정지되었습니다"
|
||||||
accountDeletedDescription: "이 계정이 삭제되었습니다."
|
accountDeletedDescription: "당신의 계정이 일정 기간 이상 비활동 또는 관리자 재량에 의해 삭제되었습니다."
|
||||||
menu: "메뉴"
|
menu: "메뉴"
|
||||||
divider: "구분선"
|
divider: "구분선"
|
||||||
addItem: "항목 추가"
|
addItem: "항목 추가"
|
||||||
@ -1271,6 +1271,35 @@ useTotp: "일회용 비밀번호 사용"
|
|||||||
useBackupCode: "백업 코드 사용"
|
useBackupCode: "백업 코드 사용"
|
||||||
launchApp: "앱 실행"
|
launchApp: "앱 실행"
|
||||||
useNativeUIForVideoAudioPlayer: "브라우저 UI에서 미디어 재생"
|
useNativeUIForVideoAudioPlayer: "브라우저 UI에서 미디어 재생"
|
||||||
|
keepOriginalFilename: "원본 파일 이름을 유지"
|
||||||
|
keepOriginalFilenameDescription: "이 설정을 끄면 업로드를 할 때 파일 이름이 자동으로 무작위 문자열로 바뀝니다."
|
||||||
|
noDescription: "설명문이 없습니다"
|
||||||
|
inquiry: "문의하기"
|
||||||
|
tryAgain: "다시 시도해 주세요."
|
||||||
|
confirmWhenRevealingSensitiveMedia: "민감한 미디어를 열 때 두 번 확인"
|
||||||
|
sensitiveMediaRevealConfirm: "민감한 미디어입니다. 표시할까요?"
|
||||||
|
createdLists: "만든 리스트"
|
||||||
|
createdAntennas: "만든 안테나"
|
||||||
|
fromX: "{x}부터"
|
||||||
|
genEmbedCode: "임베디드 코드 만들기"
|
||||||
|
noteOfThisUser: "이 유저의 노트 목록"
|
||||||
|
clipNoteLimitExceeded: "더 이상 이 클립에 노트를 추가 할 수 없습니다."
|
||||||
|
performance: "퍼포먼스"
|
||||||
|
modified: "변경 있음"
|
||||||
|
discard: "파기"
|
||||||
|
thereAreNChanges: "{n}건 변경이 있습니다."
|
||||||
|
signinWithPasskey: "패스키로 로그인"
|
||||||
|
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
|
||||||
|
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
|
||||||
|
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
|
||||||
|
messageToFollower: "팔로워에 보낼 메시지"
|
||||||
|
target: "대상"
|
||||||
|
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
|
||||||
|
prohibitedWordsForNameOfUser: "금지 단어 (사용자 이름)"
|
||||||
|
prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 사용자 이름에 있는 경우, 일반 사용자는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 사용자는 제한 대상에서 제외됩니다."
|
||||||
|
yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다."
|
||||||
|
yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요."
|
||||||
|
lockdown: "잠금"
|
||||||
here: "여기"
|
here: "여기"
|
||||||
alwaysConfirmFollow: "팔로우할 때 항상 확인하기"
|
alwaysConfirmFollow: "팔로우할 때 항상 확인하기"
|
||||||
credits: "엔딩 크레딧"
|
credits: "엔딩 크레딧"
|
||||||
@ -1310,6 +1339,27 @@ dangerZoneDescription: "함부로 실행하면 어딘가 고장날 수 있는
|
|||||||
checkedByHIBP: "비밀번호의 안전성과 더불어, HIBP를 통해 비밀번호 유출을 검사합니다."
|
checkedByHIBP: "비밀번호의 안전성과 더불어, HIBP를 통해 비밀번호 유출을 검사합니다."
|
||||||
changeUserName: "이름 변경"
|
changeUserName: "이름 변경"
|
||||||
pleaseSelectAccount: "사용할 계정을 선택해주십시오"
|
pleaseSelectAccount: "사용할 계정을 선택해주십시오"
|
||||||
|
normalize: "정상화"
|
||||||
|
normalizeConfirm: "정상화 이후에는 계정을 되돌릴 수 없게 됩니다. 실행하시겠습니까?"
|
||||||
|
normalizeDescription: "정상화는 유저의 일괄적인 데이터 말소 및 계정 정지를 위한 기능으로, 계정 삭제와 비슷한 효과를 가지며, 실행 후에는 모든 신고가 닫히게 됩니다. 기능을 실행하고 나면 되돌릴 수 없는 점을 유의하시기 바랍니다."
|
||||||
|
useNormalization: "정상화 메뉴를 표시하기"
|
||||||
|
gtagConsentCustomize: "데이터 수집 및 개인정보 설정"
|
||||||
|
gtagConsentCustomizeDescription: "{host}에서 수집하는 데이터 범위를 사용자 지정할 수 있습니다.\n다만, 인증 기능, 부정 행위 방지, 기타 사용자 보호 등 보안과 관련된 정보 수집은 비활성화할 수 없습니다."
|
||||||
|
gtagConsentAnalytics: "통계 정보 수집"
|
||||||
|
gtagConsentAnalyticsDescription: "사이트 체류 시간 등 분석 관련 정보 저장(쿠키 등)을 활성화합니다."
|
||||||
|
gtagConsentFunctionality: "기능 및 설정 사용 정보 수집"
|
||||||
|
gtagConsentFunctionalityDescription: "언어 설정 등 웹사이트나 앱의 기능을 지원하는 정보 저장을 활성화합니다."
|
||||||
|
gtagConsentPersonalization: "개인 맞춤형 정보 수집"
|
||||||
|
gtagConsentPersonalizationDescription: "추천 게시물 등 개인화 관련 정보 저장을 활성화합니다."
|
||||||
|
helpUsImproveUserExperience: "Misskey의 미래를 위해,\n데이터 수집에 협조해 주세요!"
|
||||||
|
pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUrl})에 따라 서비스 제공, 운영, 사용자 경험 향상을 위해 사용 중인 IP 주소, 이용 현황, 디바이스 정보 등 개인 정보를 포함할 수 있는 정보를 수집할 수 있습니다.\n\n수집된 데이터는 향후 기능 개발, 운영 방침 결정, 서비스 개선점 파악에 활용됩니다."
|
||||||
|
consentEssential: "필수 항목만 허용"
|
||||||
|
consentAll: "모두 허용"
|
||||||
|
consentSelected: "선택한 항목만 허용"
|
||||||
|
alsoMuteNotification: "이 유저가 보내는 알림도 같이 뮤트하기"
|
||||||
|
muteNotification: "알림을 뮤트하기"
|
||||||
|
unmuteNotification: "알림 뮤트를 해제하기"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "설명"
|
howToPlay: "설명"
|
||||||
hold: "홀드"
|
hold: "홀드"
|
||||||
@ -2609,6 +2659,7 @@ _moderationLogTypes:
|
|||||||
deleteAvatarDecoration: "아바타 장식 삭제"
|
deleteAvatarDecoration: "아바타 장식 삭제"
|
||||||
unsetUserAvatar: "유저 아바타 제거"
|
unsetUserAvatar: "유저 아바타 제거"
|
||||||
unsetUserBanner: "유저 배너 제거"
|
unsetUserBanner: "유저 배너 제거"
|
||||||
|
normalize: "정 상 화"
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "파일 상세"
|
title: "파일 상세"
|
||||||
type: "파일 유형"
|
type: "파일 유형"
|
||||||
|
36
package.json
36
package.json
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.5.0-oscar.17a",
|
"version": "2024.5.0-oscar.18",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.psec.dev/oscar-surf/misskey.git"
|
"url": "https://git.psec.dev/oscar-surf/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.7.1",
|
"packageManager": "pnpm@9.12.2",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
@ -46,31 +46,35 @@
|
|||||||
"cleanall": "pnpm clean-all"
|
"cleanall": "pnpm clean-all"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@tensorflow/tfjs-core": "4.20.0",
|
"@tensorflow/tfjs-core": "4.22.0",
|
||||||
"chokidar": "3.6.0",
|
"chokidar": "4.0.1",
|
||||||
"esbuild": "0.23.0",
|
"cookie": "1.0.1",
|
||||||
|
"cookie-signature": "1.2.2",
|
||||||
|
"debug": "4.3.7",
|
||||||
|
"esbuild": "0.24.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"sharp": "0.33.4"
|
"sharp": "0.33.5",
|
||||||
|
"web-streams-polyfill": "4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano": "7.0.5",
|
"cssnano": "7.0.6",
|
||||||
"execa": "9.3.0",
|
"execa": "9.5.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.41",
|
"postcss": "8.4.47",
|
||||||
"terser": "5.31.5",
|
"terser": "5.36.0",
|
||||||
"typescript": "5.5.4"
|
"typescript": "5.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.2.0",
|
"@types/node": "22.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.13.2",
|
"cypress": "13.15.1",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.1",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"start-server-and-test": "2.0.5"
|
"start-server-and-test": "2.0.8"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tensorflow/tfjs-core": "4.19.0"
|
"@tensorflow/tfjs-core": "4.22.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
export class GoogleAnalyticsId1730629332694 {
|
||||||
|
name = 'GoogleAnalyticsId1730629332694'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsId" character varying(32)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsId"`);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
export class NotificationMuting1730885274028 {
|
||||||
|
name = 'NotificationMuting1730885274028'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "muting" ADD "withNotification" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "withNotification"`);
|
||||||
|
}
|
||||||
|
}
|
@ -34,16 +34,16 @@
|
|||||||
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
|
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-darwin-arm64": "1.7.39",
|
"@swc/core-darwin-arm64": "1.8.0",
|
||||||
"@swc/core-darwin-x64": "1.7.39",
|
"@swc/core-darwin-x64": "1.8.0",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.7.39",
|
"@swc/core-linux-arm-gnueabihf": "1.8.0",
|
||||||
"@swc/core-linux-arm64-gnu": "1.7.39",
|
"@swc/core-linux-arm64-gnu": "1.8.0",
|
||||||
"@swc/core-linux-arm64-musl": "1.7.39",
|
"@swc/core-linux-arm64-musl": "1.8.0",
|
||||||
"@swc/core-linux-x64-gnu": "1.7.39",
|
"@swc/core-linux-x64-gnu": "1.8.0",
|
||||||
"@swc/core-linux-x64-musl": "1.7.39",
|
"@swc/core-linux-x64-musl": "1.8.0",
|
||||||
"@swc/core-win32-arm64-msvc": "1.7.39",
|
"@swc/core-win32-arm64-msvc": "1.8.0",
|
||||||
"@swc/core-win32-ia32-msvc": "1.7.39",
|
"@swc/core-win32-ia32-msvc": "1.8.0",
|
||||||
"@swc/core-win32-x64-msvc": "1.7.39",
|
"@swc/core-win32-x64-msvc": "1.8.0",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.0.8",
|
"bufferutil": "4.0.8",
|
||||||
@ -60,15 +60,15 @@
|
|||||||
"slacc-linux-x64-musl": "0.0.10",
|
"slacc-linux-x64-musl": "0.0.10",
|
||||||
"slacc-win32-arm64-msvc": "0.0.10",
|
"slacc-win32-arm64-msvc": "0.0.10",
|
||||||
"slacc-win32-x64-msvc": "0.0.10",
|
"slacc-win32-x64-msvc": "0.0.10",
|
||||||
"utf-8-validate": "6.0.4"
|
"utf-8-validate": "6.0.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authenio/samlify-node-xmllint": "2.0.0",
|
"@authenio/samlify-node-xmllint": "2.0.0",
|
||||||
"@aws-sdk/client-s3": "3.676.0",
|
"@aws-sdk/client-s3": "3.685.0",
|
||||||
"@aws-sdk/lib-storage": "3.676.0",
|
"@aws-sdk/lib-storage": "3.685.0",
|
||||||
"@bull-board/api": "6.2.4",
|
"@bull-board/api": "6.3.3",
|
||||||
"@bull-board/fastify": "6.2.4",
|
"@bull-board/fastify": "6.3.3",
|
||||||
"@bull-board/ui": "6.2.4",
|
"@bull-board/ui": "6.3.3",
|
||||||
"@discordapp/twemoji": "15.1.0",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@elastic/elasticsearch": "8.15.1",
|
"@elastic/elasticsearch": "8.15.1",
|
||||||
"@fastify/accepts": "5.0.1",
|
"@fastify/accepts": "5.0.1",
|
||||||
@ -82,16 +82,16 @@
|
|||||||
"@fastify/view": "10.0.1",
|
"@fastify/view": "10.0.1",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.1.0",
|
"@misskey-dev/summaly": "5.1.0",
|
||||||
"@napi-rs/canvas": "0.1.58",
|
"@napi-rs/canvas": "0.1.59",
|
||||||
"@nestjs/common": "10.4.5",
|
"@nestjs/common": "10.4.7",
|
||||||
"@nestjs/core": "10.4.5",
|
"@nestjs/core": "10.4.7",
|
||||||
"@nestjs/testing": "10.4.5",
|
"@nestjs/testing": "10.4.7",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@simplewebauthn/server": "11.0.0",
|
"@simplewebauthn/server": "11.0.0",
|
||||||
"@sinonjs/fake-timers": "11.3.1",
|
"@sinonjs/fake-timers": "11.3.1",
|
||||||
"@smithy/node-http-handler": "3.2.5",
|
"@smithy/node-http-handler": "3.2.5",
|
||||||
"@swc/cli": "0.4.0",
|
"@swc/cli": "0.5.0",
|
||||||
"@swc/core": "1.7.39",
|
"@swc/core": "1.8.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.17.1",
|
"ajv": "8.17.1",
|
||||||
@ -100,9 +100,9 @@
|
|||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"bullmq": "5.21.2",
|
"bullmq": "5.24.0",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "10.0.2",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
"chalk-template": "1.1.0",
|
"chalk-template": "1.1.0",
|
||||||
"chokidar": "4.0.1",
|
"chokidar": "4.0.1",
|
||||||
@ -118,8 +118,8 @@
|
|||||||
"file-type": "19.6.0",
|
"file-type": "19.6.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"got": "14.4.3",
|
"got": "14.4.4",
|
||||||
"happy-dom": "15.7.4",
|
"happy-dom": "15.9.0",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
"http-link-header": "1.1.3",
|
"http-link-header": "1.1.3",
|
||||||
@ -133,26 +133,26 @@
|
|||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.2",
|
"jsonld": "8.3.2",
|
||||||
"jsrsasign": "11.1.0",
|
"jsrsasign": "11.1.0",
|
||||||
"meilisearch": "0.44.1",
|
"meilisearch": "0.45.0",
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"microformats-parser": "2.0.2",
|
"microformats-parser": "2.0.2",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"ms": "3.0.0-canary.1",
|
"ms": "3.0.0-canary.1",
|
||||||
"nanoid": "5.0.7",
|
"nanoid": "5.0.8",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"nodemailer": "6.9.15",
|
"nodemailer": "6.9.16",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "0.10.0",
|
"oauth": "0.10.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
"oauth2orize-pkce": "0.1.2",
|
"oauth2orize-pkce": "0.1.2",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.3.4",
|
"otpauth": "9.3.4",
|
||||||
"parse5": "7.2.0",
|
"parse5": "7.2.1",
|
||||||
"pg": "8.13.0",
|
"pg": "8.13.1",
|
||||||
"pino": "9.5.0",
|
"pino": "9.5.0",
|
||||||
"pino-pretty": "11.3.0",
|
"pino-pretty": "11.3.0",
|
||||||
"pkce-challenge": "4.1.0",
|
"pkce-challenge": "4.1.0",
|
||||||
@ -193,26 +193,26 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@nestjs/platform-express": "10.4.5",
|
"@nestjs/platform-express": "10.4.7",
|
||||||
"@simplewebauthn/types": "11.0.0",
|
"@simplewebauthn/types": "11.0.0",
|
||||||
"@swc/jest": "0.2.36",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
"@types/archiver": "6.0.2",
|
"@types/archiver": "6.0.3",
|
||||||
"@types/bcryptjs": "2.4.6",
|
"@types/bcryptjs": "2.4.6",
|
||||||
"@types/body-parser": "1.19.5",
|
"@types/body-parser": "1.19.5",
|
||||||
"@types/color-convert": "2.0.4",
|
"@types/color-convert": "2.0.4",
|
||||||
"@types/content-disposition": "0.5.8",
|
"@types/content-disposition": "0.5.8",
|
||||||
"@types/fluent-ffmpeg": "2.1.26",
|
"@types/fluent-ffmpeg": "2.1.27",
|
||||||
"@types/htmlescape": "^1.1.3",
|
"@types/htmlescape": "^1.1.3",
|
||||||
"@types/http-link-header": "1.0.7",
|
"@types/http-link-header": "1.0.7",
|
||||||
"@types/jest": "29.5.13",
|
"@types/jest": "29.5.14",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsdom": "21.1.7",
|
"@types/jsdom": "21.1.7",
|
||||||
"@types/jsonld": "1.5.15",
|
"@types/jsonld": "1.5.15",
|
||||||
"@types/jsrsasign": "10.5.14",
|
"@types/jsrsasign": "10.5.14",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "22.7.8",
|
"@types/node": "22.9.0",
|
||||||
"@types/node-forge": "1.3.11",
|
"@types/node-forge": "1.3.11",
|
||||||
"@types/nodemailer": "6.4.16",
|
"@types/nodemailer": "6.4.16",
|
||||||
"@types/oauth": "0.9.6",
|
"@types/oauth": "0.9.6",
|
||||||
@ -233,14 +233,14 @@
|
|||||||
"@types/tmp": "0.2.6",
|
"@types/tmp": "0.2.6",
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@types/ws": "8.5.12",
|
"@types/ws": "8.5.13",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"execa": "9.4.1",
|
"execa": "9.5.1",
|
||||||
"fkill": "^9.0.0",
|
"fkill": "^9.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
|
@ -4,10 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LoggerService } from '@nestjs/common';
|
import { LoggerService } from '@nestjs/common';
|
||||||
import Logger from '@/logger.js';
|
import { coreLogger } from '@/logger.js';
|
||||||
|
|
||||||
const logger = new Logger('core', 'cyan');
|
const nestLogger = coreLogger.createSubLogger('nest', 'green', false);
|
||||||
const nestLogger = logger.createSubLogger('nest', 'green', false);
|
|
||||||
|
|
||||||
export class NestLogger implements LoggerService {
|
export class NestLogger implements LoggerService {
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,7 @@ import { EventEmitter } from 'node:events';
|
|||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
import Logger from '@/logger.js';
|
import { coreLogger } from '@/logger.js';
|
||||||
import { envOption } from '../env.js';
|
import { envOption } from '../env.js';
|
||||||
import { masterMain } from './master.js';
|
import { masterMain } from './master.js';
|
||||||
import { workerMain } from './worker.js';
|
import { workerMain } from './worker.js';
|
||||||
@ -24,8 +24,7 @@ process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
|
|||||||
Error.stackTraceLimit = Infinity;
|
Error.stackTraceLimit = Infinity;
|
||||||
EventEmitter.defaultMaxListeners = 128;
|
EventEmitter.defaultMaxListeners = 128;
|
||||||
|
|
||||||
const logger = new Logger('core', 'cyan');
|
const clusterLogger = coreLogger.createSubLogger('cluster', 'orange', false);
|
||||||
const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
|
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
|
||||||
//#region Events
|
//#region Events
|
||||||
@ -53,12 +52,12 @@ if (cluster.isPrimary && !envOption.disableClustering) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
logger.warn(chalk.yellow('Process received SIGINT'));
|
coreLogger.warn(chalk.yellow('Process received SIGINT'));
|
||||||
isShuttingDown = true;
|
isShuttingDown = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
logger.warn(chalk.yellow('Process received SIGTERM'));
|
coreLogger.warn(chalk.yellow('Process received SIGTERM'));
|
||||||
isShuttingDown = true;
|
isShuttingDown = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -71,18 +70,18 @@ if (!envOption.quiet) {
|
|||||||
// Display detail of uncaught exception
|
// Display detail of uncaught exception
|
||||||
process.on('uncaughtException', err => {
|
process.on('uncaughtException', err => {
|
||||||
try {
|
try {
|
||||||
logger.error(`Uncaught exception: ${err.message}`, { error: err });
|
coreLogger.error(`Uncaught exception: ${err.message}`, { error: err });
|
||||||
} catch { }
|
} catch { }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dying away...
|
// Dying away...
|
||||||
process.on('exit', code => {
|
process.on('exit', code => {
|
||||||
logger.warn(chalk.yellow(`The process is going to exit with code ${code}`));
|
coreLogger.warn(chalk.yellow(`The process is going to exit with code ${code}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('warning', warning => {
|
process.on('warning', warning => {
|
||||||
if ((warning as never)['code'] !== 'MISSKEY_SHUTDOWN') return;
|
if ((warning as never)['code'] !== 'MISSKEY_SHUTDOWN') return;
|
||||||
logger.warn(chalk.yellow(`${warning.message}: ${(warning as never)['detail']}`));
|
coreLogger.warn(chalk.yellow(`${warning.message}: ${(warning as never)['detail']}`));
|
||||||
for (const id in cluster.workers) cluster.workers[id]?.process.kill('SIGTERM');
|
for (const id in cluster.workers) cluster.workers[id]?.process.kill('SIGTERM');
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ import * as os from 'node:os';
|
|||||||
import cluster from 'node:cluster';
|
import cluster from 'node:cluster';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import chalkTemplate from 'chalk-template';
|
import chalkTemplate from 'chalk-template';
|
||||||
import Logger from '@/logger.js';
|
import { coreLogger } from '@/logger.js';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { showMachineInfo } from '@/misc/show-machine-info.js';
|
import { showMachineInfo } from '@/misc/show-machine-info.js';
|
||||||
@ -22,8 +22,7 @@ const _dirname = dirname(_filename);
|
|||||||
|
|
||||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
||||||
|
|
||||||
const logger = new Logger('core', 'cyan');
|
const bootLogger = coreLogger.createSubLogger('boot', 'magenta', false);
|
||||||
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
|
||||||
|
|
||||||
const themeColor = chalk.hex('#86b300');
|
const themeColor = chalk.hex('#86b300');
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ export class CacheService implements OnApplicationShutdown {
|
|||||||
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
||||||
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
||||||
public userProfileCache: RedisKVCache<MiUserProfile>;
|
public userProfileCache: RedisKVCache<MiUserProfile>;
|
||||||
|
public userMutingsWithNotificationCache: RedisKVCache<Set<string>>;
|
||||||
|
public userMutingsWithoutNotificationCache: RedisKVCache<Set<string>>;
|
||||||
public userMutingsCache: RedisKVCache<Set<string>>;
|
public userMutingsCache: RedisKVCache<Set<string>>;
|
||||||
public userBlockingCache: RedisKVCache<Set<string>>;
|
public userBlockingCache: RedisKVCache<Set<string>>;
|
||||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||||
@ -77,6 +79,22 @@ export class CacheService implements OnApplicationShutdown {
|
|||||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.userMutingsWithoutNotificationCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key, withNotification: false }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||||
|
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||||
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.userMutingsWithNotificationCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key, withNotification: true }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||||
|
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||||
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
|
||||||
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
|
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
|
||||||
lifetime: 1000 * 60 * 30, // 30m
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
memoryCacheLifetime: 1000 * 60, // 1m
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
@ -188,6 +206,8 @@ export class CacheService implements OnApplicationShutdown {
|
|||||||
this.uriPersonCache.dispose();
|
this.uriPersonCache.dispose();
|
||||||
this.userProfileCache.dispose();
|
this.userProfileCache.dispose();
|
||||||
this.userMutingsCache.dispose();
|
this.userMutingsCache.dispose();
|
||||||
|
this.userMutingsWithoutNotificationCache.dispose();
|
||||||
|
this.userMutingsWithNotificationCache.dispose();
|
||||||
this.userBlockingCache.dispose();
|
this.userBlockingCache.dispose();
|
||||||
this.userBlockedCache.dispose();
|
this.userBlockedCache.dispose();
|
||||||
this.renoteMutingsCache.dispose();
|
this.renoteMutingsCache.dispose();
|
||||||
|
@ -43,6 +43,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||||||
import { correctFilename } from '@/misc/correct-filename.js';
|
import { correctFilename } from '@/misc/correct-filename.js';
|
||||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
|
||||||
type AddFileArgs = {
|
type AddFileArgs = {
|
||||||
/** User who wish to add file */
|
/** User who wish to add file */
|
||||||
@ -123,12 +124,13 @@ export class DriveService {
|
|||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
private loggerService: LoggerService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
private driveChart: DriveChart,
|
private driveChart: DriveChart,
|
||||||
private perUserDriveChart: PerUserDriveChart,
|
private perUserDriveChart: PerUserDriveChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
) {
|
) {
|
||||||
const logger = new Logger('drive', 'blue');
|
const logger = this.loggerService.getLogger('drive', 'blue');
|
||||||
this.registerLogger = logger.createSubLogger('register', 'yellow');
|
this.registerLogger = logger.createSubLogger('register', 'yellow');
|
||||||
this.downloaderLogger = logger.createSubLogger('downloader');
|
this.downloaderLogger = logger.createSubLogger('downloader');
|
||||||
this.deleteLogger = logger.createSubLogger('delete');
|
this.deleteLogger = logger.createSubLogger('delete');
|
||||||
@ -503,6 +505,12 @@ export class DriveService {
|
|||||||
|
|
||||||
if (much) {
|
if (much) {
|
||||||
this.registerLogger.info(`file with same hash is found: ${much.id}`);
|
this.registerLogger.info(`file with same hash is found: ${much.id}`);
|
||||||
|
if (sensitive && !much.isSensitive) {
|
||||||
|
// The file is federated as sensitive for this time, but was federated as non-sensitive before.
|
||||||
|
// Therefore, update the file to sensitive.
|
||||||
|
await this.driveFilesRepository.update({ id: much.id }, { isSensitive: true });
|
||||||
|
much.isSensitive = true;
|
||||||
|
}
|
||||||
return much;
|
return much;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ export class FetchInstanceMetadataService {
|
|||||||
throw err.statusCode ?? err.message;
|
throw err.statusCode ?? err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
|
this.logger.succ(`Successfully fetched nodeinfo of ${instance.host}`);
|
||||||
|
|
||||||
return info as NodeInfo;
|
return info as NodeInfo;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import Logger from '@/logger.js';
|
import { rootLogger } from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { KEYWORD } from 'color-convert/conversions.js';
|
import type { KEYWORD } from 'color-convert/conversions.js';
|
||||||
|
|
||||||
@ -16,6 +16,6 @@ export class LoggerService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
|
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
|
||||||
return new Logger(domain);
|
return rootLogger.createSubLogger(domain, color, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -410,7 +410,7 @@ export class MfmService {
|
|||||||
mention: (node) => {
|
mention: (node) => {
|
||||||
const a = doc.createElement('a');
|
const a = doc.createElement('a');
|
||||||
const { username, host, acct } = node.props;
|
const { username, host, acct } = node.props;
|
||||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase());
|
||||||
a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
|
a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
|
||||||
a.className = 'u-url mention';
|
a.className = 'u-url mention';
|
||||||
a.textContent = acct;
|
a.textContent = acct;
|
||||||
|
@ -257,7 +257,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
const policies = await this.roleService.getUserPolicies(user.id);
|
const policies = await this.roleService.getUserPolicies(user.id);
|
||||||
|
|
||||||
if (!policies.canCreateContent) {
|
if (!policies.canCreateContent) {
|
||||||
this.logger.error('Request rejected because user has no permission to create content', { user: user.id, note: data });
|
this.logger.error('Request rejected because user has no permission to create content', { userId: user.id, note: data });
|
||||||
throw new IdentifiableError('5b1c2b67-50a6-4a8a-a59c-0ede40890de3', 'User has no permission to create content.');
|
throw new IdentifiableError('5b1c2b67-50a6-4a8a-a59c-0ede40890de3', 'User has no permission to create content.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
const sensitiveWords = meta.sensitiveWords;
|
const sensitiveWords = meta.sensitiveWords;
|
||||||
if (this.utilityService.isKeyWordIncluded(data.cw ?? this.utilityService.concatNoteContentsForKeyWordCheck({ text: data.text, pollChoices: data.poll?.choices }), sensitiveWords)) {
|
if (this.utilityService.isKeyWordIncluded(data.cw ?? this.utilityService.concatNoteContentsForKeyWordCheck({ text: data.text, pollChoices: data.poll?.choices }), sensitiveWords)) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
this.logger.warn('Visibility changed to home because sensitive words are included', { user: user.id, note: data });
|
this.logger.warn('Visibility changed to home because sensitive words are included', { userId: user.id, note: data });
|
||||||
} else if (!policies.canPublicNote) {
|
} else if (!policies.canPublicNote) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
}
|
}
|
||||||
@ -281,7 +281,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (hasProhibitedWords) {
|
if (hasProhibitedWords) {
|
||||||
this.logger.error('Request rejected because prohibited words are included', { user: user.id, note: data });
|
this.logger.error('Request rejected because prohibited words are included', { userId: user.id, note: data });
|
||||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Notes including prohibited words are not allowed.');
|
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Notes including prohibited words are not allowed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,7 +384,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
if (process.env.MISSKEY_BLOCK_MENTIONS_FROM_UNFAMILIAR_REMOTE_USERS === 'true' && user.host !== null && willCauseNotification) {
|
if (process.env.MISSKEY_BLOCK_MENTIONS_FROM_UNFAMILIAR_REMOTE_USERS === 'true' && user.host !== null && willCauseNotification) {
|
||||||
const userEntity = await this.usersRepository.findOneBy({ id: user.id });
|
const userEntity = await this.usersRepository.findOneBy({ id: user.id });
|
||||||
if ((userEntity?.followersCount ?? 0) === 0) {
|
if ((userEntity?.followersCount ?? 0) === 0) {
|
||||||
this.logger.error('Request rejected because user has no local followers', { user: user.id, note: data });
|
this.logger.error('Request rejected because user has no local followers', { userId: user.id, note: data });
|
||||||
throw new IdentifiableError('e11b3a16-f543-4885-8eb1-66cad131dbfd', 'Notes including mentions, replies, or renotes from remote users are not allowed until user has at least one local follower.');
|
throw new IdentifiableError('e11b3a16-f543-4885-8eb1-66cad131dbfd', 'Notes including mentions, replies, or renotes from remote users are not allowed until user has at least one local follower.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,7 +396,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
|| (data.visibility === 'specified' && data.visibleUsers?.some(u => u.id !== user.id))
|
|| (data.visibility === 'specified' && data.visibleUsers?.some(u => u.id !== user.id))
|
||||||
|| (this.isQuote(data) && data.renote.userId !== user.id)
|
|| (this.isQuote(data) && data.renote.userId !== user.id)
|
||||||
) {
|
) {
|
||||||
this.logger.error('Request rejected because user has no permission to initiate conversation', { user: user.id, note: data });
|
this.logger.error('Request rejected because user has no permission to initiate conversation', { userId: user.id, note: data });
|
||||||
throw new IdentifiableError('332dd91b-6a00-430a-ac39-620cf60ad34b', 'Notes including mentions, replies, or renotes are not allowed.');
|
throw new IdentifiableError('332dd91b-6a00-430a-ac39-620cf60ad34b', 'Notes including mentions, replies, or renotes are not allowed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ export class NotificationService implements OnApplicationShutdown {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
|
const mutings = await this.cacheService.userMutingsWithNotificationCache.fetch(notifieeId);
|
||||||
if (mutings.has(notifierId)) {
|
if (mutings.has(notifierId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ export class UserBlockingService implements OnModuleInit {
|
|||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.loggerService.getLogger('user-block');
|
this.logger = this.loggerService.getLogger('user:block');
|
||||||
}
|
}
|
||||||
|
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
|
@ -30,9 +30,8 @@ import { AccountMoveService } from '@/core/AccountMoveService.js';
|
|||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||||
import type { ThinUser } from '@/queue/types.js';
|
import type { ThinUser } from '@/queue/types.js';
|
||||||
import Logger from '../logger.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
import Logger from '@/logger.js';
|
||||||
const logger = new Logger('following/create');
|
|
||||||
|
|
||||||
type Local = MiLocalUser | {
|
type Local = MiLocalUser | {
|
||||||
id: MiLocalUser['id'];
|
id: MiLocalUser['id'];
|
||||||
@ -50,6 +49,7 @@ type Both = Local | Remote;
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserFollowingService implements OnModuleInit {
|
export class UserFollowingService implements OnModuleInit {
|
||||||
private userBlockingService: UserBlockingService;
|
private userBlockingService: UserBlockingService;
|
||||||
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
@ -73,6 +73,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
|
private loggerService: LoggerService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
@ -88,6 +89,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||||||
private perUserFollowingChart: PerUserFollowingChart,
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
) {
|
) {
|
||||||
|
this.logger = this.loggerService.getLogger('user:following');
|
||||||
}
|
}
|
||||||
|
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
@ -255,7 +257,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||||||
followeeSharedInbox: this.userEntityService.isRemoteUser(followee) ? followee.sharedInbox : null,
|
followeeSharedInbox: this.userEntityService.isRemoteUser(followee) ? followee.sharedInbox : null,
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (isDuplicateKeyValueError(err) && this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (isDuplicateKeyValueError(err) && this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
logger.info(`Insert duplicated ignore. ${follower.id} => ${followee.id}`);
|
this.logger.info(`Insert duplicated ignore. ${follower.id} => ${followee.id}`);
|
||||||
alreadyFollowed = true;
|
alreadyFollowed = true;
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@ -378,7 +380,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (following === null || !following.follower || !following.followee) {
|
if (following === null || !following.follower || !following.followee) {
|
||||||
logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
|
this.logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,15 +24,18 @@ export class UserMutingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null, withNotification = true): Promise<void> {
|
||||||
await this.mutingsRepository.insert({
|
await this.mutingsRepository.insert({
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
expiresAt: expiresAt ?? null,
|
expiresAt: expiresAt ?? null,
|
||||||
muterId: user.id,
|
muterId: user.id,
|
||||||
muteeId: target.id,
|
muteeId: target.id,
|
||||||
|
withNotification: withNotification,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cacheService.userMutingsCache.refresh(user.id);
|
this.cacheService.userMutingsCache.refresh(user.id);
|
||||||
|
this.cacheService.userMutingsWithNotificationCache.refresh(user.id);
|
||||||
|
this.cacheService.userMutingsWithoutNotificationCache.refresh(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -46,6 +49,17 @@ export class UserMutingService {
|
|||||||
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
||||||
for (const muterId of muterIds) {
|
for (const muterId of muterIds) {
|
||||||
this.cacheService.userMutingsCache.refresh(muterId);
|
this.cacheService.userMutingsCache.refresh(muterId);
|
||||||
|
this.cacheService.userMutingsWithNotificationCache.refresh(muterId);
|
||||||
|
this.cacheService.userMutingsWithoutNotificationCache.refresh(muterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async editMute(muting: MiMuting, withNotification: boolean): Promise<void> {
|
||||||
|
await this.mutingsRepository.update(muting.id, { withNotification: withNotification });
|
||||||
|
|
||||||
|
this.cacheService.userMutingsCache.refresh(muting.muterId);
|
||||||
|
this.cacheService.userMutingsWithNotificationCache.refresh(muting.muterId);
|
||||||
|
this.cacheService.userMutingsWithoutNotificationCache.refresh(muting.muterId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,7 @@ export class ApRequestService {
|
|||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
this.logger = this.loggerService.getLogger('ap:request');
|
||||||
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -45,7 +45,7 @@ export class Resolver {
|
|||||||
private recursionLimit = 100,
|
private recursionLimit = 100,
|
||||||
) {
|
) {
|
||||||
this.history = new Set();
|
this.history = new Set();
|
||||||
this.logger = this.loggerService.getLogger('ap-resolve');
|
this.logger = this.loggerService.getLogger('ap:resolve');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -78,6 +78,7 @@ export class MetaEntityService {
|
|||||||
recaptchaSiteKey: instance.recaptchaSiteKey,
|
recaptchaSiteKey: instance.recaptchaSiteKey,
|
||||||
enableTurnstile: instance.enableTurnstile,
|
enableTurnstile: instance.enableTurnstile,
|
||||||
turnstileSiteKey: instance.turnstileSiteKey,
|
turnstileSiteKey: instance.turnstileSiteKey,
|
||||||
|
googleAnalyticsId: instance.googleAnalyticsId,
|
||||||
swPublickey: instance.swPublicKey,
|
swPublickey: instance.swPublicKey,
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
|
mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
|
||||||
|
@ -40,6 +40,7 @@ export class MutingEntityService {
|
|||||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
|
withNotification: muting.withNotification,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,12 +269,12 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
*/
|
*/
|
||||||
#validateNotifier <T extends MiNotification | MiGroupedNotification> (
|
#validateNotifier <T extends MiNotification | MiGroupedNotification> (
|
||||||
notification: T,
|
notification: T,
|
||||||
userIdsWhoMeMuting: Set<MiUser['id']>,
|
userIdsWhoMeMutingWithNotification: Set<MiUser['id']>,
|
||||||
userMutedInstances: Set<string>,
|
userMutedInstances: Set<string>,
|
||||||
notifiers: MiUser[],
|
notifiers: MiUser[],
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!('notifierId' in notification)) return true;
|
if (!('notifierId' in notification)) return true;
|
||||||
if (userIdsWhoMeMuting.has(notification.notifierId)) return false;
|
if (userIdsWhoMeMutingWithNotification.has(notification.notifierId)) return false;
|
||||||
|
|
||||||
const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
|
const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
|
||||||
|
|
||||||
@ -303,10 +303,10 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
meId: MiUser['id'],
|
meId: MiUser['id'],
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const [
|
const [
|
||||||
userIdsWhoMeMuting,
|
userIdsWhoMeMutingWithNotification,
|
||||||
userMutedInstances,
|
userMutedInstances,
|
||||||
] = (await Promise.allSettled([
|
] = (await Promise.allSettled([
|
||||||
this.cacheService.userMutingsCache.fetch(meId),
|
this.cacheService.userMutingsWithNotificationCache.fetch(meId),
|
||||||
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
|
||||||
])).map(result => result.status === 'fulfilled' ? result.value : new Set<string>());
|
])).map(result => result.status === 'fulfilled' ? result.value : new Set<string>());
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
}) : [];
|
}) : [];
|
||||||
|
|
||||||
return ((await Promise.allSettled(notifications.map(async (notification) => {
|
return ((await Promise.allSettled(notifications.map(async (notification) => {
|
||||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
|
const isValid = this.#validateNotifier(notification, userIdsWhoMeMutingWithNotification, userMutedInstances, notifiers);
|
||||||
return isValid ? notification : null;
|
return isValid ? notification : null;
|
||||||
}))).filter(result => result.status === 'fulfilled' && isNotNull(result.value))
|
}))).filter(result => result.status === 'fulfilled' && isNotNull(result.value))
|
||||||
.map(result => (result as PromiseFulfilledResult<T>).value));
|
.map(result => (result as PromiseFulfilledResult<T>).value));
|
||||||
|
@ -22,13 +22,14 @@ const pinoPrettyStream = pinoPretty({
|
|||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default class Logger {
|
export default class Logger {
|
||||||
private readonly domain: string;
|
private readonly domain: string | undefined;
|
||||||
private logger: pino.Logger;
|
private readonly logger: pino.Logger;
|
||||||
private context: Record<string, any> = {};
|
private context: Record<string, any> = {};
|
||||||
|
|
||||||
constructor(domain: string, _color?: KEYWORD, _store = true, parentLogger?: Logger) {
|
constructor(domain: string | undefined, _color?: KEYWORD, _store = true, parentLogger?: Logger) {
|
||||||
if (parentLogger) {
|
if (parentLogger) {
|
||||||
this.domain = parentLogger.domain + '.' + domain;
|
this.domain = [parentLogger.domain, domain].filter(x => x).join('.') || undefined;
|
||||||
|
this.context = { ...JSON.parse(JSON.stringify(parentLogger.context)) };
|
||||||
} else {
|
} else {
|
||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
}
|
}
|
||||||
@ -50,18 +51,20 @@ export default class Logger {
|
|||||||
formatters: {
|
formatters: {
|
||||||
level: (label, number) => ({ severity: label, level: number }),
|
level: (label, number) => ({ severity: label, level: number }),
|
||||||
},
|
},
|
||||||
mixin: () => ({ cluster: cluster.isPrimary ? 'primary' : `worker#${cluster.worker!.id}`, ...this.context }),
|
mixin: () => this.mixin(),
|
||||||
}, !envOption.logJson ? pinoPrettyStream : undefined);
|
}, !envOption.logJson ? pinoPrettyStream : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
private mixin(): Record<string, any> {
|
||||||
public createSubLogger(domain: string, _color?: KEYWORD, _store = true): Logger {
|
return { cluster: cluster.isPrimary ? 'primary' : `worker#${cluster.worker!.id}`, ...this.context };
|
||||||
return new Logger(domain, undefined, false, this);
|
}
|
||||||
|
|
||||||
|
public createSubLogger(domain?: string, _color?: KEYWORD, _store = true): Logger {
|
||||||
|
return new Logger(domain, _color, _store, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public setContext(context: Record<string, any>): void {
|
public setContext(context: Record<string, any>): void {
|
||||||
this.context = context;
|
this.context = { ...this.context, ...JSON.parse(JSON.stringify(context)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -130,3 +133,6 @@ export default class Logger {
|
|||||||
this.logger.info({ context, important }, message);
|
this.logger.info({ context, important }, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const rootLogger = new Logger(undefined, undefined, false, undefined);
|
||||||
|
export const coreLogger = rootLogger.createSubLogger('core', 'cyan');
|
||||||
|
@ -265,6 +265,12 @@ export class MiMeta {
|
|||||||
|
|
||||||
// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること
|
// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 32,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public googleAnalyticsId: string | null;
|
||||||
|
|
||||||
@Column('enum', {
|
@Column('enum', {
|
||||||
enum: ['none', 'all', 'local', 'remote'],
|
enum: ['none', 'all', 'local', 'remote'],
|
||||||
default: 'none',
|
default: 'none',
|
||||||
|
@ -51,4 +51,10 @@ export class MiMuting {
|
|||||||
})
|
})
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public muter: MiUser | null;
|
public muter: MiUser | null;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
comment: 'Whether to mute notification from mutee.',
|
||||||
|
})
|
||||||
|
public withNotification: boolean;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,10 @@ export const packedMetaLiteSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
googleAnalyticsId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
swPublickey: {
|
swPublickey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
@ -32,5 +32,9 @@ export const packedMutingSchema = {
|
|||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'UserDetailedNotMe',
|
ref: 'UserDetailedNotMe',
|
||||||
},
|
},
|
||||||
|
withNotification: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -13,7 +13,7 @@ import type {
|
|||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
|
import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
|
||||||
import type * as Bull from "bullmq";
|
import type * as Bull from "bullmq";
|
||||||
import type { DbUserSuspendJobData } from "@/queue/types.js";
|
import type { DbUserSuspendJobData } from '@/queue/types.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserSuspendProcessorService {
|
export class UserSuspendProcessorService {
|
||||||
|
@ -64,6 +64,7 @@ import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'
|
|||||||
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
|
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
|
||||||
import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
|
import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
|
||||||
import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
|
import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
|
||||||
|
import * as ep___admin_normalization from './endpoints/admin/normalization.js';
|
||||||
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
|
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
|
||||||
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
|
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
|
||||||
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
|
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
|
||||||
@ -280,6 +281,7 @@ import * as ep___emoji from './endpoints/emoji.js';
|
|||||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||||
|
import * as ep___mute_edit from './endpoints/mute/edit.js';
|
||||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||||
@ -462,6 +464,7 @@ const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', us
|
|||||||
const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default };
|
const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default };
|
||||||
const $admin_invite_create: Provider = { provide: 'ep:admin/invite/create', useClass: ep___admin_invite_create.default };
|
const $admin_invite_create: Provider = { provide: 'ep:admin/invite/create', useClass: ep___admin_invite_create.default };
|
||||||
const $admin_invite_list: Provider = { provide: 'ep:admin/invite/list', useClass: ep___admin_invite_list.default };
|
const $admin_invite_list: Provider = { provide: 'ep:admin/invite/list', useClass: ep___admin_invite_list.default };
|
||||||
|
const $admin_normalization: Provider = { provide: 'ep:admin/normalization', useClass: ep___admin_normalization.default };
|
||||||
const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default };
|
const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default };
|
||||||
const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
|
const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
|
||||||
const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
|
const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
|
||||||
@ -678,6 +681,7 @@ 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 $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 };
|
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||||
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
|
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
|
||||||
|
const $mute_edit: Provider = { provide: 'ep:mute/edit', useClass: ep___mute_edit.default };
|
||||||
const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
|
const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
|
||||||
const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
|
const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
|
||||||
const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
|
const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
|
||||||
@ -864,6 +868,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$admin_getUserIps,
|
$admin_getUserIps,
|
||||||
$admin_invite_create,
|
$admin_invite_create,
|
||||||
$admin_invite_list,
|
$admin_invite_list,
|
||||||
|
$admin_normalization,
|
||||||
$admin_promo_create,
|
$admin_promo_create,
|
||||||
$admin_queue_clear,
|
$admin_queue_clear,
|
||||||
$admin_queue_deliverDelayed,
|
$admin_queue_deliverDelayed,
|
||||||
@ -1080,6 +1085,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$miauth_genToken,
|
$miauth_genToken,
|
||||||
$mute_create,
|
$mute_create,
|
||||||
$mute_delete,
|
$mute_delete,
|
||||||
|
$mute_edit,
|
||||||
$mute_list,
|
$mute_list,
|
||||||
$renoteMute_create,
|
$renoteMute_create,
|
||||||
$renoteMute_delete,
|
$renoteMute_delete,
|
||||||
@ -1260,6 +1266,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$admin_getUserIps,
|
$admin_getUserIps,
|
||||||
$admin_invite_create,
|
$admin_invite_create,
|
||||||
$admin_invite_list,
|
$admin_invite_list,
|
||||||
|
$admin_normalization,
|
||||||
$admin_promo_create,
|
$admin_promo_create,
|
||||||
$admin_queue_clear,
|
$admin_queue_clear,
|
||||||
$admin_queue_deliverDelayed,
|
$admin_queue_deliverDelayed,
|
||||||
@ -1476,6 +1483,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$miauth_genToken,
|
$miauth_genToken,
|
||||||
$mute_create,
|
$mute_create,
|
||||||
$mute_delete,
|
$mute_delete,
|
||||||
|
$mute_edit,
|
||||||
$mute_list,
|
$mute_list,
|
||||||
$renoteMute_create,
|
$renoteMute_create,
|
||||||
$renoteMute_delete,
|
$renoteMute_delete,
|
||||||
|
@ -27,6 +27,7 @@ import { RateLimiterService } from './RateLimiterService.js';
|
|||||||
import { SigninService } from './SigninService.js';
|
import { SigninService } from './SigninService.js';
|
||||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SigninApiService {
|
export class SigninApiService {
|
||||||
@ -71,7 +72,7 @@ export class SigninApiService {
|
|||||||
reply: FastifyReply,
|
reply: FastifyReply,
|
||||||
) {
|
) {
|
||||||
const logger = this.loggerService.getLogger('api:signin');
|
const logger = this.loggerService.getLogger('api:signin');
|
||||||
logger.setContext({ username: request.body.username, ip: request.ip, headers: request.headers });
|
logger.setContext({ username: request.body.username, ip: request.ip, headers: request.headers, span: request.headers['x-client-transaction-id'] ?? randomUUID() });
|
||||||
logger.info('Requested to sign in.');
|
logger.info('Requested to sign in.');
|
||||||
|
|
||||||
reply.header('Access-Control-Allow-Origin', this.config.url);
|
reply.header('Access-Control-Allow-Origin', this.config.url);
|
||||||
|
@ -22,6 +22,7 @@ import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
|||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { SigninService } from './SigninService.js';
|
import { SigninService } from './SigninService.js';
|
||||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignupApiService {
|
export class SignupApiService {
|
||||||
@ -73,7 +74,7 @@ export class SignupApiService {
|
|||||||
reply: FastifyReply,
|
reply: FastifyReply,
|
||||||
) {
|
) {
|
||||||
const logger = this.loggerService.getLogger('api:signup');
|
const logger = this.loggerService.getLogger('api:signup');
|
||||||
logger.setContext({ username: request.body.username, email: request.body.emailAddress, ip: request.ip, headers: request.headers });
|
logger.setContext({ username: request.body.username, email: request.body.emailAddress, ip: request.ip, headers: request.headers, span: request.headers['x-client-transaction-id'] ?? randomUUID() });
|
||||||
logger.info('Requested to create user account.');
|
logger.info('Requested to create user account.');
|
||||||
|
|
||||||
const body = request.body;
|
const body = request.body;
|
||||||
|
@ -64,6 +64,7 @@ import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'
|
|||||||
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
|
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
|
||||||
import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
|
import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
|
||||||
import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
|
import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
|
||||||
|
import * as ep___admin_normalization from './endpoints/admin/normalization.js';
|
||||||
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
|
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
|
||||||
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
|
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
|
||||||
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
|
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
|
||||||
@ -280,6 +281,7 @@ import * as ep___emoji from './endpoints/emoji.js';
|
|||||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||||
|
import * as ep___mute_edit from './endpoints/mute/edit.js';
|
||||||
import * as ep___mute_list from './endpoints/mute/list.js';
|
import * as ep___mute_list from './endpoints/mute/list.js';
|
||||||
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
|
||||||
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||||
@ -460,6 +462,7 @@ const eps = [
|
|||||||
['admin/get-user-ips', ep___admin_getUserIps],
|
['admin/get-user-ips', ep___admin_getUserIps],
|
||||||
['admin/invite/create', ep___admin_invite_create],
|
['admin/invite/create', ep___admin_invite_create],
|
||||||
['admin/invite/list', ep___admin_invite_list],
|
['admin/invite/list', ep___admin_invite_list],
|
||||||
|
['admin/normalization', ep___admin_normalization],
|
||||||
['admin/promo/create', ep___admin_promo_create],
|
['admin/promo/create', ep___admin_promo_create],
|
||||||
['admin/queue/clear', ep___admin_queue_clear],
|
['admin/queue/clear', ep___admin_queue_clear],
|
||||||
['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
|
['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
|
||||||
@ -676,6 +679,7 @@ const eps = [
|
|||||||
['miauth/gen-token', ep___miauth_genToken],
|
['miauth/gen-token', ep___miauth_genToken],
|
||||||
['mute/create', ep___mute_create],
|
['mute/create', ep___mute_create],
|
||||||
['mute/delete', ep___mute_delete],
|
['mute/delete', ep___mute_delete],
|
||||||
|
['mute/edit', ep___mute_edit],
|
||||||
['mute/list', ep___mute_list],
|
['mute/list', ep___mute_list],
|
||||||
['renote-mute/create', ep___renoteMute_create],
|
['renote-mute/create', ep___renoteMute_create],
|
||||||
['renote-mute/delete', ep___renoteMute_delete],
|
['renote-mute/delete', ep___renoteMute_delete],
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
@ -13,6 +14,49 @@ export const meta = {
|
|||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireRolePolicy: 'canManageAvatarDecorations',
|
requireRolePolicy: 'canManageAvatarDecorations',
|
||||||
kind: 'write:admin:avatar-decorations',
|
kind: 'write:admin:avatar-decorations',
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
@ -32,14 +76,25 @@ export const paramDef = {
|
|||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
private avatarDecorationService: AvatarDecorationService,
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
await this.avatarDecorationService.create({
|
const created = await this.avatarDecorationService.create({
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
description: ps.description,
|
description: ps.description,
|
||||||
url: ps.url,
|
url: ps.url,
|
||||||
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
||||||
}, me);
|
}, me);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: created.id,
|
||||||
|
createdAt: this.idService.parse(created.id).date.toISOString(),
|
||||||
|
updatedAt: null,
|
||||||
|
name: created.name,
|
||||||
|
description: created.description,
|
||||||
|
url: created.url,
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: created.roleIdsThatCanBeUsedThisDecoration,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/_.js';
|
|
||||||
import type { MiAnnouncement } from '@/models/Announcement.js';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
@ -73,6 +73,10 @@ export const meta = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
googleAnalyticsId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
swPublickey: {
|
swPublickey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
@ -562,6 +566,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
recaptchaSiteKey: instance.recaptchaSiteKey,
|
recaptchaSiteKey: instance.recaptchaSiteKey,
|
||||||
enableTurnstile: instance.enableTurnstile,
|
enableTurnstile: instance.enableTurnstile,
|
||||||
turnstileSiteKey: instance.turnstileSiteKey,
|
turnstileSiteKey: instance.turnstileSiteKey,
|
||||||
|
googleAnalyticsId: instance.googleAnalyticsId,
|
||||||
swPublickey: instance.swPublicKey,
|
swPublickey: instance.swPublicKey,
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
mascotImageUrl: instance.mascotImageUrl,
|
mascotImageUrl: instance.mascotImageUrl,
|
||||||
|
139
packages/backend/src/server/api/endpoints/admin/normalization.ts
Normal file
139
packages/backend/src/server/api/endpoints/admin/normalization.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import type { AbuseUserReportsRepository, FollowingsRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import type { MiUser, MiLocalUser } from '@/models/User.js';
|
||||||
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { UserSuspendService } from '@/core/UserSuspendService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
|
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
||||||
|
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||||
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:suspend-user',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: '7cc4f851-e2f1-4621-9633-ec9e1d00c01e',
|
||||||
|
},
|
||||||
|
noModerator: {
|
||||||
|
message: 'Can\'t normalize user with moderator permission.',
|
||||||
|
code: 'NO_MODERATOR_NORMALIZATION',
|
||||||
|
id: '5b68a1d3-8ee3-4862-8294-6c7d2d2edd63',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['userId'],
|
||||||
|
} 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,
|
||||||
|
|
||||||
|
@Inject(DI.followingsRepository)
|
||||||
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.abuseUserReportsRepository)
|
||||||
|
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
||||||
|
|
||||||
|
private userSuspendService: UserSuspendService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
private queueService: QueueService,
|
||||||
|
private deleteAccountService: DeleteAccountService,
|
||||||
|
private instanceActorService: InstanceActorService,
|
||||||
|
private apRendererService: ApRendererService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.roleService.isModerator(user)) {
|
||||||
|
throw new ApiError(meta.errors.noModerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.usersRepository.update(user.id, {
|
||||||
|
isSuspended: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.moderationLogService.log(me, 'normalize', {
|
||||||
|
userId: user.id,
|
||||||
|
userUsername: user.username,
|
||||||
|
userHost: user.host,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.resolveAllReports(user, me).catch(e => {});
|
||||||
|
await this.userSuspendService.doPostSuspend(user).catch(e => {});
|
||||||
|
await this.unFollowAll(user).catch(e => {});
|
||||||
|
await this.deleteAccountService.deleteAccount(user, true, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async resolveAllReports(user: MiUser, me: MiLocalUser) {
|
||||||
|
const reports = await this.abuseUserReportsRepository.findBy({ targetUserId: user.id });
|
||||||
|
|
||||||
|
for (const report of reports) {
|
||||||
|
if (report.targetUserHost != null) {
|
||||||
|
const actor = await this.instanceActorService.getInstanceActor();
|
||||||
|
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
||||||
|
|
||||||
|
this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment)), targetUser.inbox, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.abuseUserReportsRepository.update(report.id, {
|
||||||
|
resolved: true,
|
||||||
|
assigneeId: me.id,
|
||||||
|
forwarded: user.host !== null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async unFollowAll(user: MiUser) {
|
||||||
|
const followings = await this.followingsRepository.findBy({
|
||||||
|
followerId: user.id,
|
||||||
|
});
|
||||||
|
const followers = await this.followingsRepository.findBy({
|
||||||
|
followeeId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const followingPairs = await Promise.all(followings.map(f => Promise.all([
|
||||||
|
this.usersRepository.findOneByOrFail({ id: f.followerId }),
|
||||||
|
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
|
||||||
|
]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
|
||||||
|
const followerPairs = await Promise.all(followers.map(f => Promise.all([
|
||||||
|
this.usersRepository.findOneByOrFail({ id: f.followerId }),
|
||||||
|
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
|
||||||
|
]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
|
||||||
|
|
||||||
|
await this.queueService.createUnfollowJob(followingPairs.map(p => ({ from: p[0], to: p[1], silent: true })));
|
||||||
|
await this.queueService.createUnfollowJob(followerPairs.map(p => ({ from: p[0], to: p[1], silent: true })));
|
||||||
|
}
|
||||||
|
}
|
@ -79,6 +79,7 @@ export const paramDef = {
|
|||||||
enableTurnstile: { type: 'boolean' },
|
enableTurnstile: { type: 'boolean' },
|
||||||
turnstileSiteKey: { type: 'string', nullable: true },
|
turnstileSiteKey: { type: 'string', nullable: true },
|
||||||
turnstileSecretKey: { type: 'string', nullable: true },
|
turnstileSecretKey: { type: 'string', nullable: true },
|
||||||
|
googleAnalyticsId: { type: 'string', nullable: true },
|
||||||
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
|
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
|
||||||
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
||||||
setSensitiveFlagAutomatically: { type: 'boolean' },
|
setSensitiveFlagAutomatically: { type: 'boolean' },
|
||||||
@ -379,6 +380,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
set.turnstileSecretKey = ps.turnstileSecretKey;
|
set.turnstileSecretKey = ps.turnstileSecretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.googleAnalyticsId !== undefined) {
|
||||||
|
set.googleAnalyticsId = ps.googleAnalyticsId;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.sensitiveMediaDetection !== undefined) {
|
if (ps.sensitiveMediaDetection !== undefined) {
|
||||||
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
|
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||||
import { EntityNotFoundError } from "typeorm";
|
import { EntityNotFoundError } from 'typeorm';
|
||||||
import { ApiError } from "../error.js";
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
|
@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me, _token, _file, _cleanup, ip, headers) => {
|
super(meta, paramDef, async (ps, me, _token, _file, _cleanup, ip, headers) => {
|
||||||
const logger = this.loggerService.getLogger('api:federation:instances');
|
const logger = this.loggerService.getLogger('api:federation:instances');
|
||||||
logger.setContext({ params: ps, user: me?.id, ip, headers });
|
logger.setContext({ params: ps, userId: me?.id, ip, headers });
|
||||||
logger.info('Requested to fetch federated instances.');
|
logger.info('Requested to fetch federated instances.');
|
||||||
|
|
||||||
const query = this.instancesRepository.createQueryBuilder('instance');
|
const query = this.instancesRepository.createQueryBuilder('instance');
|
||||||
|
@ -8,7 +8,9 @@ import type { UserProfilesRepository } from '@/models/_.js';
|
|||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['account'],
|
tags: ['account'],
|
||||||
@ -44,11 +46,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
@Inject(DI.userProfilesRepository)
|
@Inject(DI.userProfilesRepository)
|
||||||
private userProfilesRepository: UserProfilesRepository,
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
private loggerService: LoggerService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, user, token) => {
|
super(meta, paramDef, async (ps, user, token, _file, _cleanup, ip, headers) => {
|
||||||
const isSecure = token == null;
|
const isSecure = token == null;
|
||||||
|
|
||||||
|
const logger = this.loggerService.getLogger('api:account:i');
|
||||||
|
logger.setContext({ userId: user?.id, username: user?.username, client: isSecure ? 'misskey' : 'app', ip, headers, span: (headers ? headers['x-client-transaction-id'] : undefined) ?? randomUUID() });
|
||||||
|
logger.info('Fetching account information');
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const today = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`;
|
const today = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`;
|
||||||
|
|
||||||
@ -71,11 +78,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
userProfile.loggedInDates = [...userProfile.loggedInDates, today];
|
userProfile.loggedInDates = [...userProfile.loggedInDates, today];
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.userEntityService.pack(userProfile.user!, userProfile.user!, {
|
try {
|
||||||
|
const result = await this.userEntityService.pack(userProfile.user!, userProfile.user!, {
|
||||||
schema: 'MeDetailed',
|
schema: 'MeDetailed',
|
||||||
includeSecrets: isSecure,
|
includeSecrets: isSecure,
|
||||||
userProfile,
|
userProfile,
|
||||||
});
|
});
|
||||||
|
logger.info('Returning account information');
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to pack user entity', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,8 @@ import type { Config } from '@/config.js';
|
|||||||
import { safeForSql } from '@/misc/safe-for-sql.js';
|
import { safeForSql } from '@/misc/safe-for-sql.js';
|
||||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
import { notificationRecieveConfig } from '@/models/json-schema/user.js';
|
import { notificationRecieveConfig } from '@/models/json-schema/user.js';
|
||||||
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -57,6 +57,10 @@ export const paramDef = {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
|
description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
|
||||||
},
|
},
|
||||||
|
withNotification: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: ['userId'],
|
required: ['userId'],
|
||||||
} as const;
|
} as const;
|
||||||
@ -100,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null);
|
await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null, ps.withNotification);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
88
packages/backend/src/server/api/endpoints/mute/edit.ts
Normal file
88
packages/backend/src/server/api/endpoints/mute/edit.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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 { MutingsRepository } from '@/models/_.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['account'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canUpdateContent',
|
||||||
|
|
||||||
|
kind: 'write:mutes',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: 'b851d00b-8ab1-4a56-8b1b-e24187cb48ef',
|
||||||
|
},
|
||||||
|
|
||||||
|
muteeIsYourself: {
|
||||||
|
message: 'Mutee is yourself.',
|
||||||
|
code: 'MUTEE_IS_YOURSELF',
|
||||||
|
id: 'f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9',
|
||||||
|
},
|
||||||
|
|
||||||
|
notMuting: {
|
||||||
|
message: 'You are not muting that user.',
|
||||||
|
code: 'NOT_MUTING',
|
||||||
|
id: '5467d020-daa9-4553-81e1-135c0c35a96d',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
withNotification: { type: 'boolean', nullable: false },
|
||||||
|
},
|
||||||
|
required: ['userId', 'withNotification'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.mutingsRepository)
|
||||||
|
private mutingsRepository: MutingsRepository,
|
||||||
|
|
||||||
|
private userMutingService: UserMutingService,
|
||||||
|
private getterService: GetterService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const muter = me;
|
||||||
|
|
||||||
|
// Check if the mutee is yourself
|
||||||
|
if (me.id === ps.userId) {
|
||||||
|
throw new ApiError(meta.errors.muteeIsYourself);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mutee
|
||||||
|
const mutee = await this.getterService.getUser(ps.userId).catch(err => {
|
||||||
|
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check not muting
|
||||||
|
const exist = await this.mutingsRepository.findOneBy({
|
||||||
|
muterId: muter.id,
|
||||||
|
muteeId: mutee.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist == null) {
|
||||||
|
throw new ApiError(meta.errors.notMuting);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userMutingService.editMute(exist, ps.withNotification);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -282,7 +282,7 @@ export class ClientServerService {
|
|||||||
};
|
};
|
||||||
const csp = this.config.contentSecurityPolicy
|
const csp = this.config.contentSecurityPolicy
|
||||||
?? 'script-src \'self\' ' +
|
?? 'script-src \'self\' ' +
|
||||||
'https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/ {scriptNonce}; ' +
|
'https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/ https://www.googletagmanager.com/ {scriptNonce}; ' +
|
||||||
'worker-src blob: \'self\'; ' +
|
'worker-src blob: \'self\'; ' +
|
||||||
'base-uri \'self\'; object-src \'self\'; report-uri /csp-error';
|
'base-uri \'self\'; object-src \'self\'; report-uri /csp-error';
|
||||||
reply.header('Content-Security-Policy-Report-Only', csp.replace('{scriptNonce}', `'nonce-${scriptNonce}'`));
|
reply.header('Content-Security-Policy-Report-Only', csp.replace('{scriptNonce}', `'nonce-${scriptNonce}'`));
|
||||||
@ -478,7 +478,9 @@ export class ClientServerService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Atom
|
// Atom
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user.atom', async (request, reply) => {
|
fastify.get<{ Params: { user?: string; } }>('/@:user.atom', async (request, reply) => {
|
||||||
|
if (request.params.user == null) return await renderBase(reply);
|
||||||
|
|
||||||
const feed = await getFeed(request.params.user);
|
const feed = await getFeed(request.params.user);
|
||||||
|
|
||||||
if (feed) {
|
if (feed) {
|
||||||
@ -491,7 +493,9 @@ export class ClientServerService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// RSS
|
// RSS
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user.rss', async (request, reply) => {
|
fastify.get<{ Params: { user?: string; } }>('/@:user.rss', async (request, reply) => {
|
||||||
|
if (request.params.user == null) return await renderBase(reply);
|
||||||
|
|
||||||
const feed = await getFeed(request.params.user);
|
const feed = await getFeed(request.params.user);
|
||||||
|
|
||||||
if (feed) {
|
if (feed) {
|
||||||
@ -504,7 +508,9 @@ export class ClientServerService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user.json', async (request, reply) => {
|
fastify.get<{ Params: { user?: string; } }>('/@:user.json', async (request, reply) => {
|
||||||
|
if (request.params.user == null) return await renderBase(reply);
|
||||||
|
|
||||||
const feed = await getFeed(request.params.user);
|
const feed = await getFeed(request.params.user);
|
||||||
|
|
||||||
if (feed) {
|
if (feed) {
|
||||||
|
@ -99,6 +99,7 @@ export const moderationLogTypes = [
|
|||||||
'unsetUserAvatar',
|
'unsetUserAvatar',
|
||||||
'unsetUserBanner',
|
'unsetUserBanner',
|
||||||
'unsetUserMutualLink',
|
'unsetUserMutualLink',
|
||||||
|
'normalize',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
@ -333,7 +334,12 @@ export type ModerationLogPayloads = {
|
|||||||
userId: string;
|
userId: string;
|
||||||
userUsername: string;
|
userUsername: string;
|
||||||
userMutualLinkSections: { name: string | null; mutualLinks: { id: string; url: string; fileId: string; description: string | null; imgSrc: string; }[]; }[] | []
|
userMutualLinkSections: { name: string | null; mutualLinks: { id: string; url: string; fileId: string; description: string | null; imgSrc: string; }[]; }[] | []
|
||||||
}
|
};
|
||||||
|
normalize: {
|
||||||
|
userId: string;
|
||||||
|
userUsername: string;
|
||||||
|
userHost: string | null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Serialized<T> = {
|
export type Serialized<T> = {
|
||||||
|
@ -19,7 +19,7 @@ import { entity as TestUniqueChartEntity } from '@/core/chart/charts/entities/te
|
|||||||
import { entity as TestIntersectionChartEntity } from '@/core/chart/charts/entities/test-intersection.js';
|
import { entity as TestIntersectionChartEntity } from '@/core/chart/charts/entities/test-intersection.js';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '@/config.js';
|
||||||
import type { AppLockService } from '@/core/AppLockService.js';
|
import type { AppLockService } from '@/core/AppLockService.js';
|
||||||
import Logger from '@/logger.js';
|
import { coreLogger } from '@/logger.js';
|
||||||
|
|
||||||
describe('Chart', () => {
|
describe('Chart', () => {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
@ -63,7 +63,7 @@ describe('Chart', () => {
|
|||||||
|
|
||||||
await db.initialize();
|
await db.initialize();
|
||||||
|
|
||||||
const logger = new Logger('chart'); // TODO: モックにする
|
const logger = coreLogger.createSubLogger('chart'); // TODO: モックにする
|
||||||
testChart = new TestChart(db, appLockService, logger);
|
testChart = new TestChart(db, appLockService, logger);
|
||||||
testGroupedChart = new TestGroupedChart(db, appLockService, logger);
|
testGroupedChart = new TestGroupedChart(db, appLockService, logger);
|
||||||
testUniqueChart = new TestUniqueChart(db, appLockService, logger);
|
testUniqueChart = new TestUniqueChart(db, appLockService, logger);
|
||||||
|
14
packages/frontend/@types/vue-gtag.d.ts
vendored
Normal file
14
packages/frontend/@types/vue-gtag.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
declare module 'vue-gtag' {
|
||||||
|
export type GtagConsent = (command: 'consent', arg: 'default' | 'update', params: GtagConsentParams) => void;
|
||||||
|
|
||||||
|
export interface GtagConsentParams {
|
||||||
|
ad_storage?: 'granted' | 'denied',
|
||||||
|
ad_user_data?: 'granted' | 'denied',
|
||||||
|
ad_personalization?: 'granted' | 'denied',
|
||||||
|
analytics_storage?: 'granted' | 'denied',
|
||||||
|
functionality_storage?: 'granted' | 'denied',
|
||||||
|
personalization_storage?: 'granted' | 'denied',
|
||||||
|
security_storage?: 'granted' | 'denied',
|
||||||
|
wait_for_update?: number
|
||||||
|
}
|
||||||
|
}
|
@ -25,9 +25,9 @@
|
|||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.1",
|
"@rollup/plugin-replace": "6.0.1",
|
||||||
"@rollup/plugin-typescript": "12.1.1",
|
"@rollup/plugin-typescript": "12.1.1",
|
||||||
"@rollup/pluginutils": "5.1.2",
|
"@rollup/pluginutils": "5.1.3",
|
||||||
"@syuilo/aiscript": "0.19.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@tabler/icons-webfont": "3.19.0",
|
"@tabler/icons-webfont": "3.21.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.1.4",
|
"@vitejs/plugin-vue": "5.1.4",
|
||||||
"@vue/compiler-sfc": "3.5.12",
|
"@vue/compiler-sfc": "3.5.12",
|
||||||
@ -36,12 +36,12 @@
|
|||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.0.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"canvas-confetti": "1.9.3",
|
"canvas-confetti": "1.9.3",
|
||||||
"chart.js": "4.4.5",
|
"chart.js": "4.4.6",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"chromatic": "11.14.0",
|
"chromatic": "11.16.5",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.0-rc.0",
|
"cropperjs": "2.0.0-rc.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
@ -59,86 +59,86 @@
|
|||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.24.0",
|
"rollup": "4.24.4",
|
||||||
"sanitize-html": "2.13.1",
|
"sanitize-html": "2.13.1",
|
||||||
"sass": "1.80.3",
|
"sass": "1.80.6",
|
||||||
"shiki": "1.22.0",
|
"shiki": "1.22.2",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.169.0",
|
"three": "0.170.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"uuid": "10.0.0",
|
"uuid": "11.0.2",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "5.4.9",
|
"vite": "5.4.10",
|
||||||
"vue": "3.5.12",
|
"vue": "3.5.12",
|
||||||
|
"vue-gtag": "2.0.1",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@misskey-dev/summaly": "5.1.0",
|
"@misskey-dev/summaly": "5.1.0",
|
||||||
"@storybook/addon-actions": "8.3.6",
|
"@storybook/addon-actions": "8.4.2",
|
||||||
"@storybook/addon-essentials": "8.3.6",
|
"@storybook/addon-essentials": "8.4.2",
|
||||||
"@storybook/addon-interactions": "8.3.6",
|
"@storybook/addon-interactions": "8.4.2",
|
||||||
"@storybook/addon-links": "8.3.6",
|
"@storybook/addon-links": "8.4.2",
|
||||||
"@storybook/addon-mdx-gfm": "8.3.6",
|
"@storybook/addon-mdx-gfm": "8.4.2",
|
||||||
"@storybook/addon-storysource": "8.3.6",
|
"@storybook/addon-storysource": "8.4.2",
|
||||||
"@storybook/blocks": "8.3.6",
|
"@storybook/blocks": "8.4.2",
|
||||||
"@storybook/components": "8.3.6",
|
"@storybook/components": "8.4.2",
|
||||||
"@storybook/core-events": "8.3.6",
|
"@storybook/core-events": "8.4.2",
|
||||||
"@storybook/manager-api": "8.3.6",
|
"@storybook/manager-api": "8.4.2",
|
||||||
"@storybook/preview-api": "8.3.6",
|
"@storybook/preview-api": "8.4.2",
|
||||||
"@storybook/react": "8.3.6",
|
"@storybook/react": "8.4.2",
|
||||||
"@storybook/react-vite": "8.3.6",
|
"@storybook/react-vite": "8.4.2",
|
||||||
"@storybook/test": "8.3.6",
|
"@storybook/test": "8.4.2",
|
||||||
"@storybook/theming": "8.3.6",
|
"@storybook/theming": "8.4.2",
|
||||||
"@storybook/types": "8.3.6",
|
"@storybook/types": "8.4.2",
|
||||||
"@storybook/vue3": "8.3.6",
|
"@storybook/vue3": "8.4.2",
|
||||||
"@storybook/vue3-vite": "8.3.6",
|
"@storybook/vue3-vite": "8.4.2",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "^1.6.4",
|
"@types/canvas-confetti": "^1.6.4",
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@types/matter-js": "0.19.7",
|
"@types/matter-js": "0.19.7",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.7.8",
|
"@types/node": "22.9.0",
|
||||||
"@types/punycode": "2.1.4",
|
"@types/punycode": "2.1.4",
|
||||||
"@types/sanitize-html": "2.13.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/uuid": "10.0.0",
|
"@types/ws": "8.5.13",
|
||||||
"@types/ws": "8.5.12",
|
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"@vitest/coverage-v8": "2.1.3",
|
"@vitest/coverage-v8": "2.1.4",
|
||||||
"@vue/runtime-core": "3.5.12",
|
"@vue/runtime-core": "3.5.12",
|
||||||
"acorn": "8.13.0",
|
"acorn": "8.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.15.0",
|
"cypress": "13.15.1",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.29.1",
|
"eslint-plugin-vue": "9.30.0",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"happy-dom": "15.7.4",
|
"happy-dom": "15.9.0",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.5.0",
|
"msw": "2.6.0",
|
||||||
"msw-storybook-addon": "2.0.3",
|
"msw-storybook-addon": "2.0.4",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.7",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"start-server-and-test": "2.0.8",
|
"start-server-and-test": "2.0.8",
|
||||||
"storybook": "8.3.6",
|
"storybook": "8.4.2",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "2.1.3",
|
"vitest": "2.1.4",
|
||||||
"vitest-fetch-mock": "0.3.0",
|
"vitest-fetch-mock": "0.3.0",
|
||||||
"vue-component-type-helpers": "2.1.6",
|
"vue-component-type-helpers": "2.1.10",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
"vue-tsc": "2.1.6"
|
"vue-tsc": "2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import '@/style.scss';
|
|||||||
import { mainBoot } from '@/boot/main-boot.js';
|
import { mainBoot } from '@/boot/main-boot.js';
|
||||||
import { subBoot } from '@/boot/sub-boot.js';
|
import { subBoot } from '@/boot/sub-boot.js';
|
||||||
|
|
||||||
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete', '/onboarding', '/oauth'];
|
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete', '/onboarding', '/oauth', '/sso'];
|
||||||
|
|
||||||
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
|
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
|
||||||
subBoot();
|
subBoot();
|
||||||
|
@ -12,8 +12,10 @@ import { miLocalStorage } from '@/local-storage.js';
|
|||||||
import { del, get, set } from '@/scripts/idb-proxy.js';
|
import { del, get, set } from '@/scripts/idb-proxy.js';
|
||||||
import { apiUrl } from '@/config.js';
|
import { apiUrl } from '@/config.js';
|
||||||
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
|
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { generateClientTransactionId, misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
|
import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
|
||||||
|
import { set as gtagSet } from 'vue-gtag';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ export async function signout() {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-Client-Transaction-Id': generateClientTransactionId('misskey'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -109,6 +112,7 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-Client-Transaction-Id': generateClientTransactionId('misskey'),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => {
|
.then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => {
|
||||||
@ -171,6 +175,12 @@ export function updateAccount(accountData: Partial<Account>) {
|
|||||||
$i[key] = value;
|
$i[key] = value;
|
||||||
}
|
}
|
||||||
miLocalStorage.setItem('account', JSON.stringify($i));
|
miLocalStorage.setItem('account', JSON.stringify($i));
|
||||||
|
if (instance.googleAnalyticsId) {
|
||||||
|
gtagSet({
|
||||||
|
'client_id': miLocalStorage.getItem('id'),
|
||||||
|
'user_id': $i.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refreshAccount() {
|
export async function refreshAccount() {
|
||||||
@ -222,24 +232,6 @@ export async function openAccountMenu(opts: {
|
|||||||
}, ev: MouseEvent) {
|
}, ev: MouseEvent) {
|
||||||
if (!$i) return;
|
if (!$i) return;
|
||||||
|
|
||||||
function showSigninDialog() {
|
|
||||||
popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
|
|
||||||
done: res => {
|
|
||||||
addAccount(res.id, res.i);
|
|
||||||
success();
|
|
||||||
},
|
|
||||||
}, 'closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAccount() {
|
|
||||||
popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
|
|
||||||
done: res => {
|
|
||||||
addAccount(res.id, res.i);
|
|
||||||
switchAccountWithToken(res.i);
|
|
||||||
},
|
|
||||||
}, 'closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function switchAccount(account: Misskey.entities.UserDetailed) {
|
async function switchAccount(account: Misskey.entities.UserDetailed) {
|
||||||
const storedAccounts = await getAccounts();
|
const storedAccounts = await getAccounts();
|
||||||
const found = storedAccounts.find(x => x.id === account.id);
|
const found = storedAccounts.find(x => x.id === account.id);
|
||||||
|
@ -11,7 +11,7 @@ import components from '@/components/index.js';
|
|||||||
import { version, lang, updateLocale, locale } from '@/config.js';
|
import { version, lang, updateLocale, locale } from '@/config.js';
|
||||||
import { applyTheme } from '@/scripts/theme.js';
|
import { applyTheme } from '@/scripts/theme.js';
|
||||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
||||||
import { updateI18n } from '@/i18n.js';
|
import { updateI18n, i18n } from '@/i18n.js';
|
||||||
import { $i, iAmAdmin, refreshAccount, login } from '@/account.js';
|
import { $i, iAmAdmin, refreshAccount, login } from '@/account.js';
|
||||||
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
@ -24,6 +24,8 @@ import { deckStore } from '@/ui/deck/deck-store.js';
|
|||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||||
import { setupRouter } from '@/router/definition.js';
|
import { setupRouter } from '@/router/definition.js';
|
||||||
|
import { mainRouter } from '@/router/main.js';
|
||||||
|
import VueGtag, { bootstrap as gtagBootstrap, GtagConsent, GtagConsentParams } from 'vue-gtag';
|
||||||
|
|
||||||
export async function common(createVue: () => App<Element>) {
|
export async function common(createVue: () => App<Element>) {
|
||||||
console.info(`Misskey v${version}`);
|
console.info(`Misskey v${version}`);
|
||||||
@ -61,6 +63,10 @@ export async function common(createVue: () => App<Element>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (miLocalStorage.getItem('id') === null) {
|
||||||
|
miLocalStorage.setItem('id', crypto.randomUUID());
|
||||||
|
}
|
||||||
|
|
||||||
let isClientUpdated = false;
|
let isClientUpdated = false;
|
||||||
|
|
||||||
//#region クライアントが更新されたかチェック
|
//#region クライアントが更新されたかチェック
|
||||||
@ -275,6 +281,38 @@ export async function common(createVue: () => App<Element>) {
|
|||||||
directives(app);
|
directives(app);
|
||||||
components(app);
|
components(app);
|
||||||
|
|
||||||
|
if (instance.googleAnalyticsId) {
|
||||||
|
app.use(VueGtag, {
|
||||||
|
bootstrap: false,
|
||||||
|
appName: `Misskey v${version}`,
|
||||||
|
config: {
|
||||||
|
id: instance.googleAnalyticsId,
|
||||||
|
params: {
|
||||||
|
anonymize_ip: false,
|
||||||
|
send_page_view: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, mainRouter);
|
||||||
|
|
||||||
|
const gtagConsent = miLocalStorage.getItemAsJson('gtagConsent') as GtagConsentParams ?? {
|
||||||
|
ad_storage: 'denied',
|
||||||
|
ad_user_data: 'denied',
|
||||||
|
ad_personalization: 'denied',
|
||||||
|
analytics_storage: 'denied',
|
||||||
|
functionality_storage: 'denied',
|
||||||
|
personalization_storage: 'denied',
|
||||||
|
security_storage: 'granted',
|
||||||
|
};
|
||||||
|
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||||
|
|
||||||
|
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'default', gtagConsent);
|
||||||
|
|
||||||
|
if (miLocalStorage.getItem('gaConsent') === 'true') {
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
|
gtagBootstrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
||||||
// なぜか2回実行されることがあるため、mountするdivを1つに制限する
|
// なぜか2回実行されることがあるため、mountするdivを1つに制限する
|
||||||
const rootEl = ((): HTMLElement => {
|
const rootEl = ((): HTMLElement => {
|
||||||
@ -301,6 +339,27 @@ export async function common(createVue: () => App<Element>) {
|
|||||||
|
|
||||||
removeSplash();
|
removeSplash();
|
||||||
|
|
||||||
|
//#region Self-XSS 対策メッセージ
|
||||||
|
console.log(
|
||||||
|
`%c${i18n.ts._selfXssPrevention.warning}`,
|
||||||
|
'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`%c${i18n.ts._selfXssPrevention.title}`,
|
||||||
|
'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`%c${i18n.ts._selfXssPrevention.description1}`,
|
||||||
|
'font-size: 16px; font-weight: 700;',
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`%c${i18n.ts._selfXssPrevention.description2}`,
|
||||||
|
'font-size: 16px;',
|
||||||
|
'font-size: 20px; font-weight: 700; color: #f00;',
|
||||||
|
);
|
||||||
|
console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
|
||||||
|
//#endregion
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isClientUpdated,
|
isClientUpdated,
|
||||||
app,
|
app,
|
||||||
|
@ -20,6 +20,7 @@ import { initializeSw } from '@/scripts/initialize-sw.js';
|
|||||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||||
import { mainRouter } from '@/router/main.js';
|
import { mainRouter } from '@/router/main.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
export async function mainBoot() {
|
export async function mainBoot() {
|
||||||
const { isClientUpdated } = await common(() => createApp(
|
const { isClientUpdated } = await common(() => createApp(
|
||||||
@ -228,9 +229,10 @@ export async function mainBoot() {
|
|||||||
}
|
}
|
||||||
miLocalStorage.setItem('lastUsed', Date.now().toString());
|
miLocalStorage.setItem('lastUsed', Date.now().toString());
|
||||||
|
|
||||||
|
if (!location.pathname.startsWith('/miauth') && !location.pathname.startsWith('/sso') && !location.pathname.startsWith('/oauth')) {
|
||||||
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
|
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
|
||||||
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
|
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
|
||||||
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
|
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3)))) {
|
||||||
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
|
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
|
||||||
popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
|
popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
|
||||||
}
|
}
|
||||||
@ -241,6 +243,11 @@ export async function mainBoot() {
|
|||||||
// popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
|
// popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if (instance.googleAnalyticsId && miLocalStorage.getItem('gaConsent') === null) {
|
||||||
|
popup(defineAsyncComponent(() => import('@/components/MkTrackingConsent.vue')), {}, {}, 'closed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ('Notification' in window) {
|
if ('Notification' in window) {
|
||||||
// 許可を得ていなかったらリクエスト
|
// 許可を得ていなかったらリクエスト
|
||||||
if (Notification.permission === 'default') {
|
if (Notification.permission === 'default') {
|
||||||
|
@ -400,7 +400,7 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:checked + .accountSelectorItem {
|
&:checked + .accountSelectorItem {
|
||||||
background: var(--accent);
|
background: color-mix(in srgb, var(--accent), transparent 50%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,12 @@ import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
|
|||||||
import MkModal from './MkModal.vue';
|
import MkModal from './MkModal.vue';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
withOkButton: boolean;
|
withOkButton?: boolean;
|
||||||
withCloseButton: boolean;
|
withCloseButton?: boolean;
|
||||||
okButtonDisabled: boolean;
|
okButtonDisabled?: boolean;
|
||||||
escKeyDisabled: boolean;
|
escKeyDisabled?: boolean;
|
||||||
width: number;
|
width?: number;
|
||||||
height: number;
|
height?: number;
|
||||||
}>(), {
|
}>(), {
|
||||||
withOkButton: false,
|
withOkButton: false,
|
||||||
withCloseButton: true,
|
withCloseButton: true,
|
||||||
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
ref="dialog"
|
ref="dialog"
|
||||||
:width="500"
|
:width="500"
|
||||||
:height="600"
|
:height="600"
|
||||||
@close="dialog?.close()"
|
@close="onClose"
|
||||||
@closed="$emit('closed')"
|
@closed="$emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.signup }}</template>
|
<template #header>{{ i18n.ts.signup }}</template>
|
||||||
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:leaveToClass="$style.transition_x_leaveTo"
|
:leaveToClass="$style.transition_x_leaveTo"
|
||||||
>
|
>
|
||||||
<template v-if="!isAcceptedServerRule">
|
<template v-if="!isAcceptedServerRule">
|
||||||
<XServerRules @done="isAcceptedServerRule = true" @cancel="dialog?.close()"/>
|
<XServerRules @done="isAcceptedServerRule = true" @cancel="onClose"/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
|
<XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
|
||||||
@ -47,7 +47,7 @@ const props = withDefaults(defineProps<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'done', res: Misskey.entities.SigninResponse): void;
|
(ev: 'done', res: Misskey.entities.SignupResponse): void;
|
||||||
(ev: 'cancelled'): void;
|
(ev: 'cancelled'): void;
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
@ -56,7 +56,12 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
|||||||
|
|
||||||
const isAcceptedServerRule = ref(false);
|
const isAcceptedServerRule = ref(false);
|
||||||
|
|
||||||
function onSignup(res: Misskey.entities.SigninResponse) {
|
function onClose() {
|
||||||
|
emit('cancelled');
|
||||||
|
dialog.value?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSignup(res: Misskey.entities.SignupResponse) {
|
||||||
emit('done', res);
|
emit('done', res);
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
177
packages/frontend/src/components/MkTrackingConsent.vue
Normal file
177
packages/frontend/src/components/MkTrackingConsent.vue
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_panel _shadow" :class="$style.root">
|
||||||
|
<div :class="$style.main">
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<div :class="$style.headerIcon">
|
||||||
|
<i class="ti ti-report-analytics"></i>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.headerTitle"><Mfm :text="i18n.ts.helpUsImproveUserExperience" /></div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.text">
|
||||||
|
<Mfm
|
||||||
|
:text="i18n.tsx.pleaseConsentToTracking({
|
||||||
|
host: instance.name ?? host,
|
||||||
|
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||||
|
})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<div class="_buttons" style="justify-content: right;">
|
||||||
|
<MkButton @click="consentEssential">{{ i18n.ts.consentEssential }}</MkButton>
|
||||||
|
<MkButton primary @click="consentAll">{{ i18n.ts.consentAll }}</MkButton>
|
||||||
|
</div>
|
||||||
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-lock-square"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.gtagConsentCustomize }}</template>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkInfo>{{ i18n.tsx.gtagConsentCustomizeDescription({ host: instance.name ?? host }) }}</MkInfo>
|
||||||
|
<MkSwitch v-model="gtagConsentAnalytics">
|
||||||
|
{{ i18n.ts.gtagConsentAnalytics }}
|
||||||
|
<template #caption>{{ i18n.ts.gtagConsentAnalyticsDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="gtagConsentFunctionality">
|
||||||
|
{{ i18n.ts.gtagConsentFunctionality }}
|
||||||
|
<template #caption>{{ i18n.ts.gtagConsentFunctionalityDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="gtagConsentPersonalization">
|
||||||
|
{{ i18n.ts.gtagConsentPersonalization }}
|
||||||
|
<template #caption>{{ i18n.ts.gtagConsentPersonalizationDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<div class="_buttons" style="justify-content: right;">
|
||||||
|
<MkButton @click="consentSelected">{{ i18n.ts.consentSelected }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
import { host } from '@/config.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { $i } from '@/account.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import {
|
||||||
|
bootstrap as gtagBootstrap,
|
||||||
|
GtagConsent,
|
||||||
|
GtagConsentParams,
|
||||||
|
set as gtagSet
|
||||||
|
} from 'vue-gtag';
|
||||||
|
|
||||||
|
const emit = defineEmits<(ev: 'closed') => void>();
|
||||||
|
|
||||||
|
const zIndex = os.claimZIndex('middle');
|
||||||
|
|
||||||
|
const gtagConsentAnalytics = ref(false);
|
||||||
|
const gtagConsentFunctionality = ref(false);
|
||||||
|
const gtagConsentPersonalization = ref(false);
|
||||||
|
|
||||||
|
function consentAll() {
|
||||||
|
miLocalStorage.setItem('gaConsent', 'true');
|
||||||
|
const gtagConsent = <GtagConsentParams>{
|
||||||
|
ad_storage: 'granted',
|
||||||
|
ad_user_data: 'granted',
|
||||||
|
ad_personalization: 'granted',
|
||||||
|
analytics_storage: 'granted',
|
||||||
|
functionality_storage: 'granted',
|
||||||
|
personalization_storage: 'granted',
|
||||||
|
security_storage: 'granted',
|
||||||
|
};
|
||||||
|
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||||
|
|
||||||
|
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent);
|
||||||
|
bootstrap();
|
||||||
|
|
||||||
|
emit('closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function consentEssential() {
|
||||||
|
miLocalStorage.setItem('gaConsent', 'true');
|
||||||
|
const gtagConsent = <GtagConsentParams>{
|
||||||
|
ad_storage: 'denied',
|
||||||
|
ad_user_data: 'denied',
|
||||||
|
ad_personalization: 'denied',
|
||||||
|
analytics_storage: 'denied',
|
||||||
|
functionality_storage: 'denied',
|
||||||
|
personalization_storage: 'denied',
|
||||||
|
security_storage: 'granted',
|
||||||
|
};
|
||||||
|
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||||
|
|
||||||
|
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent);
|
||||||
|
bootstrap();
|
||||||
|
|
||||||
|
emit('closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function consentSelected() {
|
||||||
|
miLocalStorage.setItem('gaConsent', 'true');
|
||||||
|
const gtagConsent = <GtagConsentParams>{
|
||||||
|
ad_storage: gtagConsentAnalytics.value ? 'granted' : 'denied',
|
||||||
|
ad_user_data: gtagConsentFunctionality.value ? 'granted' : 'denied',
|
||||||
|
ad_personalization: gtagConsentPersonalization.value ? 'granted' : 'denied',
|
||||||
|
analytics_storage: gtagConsentAnalytics.value ? 'granted' : 'denied',
|
||||||
|
functionality_storage: gtagConsentFunctionality.value ? 'granted' : 'denied',
|
||||||
|
personalization_storage: gtagConsentPersonalization.value ? 'granted' : 'denied',
|
||||||
|
security_storage: 'granted',
|
||||||
|
};
|
||||||
|
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||||
|
|
||||||
|
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent);
|
||||||
|
bootstrap();
|
||||||
|
|
||||||
|
emit('closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bootstrap() {
|
||||||
|
gtagBootstrap();
|
||||||
|
gtagSet({
|
||||||
|
'client_id': miLocalStorage.getItem('id'),
|
||||||
|
'user_id': $i?.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
position: fixed;
|
||||||
|
z-index: v-bind(zIndex);
|
||||||
|
bottom: var(--margin);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: calc(100% - (var(--margin) * 2));
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding: 25px 25px 25px 25px;
|
||||||
|
width: inherit;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerIcon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 40px;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerTitle {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin: 0.7em 0 1em 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
rel: 'nofollow noopener',
|
rel: 'nofollow noopener',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
}"
|
}"
|
||||||
|
@click="onAdClicked"
|
||||||
>
|
>
|
||||||
<img :src="chosen.imageUrl" :class="$style.img">
|
<img :src="chosen.imageUrl" :class="$style.img">
|
||||||
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
|
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
|
||||||
@ -42,7 +43,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
/* eslint-disable id-denylist */
|
||||||
|
import { ref, computed, onActivated, onMounted } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { url as local, host } from '@/config.js';
|
import { url as local, host } from '@/config.js';
|
||||||
@ -50,6 +52,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { usageReport } from '@/scripts/usage-report.js';
|
||||||
|
|
||||||
type Ad = (typeof instance)['ads'][number];
|
type Ad = (typeof instance)['ads'][number];
|
||||||
|
|
||||||
@ -123,6 +126,36 @@ function reduceFrequency(): void {
|
|||||||
chosen.value = choseAd();
|
chosen.value = choseAd();
|
||||||
showMenu.value = false;
|
showMenu.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onAdClicked(): void {
|
||||||
|
if (chosen.value == null) return;
|
||||||
|
usageReport({
|
||||||
|
t: Math.floor(Date.now() / 1000),
|
||||||
|
e: 'a',
|
||||||
|
i: chosen.value.id,
|
||||||
|
a: 'c',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (chosen.value == null) return;
|
||||||
|
usageReport({
|
||||||
|
t: Math.floor(Date.now() / 1000),
|
||||||
|
e: 'a',
|
||||||
|
i: chosen.value.id,
|
||||||
|
a: 'v',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
if (chosen.value == null) return;
|
||||||
|
usageReport({
|
||||||
|
t: Math.floor(Date.now() / 1000),
|
||||||
|
e: 'a',
|
||||||
|
i: chosen.value.id,
|
||||||
|
a: 'v',
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -16,13 +16,13 @@
|
|||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
|
content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/ https://fonts.gstatic.com/ https://www.google-analytics.com/ https://www.googletagmanager.com/;
|
||||||
worker-src 'self';
|
worker-src 'self';
|
||||||
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh;
|
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://www.googletagmanager.com https://esm.sh;
|
||||||
style-src 'self' 'unsafe-inline';
|
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com https://www.googletagmanager.com;
|
||||||
img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://fonts.gstatic.com https://www.googletagmanager.com;
|
||||||
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
||||||
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.pwnedpasswords.com;
|
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.pwnedpasswords.com https://www.google-analytics.com https://analytics.google.com;
|
||||||
frame-src *;"
|
frame-src *;"
|
||||||
/>
|
/>
|
||||||
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type Keys =
|
type Keys =
|
||||||
|
'id' |
|
||||||
'v' |
|
'v' |
|
||||||
'lastVersion' |
|
'lastVersion' |
|
||||||
'instance' |
|
'instance' |
|
||||||
@ -39,7 +40,10 @@ type Keys =
|
|||||||
'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
|
'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
|
||||||
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
|
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
|
||||||
`channelLastReadedAt:${string}` |
|
`channelLastReadedAt:${string}` |
|
||||||
'kawaii'
|
'kawaii' |
|
||||||
|
'gaConsent' |
|
||||||
|
'gtagConsent'
|
||||||
|
;
|
||||||
|
|
||||||
export const miLocalStorage = {
|
export const miLocalStorage = {
|
||||||
getItem: (key: Keys): string | null => window.localStorage.getItem(key),
|
getItem: (key: Keys): string | null => window.localStorage.getItem(key),
|
||||||
|
@ -71,6 +71,8 @@ export type Resolved = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AfterNavigationHook = (to: RouteDef, from: RouteDef) => any;
|
||||||
|
|
||||||
function parsePath(path: string): ParsedPath {
|
function parsePath(path: string): ParsedPath {
|
||||||
const res = [] as ParsedPath;
|
const res = [] as ParsedPath;
|
||||||
|
|
||||||
@ -101,6 +103,7 @@ export interface IRouter extends EventEmitter<RouterEvent> {
|
|||||||
currentRef: ShallowRef<Resolved>;
|
currentRef: ShallowRef<Resolved>;
|
||||||
currentRoute: ShallowRef<RouteDef>;
|
currentRoute: ShallowRef<RouteDef>;
|
||||||
navHook: ((path: string, flag?: any) => boolean) | null;
|
navHook: ((path: string, flag?: any) => boolean) | null;
|
||||||
|
afterHooks: Array<AfterNavigationHook | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
|
* ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
|
||||||
@ -109,10 +112,14 @@ export interface IRouter extends EventEmitter<RouterEvent> {
|
|||||||
|
|
||||||
resolve(path: string): Resolved | null;
|
resolve(path: string): Resolved | null;
|
||||||
|
|
||||||
|
isReady(): Promise<boolean>;
|
||||||
|
|
||||||
getCurrentPath(): any;
|
getCurrentPath(): any;
|
||||||
|
|
||||||
getCurrentKey(): string;
|
getCurrentKey(): string;
|
||||||
|
|
||||||
|
afterEach(hook: AfterNavigationHook): AfterNavigationHook | undefined;
|
||||||
|
|
||||||
push(path: string, flag?: any): void;
|
push(path: string, flag?: any): void;
|
||||||
|
|
||||||
replace(path: string, key?: string | null): void;
|
replace(path: string, key?: string | null): void;
|
||||||
@ -191,6 +198,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||||||
private redirectCount = 0;
|
private redirectCount = 0;
|
||||||
|
|
||||||
public navHook: ((path: string, flag?: any) => boolean) | null = null;
|
public navHook: ((path: string, flag?: any) => boolean) | null = null;
|
||||||
|
public afterHooks: Array<AfterNavigationHook | null> = [];
|
||||||
|
|
||||||
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
|
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
|
||||||
super();
|
super();
|
||||||
@ -339,6 +347,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||||||
|
|
||||||
private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
|
private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
|
||||||
const beforePath = this.currentPath;
|
const beforePath = this.currentPath;
|
||||||
|
const beforeRoute = this.currentRoute.value;
|
||||||
this.currentPath = path;
|
this.currentPath = path;
|
||||||
|
|
||||||
const res = this.resolve(this.currentPath);
|
const res = this.resolve(this.currentPath);
|
||||||
@ -382,6 +391,12 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.afterHooks.length > 0) {
|
||||||
|
for (const hook of this.afterHooks) {
|
||||||
|
if (hook) hook(res.route, beforeRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.redirectCount = 0;
|
this.redirectCount = 0;
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
@ -389,6 +404,10 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isReady() {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
public getCurrentPath() {
|
public getCurrentPath() {
|
||||||
return this.currentPath;
|
return this.currentPath;
|
||||||
}
|
}
|
||||||
@ -397,6 +416,18 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||||||
return this.currentKey;
|
return this.currentKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public afterEach(hook: AfterNavigationHook): AfterNavigationHook | undefined {
|
||||||
|
this.afterHooks.push(hook);
|
||||||
|
return () => {
|
||||||
|
const index = this.afterHooks.indexOf(hook);
|
||||||
|
if (index !== -1) {
|
||||||
|
return this.afterHooks.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public push(path: string, flag?: any) {
|
public push(path: string, flag?: any) {
|
||||||
const beforePath = this.currentPath;
|
const beforePath = this.currentPath;
|
||||||
if (path === beforePath) {
|
if (path === beforePath) {
|
||||||
|
@ -21,6 +21,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
<FormSection>
|
||||||
|
<template #label>Google Analytics</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkInput v-model="googleAnalyticsId">
|
||||||
|
<template #prefix><i class="ti ti-report-analytics"></i></template>
|
||||||
|
<template #label>Google Analytics ID</template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@ -49,17 +59,20 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|||||||
|
|
||||||
const deeplAuthKey = ref<string>('');
|
const deeplAuthKey = ref<string>('');
|
||||||
const deeplIsPro = ref<boolean>(false);
|
const deeplIsPro = ref<boolean>(false);
|
||||||
|
const googleAnalyticsId = ref<string>('');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
deeplAuthKey.value = meta.deeplAuthKey;
|
deeplAuthKey.value = meta.deeplAuthKey;
|
||||||
deeplIsPro.value = meta.deeplIsPro;
|
deeplIsPro.value = meta.deeplIsPro;
|
||||||
|
googleAnalyticsId.value = meta.googleAnalyticsId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
os.apiWithDialog('admin/update-meta', {
|
||||||
deeplAuthKey: deeplAuthKey.value,
|
deeplAuthKey: deeplAuthKey.value,
|
||||||
deeplIsPro: deeplIsPro.value,
|
deeplIsPro: deeplIsPro.value,
|
||||||
|
googleAnalyticsId: googleAnalyticsId.value,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance(true);
|
fetchInstance(true);
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<div v-if="$i && !announcement.silence && !announcement.isRead" :class="$style.footer">
|
<div v-if="$i && !announcement.silence && !announcement.isRead" :class="$style.footer">
|
||||||
<MkButton primary gradate @click="read(announcement)">
|
<MkButton primary gradate @click="read(announcement)">
|
||||||
<i :class="!announcement.needEnrollmentTutorialToRead ? 'ti ti-check' : 'ti ti-presentation'"/>
|
<i :class="!announcement.needEnrollmentTutorialToRead ? 'ti ti-check' : 'ti ti-presentation'"/>
|
||||||
{{ !announcement.needEnrollmentTutorialToRead ? i18n.ts.gotIt : i18n.ts._initialAccountSetting.startTutorial }}
|
{{ !announcement.needEnrollmentTutorialToRead ? i18n.ts.gotIt : i18n.ts._initialTutorial.launchTutorial }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
220
packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
Normal file
220
packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkWindow
|
||||||
|
ref="windowEl"
|
||||||
|
:initialWidth="400"
|
||||||
|
:initialHeight="500"
|
||||||
|
:canResize="true"
|
||||||
|
@close="windowEl?.close()"
|
||||||
|
@closed="emit('closed')"
|
||||||
|
>
|
||||||
|
<template v-if="avatarDecoration" #header>{{ avatarDecoration.name }}</template>
|
||||||
|
<template v-else #header>New decoration</template>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: column; min-height: 100%;">
|
||||||
|
<MkSpacer :marginMin="20" :marginMax="28" style="flex-grow: 1;">
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<div :class="$style.preview">
|
||||||
|
<div :class="[$style.previewItem, $style.light]">
|
||||||
|
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="url != '' ? [{ url }] : []" forceShowDecoration/>
|
||||||
|
</div>
|
||||||
|
<div :class="[$style.previewItem, $style.dark]">
|
||||||
|
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="url != '' ? [{ url }] : []" forceShowDecoration/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MkInput v-model="name">
|
||||||
|
<template #label>{{ i18n.ts.name }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="url">
|
||||||
|
<template #label>{{ i18n.ts.imageUrl }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkTextarea v-model="description">
|
||||||
|
<template #label>{{ i18n.ts.description }}</template>
|
||||||
|
</MkTextarea>
|
||||||
|
<MkFolder>
|
||||||
|
<template #label>{{ i18n.ts.availableRoles }}</template>
|
||||||
|
<template #suffix>{{ rolesThatCanBeUsedThisDecoration.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisDecoration.length }}</template>
|
||||||
|
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
|
||||||
|
<div v-for="role in rolesThatCanBeUsedThisDecoration" :key="role.id" :class="$style.roleItem">
|
||||||
|
<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
|
||||||
|
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||||
|
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
<MkButton v-if="avatarDecoration" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
<div :class="$style.footer">
|
||||||
|
<MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.avatarDecoration ? i18n.ts.update : i18n.ts.create }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, watch, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
import { signinRequired } from '@/account.js';
|
||||||
|
|
||||||
|
const $i = signinRequired();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
avatarDecoration?: any,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
||||||
|
(ev: 'closed'): void
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
||||||
|
const url = ref<string>(props.avatarDecoration ? props.avatarDecoration.url : '');
|
||||||
|
const name = ref<string>(props.avatarDecoration ? props.avatarDecoration.name : '');
|
||||||
|
const description = ref<string>(props.avatarDecoration ? props.avatarDecoration.description : '');
|
||||||
|
const roleIdsThatCanBeUsedThisDecoration = ref(props.avatarDecoration ? props.avatarDecoration.roleIdsThatCanBeUsedThisDecoration : []);
|
||||||
|
const rolesThatCanBeUsedThisDecoration = ref<Misskey.entities.Role[]>([]);
|
||||||
|
|
||||||
|
watch(roleIdsThatCanBeUsedThisDecoration, async () => {
|
||||||
|
rolesThatCanBeUsedThisDecoration.value = (await Promise.all(roleIdsThatCanBeUsedThisDecoration.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
async function addRole() {
|
||||||
|
const roles = await misskeyApi('admin/roles/list');
|
||||||
|
const currentRoleIds = rolesThatCanBeUsedThisDecoration.value.map(x => x.id);
|
||||||
|
|
||||||
|
const { canceled, result: role } = await os.select({
|
||||||
|
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
|
||||||
|
});
|
||||||
|
if (canceled || role == null) return;
|
||||||
|
|
||||||
|
rolesThatCanBeUsedThisDecoration.value.push(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeRole(role, ev) {
|
||||||
|
rolesThatCanBeUsedThisDecoration.value = rolesThatCanBeUsedThisDecoration.value.filter(x => x.id !== role.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function done() {
|
||||||
|
const params = {
|
||||||
|
url: url.value,
|
||||||
|
name: name.value,
|
||||||
|
description: description.value,
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: rolesThatCanBeUsedThisDecoration.value.map(x => x.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.avatarDecoration) {
|
||||||
|
await os.apiWithDialog('admin/avatar-decorations/update', {
|
||||||
|
id: props.avatarDecoration.id,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
emit('done', {
|
||||||
|
updated: {
|
||||||
|
id: props.avatarDecoration.id,
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
windowEl.value?.close();
|
||||||
|
} else {
|
||||||
|
const created = await os.apiWithDialog('admin/avatar-decorations/create', params);
|
||||||
|
|
||||||
|
emit('done', {
|
||||||
|
created: created,
|
||||||
|
});
|
||||||
|
|
||||||
|
windowEl.value?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del() {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.tsx.removeAreYouSure({ x: name.value }),
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
misskeyApi('admin/avatar-decorations/delete', {
|
||||||
|
id: props.avatarDecoration.id,
|
||||||
|
}).then(() => {
|
||||||
|
emit('done', {
|
||||||
|
deleted: true,
|
||||||
|
});
|
||||||
|
windowEl.value?.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.preview {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
gap: var(--margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewItem {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 160px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.roleItem {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roleUnassign {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-left: 8px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 10000;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-top: solid 0.5px var(--divider);
|
||||||
|
background: var(--acrylicBg);
|
||||||
|
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||||
|
backdrop-filter: var(--blur, blur(15px));
|
||||||
|
}
|
||||||
|
</style>
|
@ -5,78 +5,39 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="900">
|
<MkSpacer :contentMax="900">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null">
|
<div :class="$style.decorations">
|
||||||
<template #label>{{ avatarDecoration.name }}</template>
|
<div
|
||||||
<template #caption>{{ avatarDecoration.description }}</template>
|
v-for="avatarDecoration in avatarDecorations"
|
||||||
|
:key="avatarDecoration.id"
|
||||||
<div class="_gaps_m">
|
v-panel
|
||||||
<MkInput v-model="avatarDecoration.name">
|
:class="$style.decoration"
|
||||||
<template #label>{{ i18n.ts.name }}</template>
|
@click="edit(avatarDecoration)"
|
||||||
</MkInput>
|
>
|
||||||
<MkTextarea v-model="avatarDecoration.description">
|
<div :class="$style.decorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
|
||||||
<template #label>{{ i18n.ts.description }}</template>
|
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: avatarDecoration.url }]" forceShowDecoration/>
|
||||||
</MkTextarea>
|
|
||||||
<MkInput v-model="avatarDecoration.url">
|
|
||||||
<template #label>{{ i18n.ts.imageUrl }}</template>
|
|
||||||
</MkInput>
|
|
||||||
<div class="buttons _buttons">
|
|
||||||
<MkButton class="button" inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
|
||||||
<MkButton v-if="avatarDecoration.id != null" class="button" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, defineAsyncComponent } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import { signinRequired } from '@/account.js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
|
||||||
|
const $i = signinRequired();
|
||||||
|
|
||||||
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
|
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
|
||||||
|
|
||||||
function add() {
|
|
||||||
avatarDecorations.value.unshift({
|
|
||||||
_id: Math.random().toString(36),
|
|
||||||
id: null,
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
url: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function del(avatarDecoration) {
|
|
||||||
os.confirm({
|
|
||||||
type: 'warning',
|
|
||||||
text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }),
|
|
||||||
}).then(({ canceled }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
|
|
||||||
misskeyApi('admin/avatar-decorations/delete', avatarDecoration);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save(avatarDecoration) {
|
|
||||||
if (avatarDecoration.id == null) {
|
|
||||||
await os.apiWithDialog('admin/avatar-decorations/create', avatarDecoration);
|
|
||||||
load();
|
|
||||||
} else {
|
|
||||||
os.apiWithDialog('admin/avatar-decorations/update', avatarDecoration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
misskeyApi('admin/avatar-decorations/list').then(_avatarDecorations => {
|
misskeyApi('admin/avatar-decorations/list').then(_avatarDecorations => {
|
||||||
avatarDecorations.value = _avatarDecorations;
|
avatarDecorations.value = _avatarDecorations;
|
||||||
@ -85,6 +46,37 @@ function load() {
|
|||||||
|
|
||||||
load();
|
load();
|
||||||
|
|
||||||
|
async function add(ev: MouseEvent) {
|
||||||
|
const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), {
|
||||||
|
}, {
|
||||||
|
done: result => {
|
||||||
|
if (result.created) {
|
||||||
|
avatarDecorations.value.unshift(result.created);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closed: () => dispose(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit(avatarDecoration) {
|
||||||
|
const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), {
|
||||||
|
avatarDecoration: avatarDecoration,
|
||||||
|
}, {
|
||||||
|
done: result => {
|
||||||
|
if (result.updated) {
|
||||||
|
const index = avatarDecorations.value.findIndex(x => x.id === avatarDecoration.id);
|
||||||
|
avatarDecorations.value[index] = {
|
||||||
|
...avatarDecorations.value[index],
|
||||||
|
...result.updated,
|
||||||
|
};
|
||||||
|
} else if (result.deleted) {
|
||||||
|
avatarDecorations.value = avatarDecorations.value.filter(x => x.id !== avatarDecoration.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closed: () => dispose(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
asFullButton: true,
|
asFullButton: true,
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
@ -99,3 +91,28 @@ definePageMetadata(() => ({
|
|||||||
icon: 'ti ti-sparkles',
|
icon: 'ti ti-sparkles',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.decorations {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
|
grid-gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decoration {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 16px 16px 28px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 90%;
|
||||||
|
overflow: clip;
|
||||||
|
contain: content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decorationName {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -5,12 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :contentMax="1200">
|
<MkSpacer :contentMax="1200">
|
||||||
<MkTab v-model="origin" style="margin-bottom: var(--margin);">
|
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
|
||||||
</MkTab>
|
|
||||||
<div v-if="origin === 'local'">
|
|
||||||
<template v-if="tag == null">
|
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-pinned-users">
|
<MkFoldableSection class="_margin" persistKey="explore-pinned-users">
|
||||||
<template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
|
<template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
|
||||||
<MkUserList :pagination="pinnedUsers"/>
|
<MkUserList :pagination="pinnedUsers"/>
|
||||||
@ -19,122 +13,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
||||||
<MkUserList :pagination="popularUsers"/>
|
<MkUserList :pagination="popularUsers"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-recently-updated-users">
|
|
||||||
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
|
||||||
<MkUserList :pagination="recentlyUpdatedUsers"/>
|
|
||||||
</MkFoldableSection>
|
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-recently-registered-users">
|
|
||||||
<template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template>
|
|
||||||
<MkUserList :pagination="recentlyRegisteredUsers"/>
|
|
||||||
</MkFoldableSection>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<MkFoldableSection ref="tagsEl" :foldable="true" :expanded="false" class="_margin">
|
|
||||||
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularTags }}</template>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<MkA v-for="tag in tagsLocal" :key="'local:' + tag.tag" :to="`/user-tags/${tag.tag}`" style="margin-right: 16px; font-weight: bold;">{{ tag.tag }}</MkA>
|
|
||||||
<MkA v-for="tag in tagsRemote" :key="'remote:' + tag.tag" :to="`/user-tags/${tag.tag}`" style="margin-right: 16px;">{{ tag.tag }}</MkA>
|
|
||||||
</div>
|
|
||||||
</MkFoldableSection>
|
|
||||||
|
|
||||||
<MkFoldableSection v-if="tag != null" :key="`${tag}`" class="_margin">
|
|
||||||
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
|
|
||||||
<MkUserList :pagination="tagUsers"/>
|
|
||||||
</MkFoldableSection>
|
|
||||||
|
|
||||||
<template v-if="tag == null">
|
|
||||||
<MkFoldableSection class="_margin">
|
|
||||||
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
|
||||||
<MkUserList :pagination="popularUsersF"/>
|
|
||||||
</MkFoldableSection>
|
|
||||||
<MkFoldableSection class="_margin">
|
|
||||||
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
|
||||||
<MkUserList :pagination="recentlyUpdatedUsersF"/>
|
|
||||||
</MkFoldableSection>
|
|
||||||
<MkFoldableSection class="_margin">
|
|
||||||
<template #header><i class="ti ti-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template>
|
|
||||||
<MkUserList :pagination="recentlyRegisteredUsersF"/>
|
|
||||||
</MkFoldableSection>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, shallowRef, computed } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import MkUserList from '@/components/MkUserList.vue';
|
import MkUserList from '@/components/MkUserList.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkTab from '@/components/MkTab.vue';
|
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
tag?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const origin = ref('local');
|
|
||||||
const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
|
|
||||||
const tagsLocal = ref<Misskey.entities.Hashtag[]>([]);
|
|
||||||
const tagsRemote = ref<Misskey.entities.Hashtag[]>([]);
|
|
||||||
|
|
||||||
watch(() => props.tag, () => {
|
|
||||||
if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
|
|
||||||
});
|
|
||||||
|
|
||||||
const tagUsers = computed(() => ({
|
|
||||||
endpoint: 'hashtags/users' as const,
|
|
||||||
limit: 30,
|
|
||||||
params: {
|
|
||||||
tag: props.tag,
|
|
||||||
origin: 'combined',
|
|
||||||
sort: '+follower',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
|
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
|
||||||
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
const popularUsers = { endpoint: 'users', limit: 30, noPaging: true, params: {
|
||||||
state: 'alive',
|
state: 'alive',
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
sort: '+pv',
|
sort: '+pv',
|
||||||
} };
|
} };
|
||||||
const recentlyUpdatedUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
|
||||||
origin: 'local',
|
|
||||||
sort: '+updatedAt',
|
|
||||||
} };
|
|
||||||
const recentlyRegisteredUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
|
||||||
origin: 'local',
|
|
||||||
state: 'alive',
|
|
||||||
sort: '+createdAt',
|
|
||||||
} };
|
|
||||||
const popularUsersF = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
|
||||||
state: 'alive',
|
|
||||||
origin: 'remote',
|
|
||||||
sort: '+follower',
|
|
||||||
} };
|
|
||||||
const recentlyUpdatedUsersF = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
|
||||||
origin: 'combined',
|
|
||||||
sort: '+updatedAt',
|
|
||||||
} };
|
|
||||||
const recentlyRegisteredUsersF = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
|
||||||
origin: 'combined',
|
|
||||||
sort: '+createdAt',
|
|
||||||
} };
|
|
||||||
|
|
||||||
misskeyApi('hashtags/list', {
|
|
||||||
sort: '+attachedLocalUsers',
|
|
||||||
attachedToLocalUserOnly: true,
|
|
||||||
limit: 30,
|
|
||||||
}).then(tags => {
|
|
||||||
tagsLocal.value = tags;
|
|
||||||
});
|
|
||||||
misskeyApi('hashtags/list', {
|
|
||||||
sort: '+attachedRemoteUsers',
|
|
||||||
attachedToRemoteUserOnly: true,
|
|
||||||
limit: 30,
|
|
||||||
}).then(tags => {
|
|
||||||
tagsRemote.value = tags;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -10,12 +10,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
>
|
>
|
||||||
<div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ decoration.name }}</MkCondensedLine></div>
|
<div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ decoration.name }}</MkCondensedLine></div>
|
||||||
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH, offsetX, offsetY }]" forceShowDecoration/>
|
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH, offsetX, offsetY }]" forceShowDecoration/>
|
||||||
<i v-if="decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.lock" class="ti ti-lock"></i>
|
<i v-if="locked" :class="$style.lock" class="ti ti-lock"></i>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { signinRequired } from '@/account.js';
|
import { signinRequired } from '@/account.js';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
@ -37,6 +37,8 @@ const props = defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'click'): void;
|
(ev: 'click'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id)));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@ -67,5 +69,6 @@ const emit = defineEmits<{
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
|
color: var(--warn);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div :class="$style.footer" class="_buttonsCenter">
|
<div :class="$style.footer" class="_buttonsCenter">
|
||||||
<MkButton v-if="usingIndex != null" primary rounded @click="update"><i class="ti ti-check"></i> {{ i18n.ts.update }}</MkButton>
|
<MkButton v-if="usingIndex != null" primary rounded @click="update"><i class="ti ti-check"></i> {{ i18n.ts.update }}</MkButton>
|
||||||
<MkButton v-if="usingIndex != null" rounded @click="detach"><i class="ti ti-x"></i> {{ i18n.ts.detach }}</MkButton>
|
<MkButton v-if="usingIndex != null" rounded @click="detach"><i class="ti ti-x"></i> {{ i18n.ts.detach }}</MkButton>
|
||||||
<MkButton v-else :disabled="exceeded" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton>
|
<MkButton v-else :disabled="exceeded || locked" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
@ -61,6 +61,7 @@ const props = defineProps<{
|
|||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: string[];
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0);
|
const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0);
|
||||||
|
const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id)));
|
||||||
const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0);
|
const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0);
|
||||||
const flipH = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].flipH : null) ?? false);
|
const flipH = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].flipH : null) ?? false);
|
||||||
const offsetX = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetX : null) ?? 0);
|
const offsetX = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetX : null) ?? 0);
|
||||||
|
@ -34,12 +34,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton v-if="periodChanged" primary class="save" @click="saveRemovalCondition"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton v-if="periodChanged" primary class="save" @click="saveRemovalCondition"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
|
|
||||||
<MkSwitch v-model="noPiningNotes" @update:modelValue="saveRemovalCondition()">
|
<MkSwitch v-model="noPiningNotes" @update:modelValue="saveRemovalCondition">
|
||||||
<template #label>{{ i18n.ts._autoRemoval.noPiningNotes }}</template>
|
<template #label>{{ i18n.ts._autoRemoval.noPiningNotes }}</template>
|
||||||
<template #caption>{{ i18n.ts._autoRemoval.noPiningNotesDescription }}</template>
|
<template #caption>{{ i18n.ts._autoRemoval.noPiningNotesDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
<MkSwitch v-model="noSpecifiedNotes" @update:modelValue="saveRemovalCondition()">
|
<MkSwitch v-model="noSpecifiedNotes" @update:modelValue="saveRemovalCondition">
|
||||||
<template #label>{{ i18n.ts._autoRemoval.noSpecifiedNotes }}</template>
|
<template #label>{{ i18n.ts._autoRemoval.noSpecifiedNotes }}</template>
|
||||||
<template #caption>{{ i18n.ts._autoRemoval.noSpecifiedNotesDescription }}</template>
|
<template #caption>{{ i18n.ts._autoRemoval.noSpecifiedNotesDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
@ -131,6 +131,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection v-if="iAmModerator">
|
||||||
|
<template #label><i class="ti ti-aperture"></i> EZPZ User Normalization Menu™</template>
|
||||||
|
<template #description>{{ i18n.ts.normalizeDescription }}</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkInfo warn rounded>
|
||||||
|
{{ i18n.ts.thisIsExperimentalFeature }}
|
||||||
|
</MkInfo>
|
||||||
|
|
||||||
|
<MkSwitch v-model="mapleDirectorMode">
|
||||||
|
{{ i18n.ts.useNormalization }}
|
||||||
|
</MkSwitch>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -141,6 +156,8 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
|||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { iAmModerator, signinRequired } from '@/account.js';
|
import { iAmModerator, signinRequired } from '@/account.js';
|
||||||
@ -148,11 +165,9 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import MkInput from "@/components/MkInput.vue";
|
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
const isVacation = ref<boolean | undefined>($i.isVacation !== null ? $i.isVacation : undefined);
|
const isVacation = ref<boolean>($i.isVacation ?? false);
|
||||||
const autoRemoval = ref<boolean>($i.autoRemovalCondition.active);
|
const autoRemoval = ref<boolean>($i.autoRemovalCondition.active);
|
||||||
const deleteAfter = ref<number>($i.autoRemovalCondition.deleteAfter || 7);
|
const deleteAfter = ref<number>($i.autoRemovalCondition.deleteAfter || 7);
|
||||||
const noPiningNotes = ref<boolean>($i.autoRemovalCondition.noPiningNotes);
|
const noPiningNotes = ref<boolean>($i.autoRemovalCondition.noPiningNotes);
|
||||||
@ -166,6 +181,7 @@ const hideDirectMessages = computed(defaultStore.makeGetterSetter('hideDirectMes
|
|||||||
const hideDriveFileList = computed(defaultStore.makeGetterSetter('hideDriveFileList'));
|
const hideDriveFileList = computed(defaultStore.makeGetterSetter('hideDriveFileList'));
|
||||||
const hideModerationLog = computed(defaultStore.makeGetterSetter('hideModerationLog'));
|
const hideModerationLog = computed(defaultStore.makeGetterSetter('hideModerationLog'));
|
||||||
const hideRoleList = computed(defaultStore.makeGetterSetter('hideRoleList'));
|
const hideRoleList = computed(defaultStore.makeGetterSetter('hideRoleList'));
|
||||||
|
const mapleDirectorMode = computed(defaultStore.makeGetterSetter('mapleDirectorMode'));
|
||||||
|
|
||||||
function saveRemovalCondition() {
|
function saveRemovalCondition() {
|
||||||
misskeyApi('i/update-removal-condition', {
|
misskeyApi('i/update-removal-condition', {
|
||||||
@ -204,6 +220,7 @@ watch([
|
|||||||
hideDriveFileList,
|
hideDriveFileList,
|
||||||
hideRoleList,
|
hideRoleList,
|
||||||
hideModerationLog,
|
hideModerationLog,
|
||||||
|
mapleDirectorMode,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk();
|
||||||
});
|
});
|
||||||
|
@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkUserCardMini :user="item.mutee"/>
|
<MkUserCardMini :user="item.mutee"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
|
||||||
<button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
|
<button class="_button" :class="$style.remove" @click="editMute(item, $event)"><i class="ti ti-dots"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
|
||||||
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||||
@ -216,8 +216,20 @@ async function unrenoteMute(user, ev) {
|
|||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unmute(user, ev) {
|
async function editMute(muting, ev) {
|
||||||
os.popupMenu([{
|
os.popupMenu([...(muting.withNotification === false ? [{
|
||||||
|
icon: 'ti ti-bell',
|
||||||
|
text: i18n.ts.muteNotification,
|
||||||
|
action: async () => {
|
||||||
|
await os.apiWithDialog('mute/edit', { userId: muting.mutee.id, withNotification: true });
|
||||||
|
},
|
||||||
|
}] : [{
|
||||||
|
icon: 'ti ti-bell-off',
|
||||||
|
text: i18n.ts.unmuteNotification,
|
||||||
|
action: async () => {
|
||||||
|
await os.apiWithDialog('mute/edit', { userId: muting.mutee.id, withNotification: false });
|
||||||
|
},
|
||||||
|
}]), {
|
||||||
text: i18n.ts.unmute,
|
text: i18n.ts.unmute,
|
||||||
icon: 'ti ti-x',
|
icon: 'ti ti-x',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkSwitch v-model="rememberNoteVisibility">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
|
<MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
|
||||||
<MkFolder v-if="!rememberNoteVisibility">
|
<MkFolder v-if="!rememberNoteVisibility">
|
||||||
<template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
|
<template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
|
||||||
<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
|
<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
|
||||||
@ -67,20 +67,49 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSwitch v-model="keepCw">{{ i18n.ts.keepCw }}</MkSwitch>
|
<MkSwitch v-model="keepCw">{{ i18n.ts.keepCw }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection v-if="instance.googleAnalyticsId">
|
||||||
|
<MkFolder :defaultOpen="true">
|
||||||
|
<template #icon><i class="ti ti-lock-square"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.gtagConsentCustomize }}</template>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkInfo>{{ i18n.tsx.gtagConsentCustomizeDescription({ host: instance.name ?? host }) }}</MkInfo>
|
||||||
|
<MkSwitch v-model="gtagConsentAnalytics">
|
||||||
|
{{ i18n.ts.gtagConsentAnalytics }}
|
||||||
|
<template #caption>{{ i18n.ts.gtagConsentAnalyticsDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="gtagConsentFunctionality">
|
||||||
|
{{ i18n.ts.gtagConsentFunctionality }}
|
||||||
|
<template #caption>{{ i18n.ts.gtagConsentFunctionalityDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="gtagConsentPersonalization">
|
||||||
|
{{ i18n.ts.gtagConsentPersonalization }}
|
||||||
|
<template #caption>{{ i18n.ts.gtagConsentPersonalizationDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
</FormSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import FormSection from '@/components/form/section.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { signinRequired } from '@/account.js';
|
import { signinRequired } from '@/account.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
import { host } from '@/config.js';
|
||||||
|
import { GtagConsent, GtagConsentParams } from 'vue-gtag';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
|
||||||
@ -99,6 +128,81 @@ const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNote
|
|||||||
const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
|
const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
|
||||||
const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
|
const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
|
||||||
|
|
||||||
|
const gaConsentInternal = ref(miLocalStorage.getItem('gaConsent') === 'true');
|
||||||
|
const gaConsent = computed({
|
||||||
|
get: () => gaConsentInternal.value,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
miLocalStorage.setItem('gaConsent', value ? 'true' : 'false');
|
||||||
|
gaConsentInternal.value = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const gtagConsentInternal = ref(
|
||||||
|
(miLocalStorage.getItemAsJson('gtagConsent') as GtagConsentParams) ?? {
|
||||||
|
ad_storage: 'denied',
|
||||||
|
ad_user_data: 'denied',
|
||||||
|
ad_personalization: 'denied',
|
||||||
|
analytics_storage: 'denied',
|
||||||
|
functionality_storage: 'denied',
|
||||||
|
personalization_storage: 'denied',
|
||||||
|
security_storage: 'granted',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const gtagConsent = computed({
|
||||||
|
get: () => gtagConsentInternal.value,
|
||||||
|
set: (value: GtagConsentParams) => {
|
||||||
|
miLocalStorage.setItemAsJson('gtagConsent', value);
|
||||||
|
gtagConsentInternal.value = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const gtagConsentAnalytics = computed({
|
||||||
|
get: () => gtagConsent.value.analytics_storage === 'granted',
|
||||||
|
set: (value: boolean) => {
|
||||||
|
gtagConsent.value = {
|
||||||
|
...gtagConsent.value,
|
||||||
|
ad_storage: value ? 'granted' : 'denied',
|
||||||
|
ad_user_data: value ? 'granted' : 'denied',
|
||||||
|
analytics_storage: value ? 'granted' : 'denied',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const gtagConsentFunctionality = computed({
|
||||||
|
get: () => gtagConsent.value.functionality_storage === 'granted',
|
||||||
|
set: (value: boolean) => {
|
||||||
|
gtagConsent.value = {
|
||||||
|
...gtagConsent.value,
|
||||||
|
functionality_storage: value ? 'granted' : 'denied',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const gtagConsentPersonalization = computed({
|
||||||
|
get: () => gtagConsent.value.personalization_storage === 'granted',
|
||||||
|
set: (value: boolean) => {
|
||||||
|
gtagConsent.value = {
|
||||||
|
...gtagConsent.value,
|
||||||
|
ad_personalization: value ? 'granted' : 'denied',
|
||||||
|
personalization_storage: value ? 'granted' : 'denied',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function reloadAsk() {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'info',
|
||||||
|
text: i18n.ts.reloadToApplySetting,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
unisonReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(gaConsent, async () => {
|
||||||
|
await reloadAsk();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(gtagConsent, async () => {
|
||||||
|
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent.value);
|
||||||
|
});
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
misskeyApi('i/update', {
|
misskeyApi('i/update', {
|
||||||
isLocked: !!isLocked.value,
|
isLocked: !!isLocked.value,
|
||||||
|
@ -1,100 +1,88 @@
|
|||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<div>
|
||||||
<template #header><MkPageHeader/></template>
|
<MkAnimBg style="position: fixed; top: 0;"/>
|
||||||
<MkSpacer :contentMax="800">
|
<div :class="$style.formContainer">
|
||||||
<div v-if="$i && !loading">
|
<div :class="$style.form">
|
||||||
<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
|
<MkAuthConfirm
|
||||||
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
ref="authRoot"
|
||||||
<div :class="$style.buttons">
|
:name="name"
|
||||||
<MkButton @click="onCancel">{{ i18n.ts.cancel }}</MkButton>
|
@accept="onAccept"
|
||||||
<MkButton primary @click="onAccept">{{ i18n.ts.accept }}</MkButton>
|
@deny="onDeny"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="$i && loading">
|
|
||||||
<div>{{ i18n.ts._auth.callback }}</div>
|
|
||||||
<MkLoading class="loading"/>
|
|
||||||
<div style="display: none">
|
|
||||||
<form ref="postBindingForm" method="post" :action="actionUrl" autocomplete="off">
|
|
||||||
<input v-for="(value, key) in actionContext" :key="key" :name="key" :value="value" type="hidden"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
|
|
||||||
<MkSignin @login="onLogin"/>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, nextTick, onMounted } from 'vue';
|
import MkAnimBg from '@/components/MkAnimBg.vue';
|
||||||
import MkSignin from '@/components/MkSignin.vue';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
|
||||||
import { $i, login } from '@/account.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import MkAuthConfirm from '@/components/MkAuthConfirm.vue';
|
||||||
|
import { nextTick, onMounted, useTemplateRef } from "vue";
|
||||||
|
import { $i } from "@/account.js";
|
||||||
|
|
||||||
const transactionIdMeta = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:transaction-id"]');
|
const transactionIdMeta = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:transaction-id"]');
|
||||||
if (transactionIdMeta) {
|
if (transactionIdMeta) {
|
||||||
transactionIdMeta.remove();
|
transactionIdMeta.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:service-name"]')?.content;
|
const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:service-name"]')?.content;
|
||||||
const kind = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:kind"]')?.content;
|
const kind = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:kind"]')?.content;
|
||||||
const prompt = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:prompt"]')?.content;
|
const prompt = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:prompt"]')?.content;
|
||||||
|
|
||||||
const loading = ref(false);
|
const authRoot = useTemplateRef('authRoot');
|
||||||
const postBindingForm = ref<HTMLFormElement | null>(null);
|
|
||||||
const actionUrl = ref<string | undefined>(undefined);
|
|
||||||
const actionContext = ref<Record<string, string> | null>(null);
|
|
||||||
|
|
||||||
function onLogin(res): void {
|
async function onAccept(token: string) {
|
||||||
login(res.i);
|
const result = await fetch(`/sso/${kind}/authorize`, {
|
||||||
}
|
|
||||||
|
|
||||||
function onCancel(): void {
|
|
||||||
if (history.length > 1) history.back();
|
|
||||||
else location.href = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAccept(): void {
|
|
||||||
loading.value = true;
|
|
||||||
os.promiseDialog(authorize());
|
|
||||||
}
|
|
||||||
|
|
||||||
async function authorize(): Promise<void> {
|
|
||||||
const res = await fetch(`/sso/${kind}/authorize`, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
transaction_id: transactionIdMeta?.content,
|
transaction_id: transactionIdMeta?.content,
|
||||||
login_token: $i!.token,
|
login_token: token,
|
||||||
}),
|
}),
|
||||||
|
}).then(res => res.json()).catch(() => {
|
||||||
|
authRoot.value?.showUI('failed');
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
|
||||||
if (json.binding === 'post') {
|
if (!result) return;
|
||||||
actionUrl.value = json.action;
|
authRoot.value?.showUI('success');
|
||||||
actionContext.value = json.context;
|
|
||||||
|
if (result.binding === 'post') {
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.style.display = 'none';
|
||||||
|
form.action = result.action;
|
||||||
|
form.method = 'post';
|
||||||
|
form.acceptCharset = 'utf-8';
|
||||||
|
|
||||||
|
for (const [name, value] of Object.entries(result.context)) {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = name;
|
||||||
|
input.value = value as string;
|
||||||
|
form.appendChild(input);
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
postBindingForm.value?.submit();
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
location.href = json.action;
|
location.href = result.action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDeny(token: string) {
|
||||||
|
authRoot.value?.showUI('denied');
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
if ($i && prompt === 'none') {
|
if ($i && prompt === 'none') {
|
||||||
onAccept();
|
onAccept($i.token);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
definePageMetadata(() => ({
|
||||||
@ -104,15 +92,24 @@ definePageMetadata(() => ({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.buttons {
|
.formContainer {
|
||||||
margin-top: 16px;
|
min-height: 100svh;
|
||||||
display: flex;
|
padding: 32px 32px calc(env(safe-area-inset-bottom, 0px) + 32px) 32px;
|
||||||
gap: 8px;
|
box-sizing: border-box;
|
||||||
flex-wrap: wrap;
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loginMessage {
|
.form {
|
||||||
text-align: center;
|
position: relative;
|
||||||
margin: 8px 0 24px;
|
z-index: 10;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background-color: var(--panel);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: clip;
|
||||||
|
max-width: 500px;
|
||||||
|
width: calc(100vw - 64px);
|
||||||
|
height: min(65svh, calc(100svh - calc(env(safe-area-inset-bottom, 0px) + 64px)));
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { ShallowRef } from 'vue';
|
import { ShallowRef } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
|
import { AfterNavigationHook, IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
|
||||||
|
|
||||||
function getMainRouter(): IRouter {
|
function getMainRouter(): IRouter {
|
||||||
const router = mainRouterHolder;
|
const router = mainRouterHolder;
|
||||||
@ -40,6 +40,10 @@ class MainRouterProxy implements IRouter {
|
|||||||
this.supplier = supplier;
|
this.supplier = supplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get options(): { [key: string]: any } {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
get current(): Resolved {
|
get current(): Resolved {
|
||||||
return this.supplier().current;
|
return this.supplier().current;
|
||||||
}
|
}
|
||||||
@ -60,6 +64,10 @@ class MainRouterProxy implements IRouter {
|
|||||||
this.supplier().navHook = value;
|
this.supplier().navHook = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isReady(): Promise<boolean> {
|
||||||
|
return this.supplier().isReady();
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentKey(): string {
|
getCurrentKey(): string {
|
||||||
return this.supplier().getCurrentKey();
|
return this.supplier().getCurrentKey();
|
||||||
}
|
}
|
||||||
@ -68,6 +76,10 @@ class MainRouterProxy implements IRouter {
|
|||||||
return this.supplier().getCurrentPath();
|
return this.supplier().getCurrentPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
afterEach(hook: AfterNavigationHook): AfterNavigationHook | undefined {
|
||||||
|
return this.supplier().afterEach(hook);
|
||||||
|
}
|
||||||
|
|
||||||
push(path: string, flag?: any): void {
|
push(path: string, flag?: any): void {
|
||||||
this.supplier().push(path, flag);
|
this.supplier().push(path, flag);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ export function createAiScriptEnv(opts) {
|
|||||||
}
|
}
|
||||||
const actualToken: string|null = token?.value ?? opts.token ?? null;
|
const actualToken: string|null = token?.value ?? opts.token ?? null;
|
||||||
if (!rateLimiter.hit(ep.value)) return values.ERROR('rate_limited', values.NULL);
|
if (!rateLimiter.hit(ep.value)) return values.ERROR('rate_limited', values.NULL);
|
||||||
return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => {
|
return misskeyApi(ep.value, utils.valToJs(param), actualToken, undefined, 'aiscript').then(res => {
|
||||||
return utils.jsToVal(res);
|
return utils.jsToVal(res);
|
||||||
}, err => {
|
}, err => {
|
||||||
return values.ERROR('request_failed', utils.jsToVal(err));
|
return values.ERROR('request_failed', utils.jsToVal(err));
|
||||||
|
@ -116,7 +116,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function userInfoUpdate() {
|
async function userInfoUpdate() {
|
||||||
os.apiWithDialog('federation/update-remote-user', {
|
await os.apiWithDialog('federation/update-remote-user', {
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function immediateUserNormalization() {
|
||||||
|
if (!await getConfirmed(i18n.ts.normalizeConfirm)) return;
|
||||||
|
|
||||||
|
await os.apiWithDialog('admin/normalization', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -344,6 +352,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
|||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($i && iAmModerator && defaultStore.state.mapleDirectorMode) {
|
||||||
|
menu = menu.concat([{ type: 'divider' }, {
|
||||||
|
icon: 'ti ti-aperture',
|
||||||
|
text: i18n.ts.normalize,
|
||||||
|
action: immediateUserNormalization,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
if (user.host !== null) {
|
if (user.host !== null) {
|
||||||
menu = menu.concat([{ type: 'divider' }, {
|
menu = menu.concat([{ type: 'divider' }, {
|
||||||
icon: 'ti ti-refresh',
|
icon: 'ti ti-refresh',
|
||||||
|
@ -7,8 +7,39 @@ import * as Misskey from 'misskey-js';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { apiUrl } from '@/config.js';
|
import { apiUrl } from '@/config.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
export const pendingApiRequestsCount = ref(0);
|
export const pendingApiRequestsCount = ref(0);
|
||||||
|
|
||||||
|
let id: string | null = miLocalStorage.getItem('id');
|
||||||
|
export function generateClientTransactionId(initiator: string) {
|
||||||
|
if (id === null) {
|
||||||
|
id = crypto.randomUUID();
|
||||||
|
miLocalStorage.setItem('id', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${id}-${initiator}-${crypto.randomUUID()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResponse<_ResT>(
|
||||||
|
resolve: (value: (_ResT | PromiseLike<_ResT>)) => void,
|
||||||
|
reject: (reason?: any) => void,
|
||||||
|
): ((value: Response) => (void | PromiseLike<void>)) {
|
||||||
|
return async (res) => {
|
||||||
|
if (res.ok && res.status !== 204) {
|
||||||
|
const body = await res.json();
|
||||||
|
resolve(body);
|
||||||
|
} else if (res.status === 204) {
|
||||||
|
resolve(undefined as _ResT); // void -> undefined
|
||||||
|
} else {
|
||||||
|
// エラー応答で JSON.parse に失敗した場合は HTTP ステータスコードとメッセージを返す
|
||||||
|
const body = await res
|
||||||
|
.json()
|
||||||
|
.catch(() => ({ statusCode: res.status, message: res.statusText }));
|
||||||
|
reject(typeof body.error === 'object' ? body.error : body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Implements Misskey.api.ApiClient.request
|
// Implements Misskey.api.ApiClient.request
|
||||||
export function misskeyApi<
|
export function misskeyApi<
|
||||||
ResT = void,
|
ResT = void,
|
||||||
@ -20,6 +51,7 @@ export function misskeyApi<
|
|||||||
data: P = {} as any,
|
data: P = {} as any,
|
||||||
token?: string | null | undefined,
|
token?: string | null | undefined,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
|
initiator: string = 'misskey',
|
||||||
): Promise<_ResT> {
|
): Promise<_ResT> {
|
||||||
if (endpoint.includes('://')) throw new Error('invalid endpoint');
|
if (endpoint.includes('://')) throw new Error('invalid endpoint');
|
||||||
pendingApiRequestsCount.value++;
|
pendingApiRequestsCount.value++;
|
||||||
@ -41,20 +73,10 @@ export function misskeyApi<
|
|||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-Client-Transaction-Id': generateClientTransactionId(initiator),
|
||||||
},
|
},
|
||||||
signal,
|
signal,
|
||||||
}).then(async (res) => {
|
}).then(handleResponse(resolve, reject)).catch(reject);
|
||||||
if (res.ok && res.status !== 204) {
|
|
||||||
const body = await res.json();
|
|
||||||
resolve(body);
|
|
||||||
} else if (res.status === 204) {
|
|
||||||
resolve(undefined as _ResT); // void -> undefined
|
|
||||||
} else {
|
|
||||||
// エラー応答で JSON.parse に失敗した場合は HTTP ステータスコードとメッセージを返す
|
|
||||||
const body = await res.json().catch(() => ({ statusCode: res.status, message: res.statusText }));
|
|
||||||
reject(typeof body.error === 'object' ? body.error : body);
|
|
||||||
}
|
|
||||||
}).catch(reject);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
promise.then(onFinally, onFinally);
|
promise.then(onFinally, onFinally);
|
||||||
@ -71,6 +93,7 @@ export function misskeyApiGet<
|
|||||||
>(
|
>(
|
||||||
endpoint: E,
|
endpoint: E,
|
||||||
data: P = {} as any,
|
data: P = {} as any,
|
||||||
|
initiator: string = 'misskey',
|
||||||
): Promise<_ResT> {
|
): Promise<_ResT> {
|
||||||
pendingApiRequestsCount.value++;
|
pendingApiRequestsCount.value++;
|
||||||
|
|
||||||
@ -86,17 +109,10 @@ export function misskeyApiGet<
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
cache: 'default',
|
cache: 'default',
|
||||||
}).then(async (res) => {
|
headers: {
|
||||||
const body = res.status === 204 ? null : await res.json();
|
'X-Client-Transaction-Id': generateClientTransactionId(initiator),
|
||||||
|
},
|
||||||
if (res.status === 200) {
|
}).then(handleResponse(resolve, reject)).catch(reject);
|
||||||
resolve(body);
|
|
||||||
} else if (res.status === 204) {
|
|
||||||
resolve(undefined as _ResT); // void -> undefined
|
|
||||||
} else {
|
|
||||||
reject(body.error);
|
|
||||||
}
|
|
||||||
}).catch(reject);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
promise.then(onFinally, onFinally);
|
promise.then(onFinally, onFinally);
|
||||||
|
61
packages/frontend/src/scripts/usage-report.ts
Normal file
61
packages/frontend/src/scripts/usage-report.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/* eslint-disable id-denylist */
|
||||||
|
// buffering usage report data for 1 minute, then sending it to the server
|
||||||
|
// POST /api/usage [ { t: number, e: string, i: string, a: string } ]
|
||||||
|
// t: timestamp
|
||||||
|
// e: event type
|
||||||
|
// i: event initiator
|
||||||
|
// a: action
|
||||||
|
|
||||||
|
import { generateClientTransactionId } from '@/scripts/misskey-api.js';
|
||||||
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
import { GtagConsentParams } from 'vue-gtag';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
|
export interface UsageReport {
|
||||||
|
t: number;
|
||||||
|
e: string;
|
||||||
|
i: string;
|
||||||
|
a: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let disableUsageReport = !instance.googleAnalyticsId;
|
||||||
|
const usageReportBuffer: UsageReport[] = [];
|
||||||
|
let usageReportBufferTimer: number | null = null;
|
||||||
|
|
||||||
|
export function usageReport(data: UsageReport) {
|
||||||
|
if (disableUsageReport) return;
|
||||||
|
|
||||||
|
if (usageReportBuffer.length > 0) {
|
||||||
|
const last = usageReportBuffer[usageReportBuffer.length - 1];
|
||||||
|
if (last.t === data.t && last.e === data.e && last.a === data.a) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
usageReportBuffer.push(data);
|
||||||
|
if (usageReportBufferTimer === null) {
|
||||||
|
usageReportBufferTimer = window.setTimeout(() => {
|
||||||
|
sendUsageReport();
|
||||||
|
}, 60 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendUsageReport() {
|
||||||
|
if (usageReportBuffer.length === 0) return;
|
||||||
|
const data = usageReportBuffer.splice(0, usageReportBuffer.length);
|
||||||
|
usageReportBufferTimer = null;
|
||||||
|
|
||||||
|
if ((miLocalStorage.getItemAsJson('gtagConsent') as GtagConsentParams)?.ad_user_data !== 'granted') {
|
||||||
|
console.log('Usage report is not sent because the user has not consented to sharing data about ad interactions.');
|
||||||
|
disableUsageReport = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fetch('/api/usage', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
cache: 'no-cache',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Client-Transaction-Id': generateClientTransactionId('misskey'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -216,7 +216,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
filter: {
|
filter: {
|
||||||
withReplies: true,
|
withReplies: true,
|
||||||
withRenotes: true,
|
withRenotes: true,
|
||||||
withSensitive: true,
|
withSensitive: false,
|
||||||
onlyFiles: false,
|
onlyFiles: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -252,7 +252,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
animatedMfm: {
|
animatedMfm: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||||
},
|
},
|
||||||
advancedMfm: {
|
advancedMfm: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
@ -288,7 +288,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
emojiStyle: {
|
emojiStyle: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: 'twemoji', // twemoji / fluentEmoji / native
|
default: 'fluentEmoji', // twemoji / fluentEmoji / native
|
||||||
},
|
},
|
||||||
disableDrawer: {
|
disableDrawer: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
@ -436,7 +436,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
enableCondensedLineForAcct: {
|
enableCondensedLineForAcct: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: false,
|
default: true,
|
||||||
},
|
},
|
||||||
additionalUnicodeEmojiIndexes: {
|
additionalUnicodeEmojiIndexes: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
@ -448,7 +448,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
hideMutedNotes: {
|
hideMutedNotes: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: false,
|
default: true,
|
||||||
},
|
},
|
||||||
defaultWithReplies: {
|
defaultWithReplies: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
@ -473,7 +473,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
enableSeasonalScreenEffect: {
|
enableSeasonalScreenEffect: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: false,
|
default: true,
|
||||||
},
|
},
|
||||||
dropAndFusion: {
|
dropAndFusion: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
@ -488,7 +488,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
enableHorizontalSwipe: {
|
enableHorizontalSwipe: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: false,
|
||||||
},
|
},
|
||||||
trustedExternalWebsites: {
|
trustedExternalWebsites: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
@ -504,7 +504,11 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
},
|
},
|
||||||
alwaysConfirmFollow: {
|
alwaysConfirmFollow: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: false,
|
||||||
|
},
|
||||||
|
mapleDirectorMode: {
|
||||||
|
where: 'deviceAccount',
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
|
2
packages/frontend/vue-shims.d.ts
vendored
2
packages/frontend/vue-shims.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
declare module "*.vue" {
|
declare module "*.vue" {
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from 'vue';
|
||||||
const component: ReturnType<typeof defineComponent>;
|
const component: ReturnType<typeof defineComponent>;
|
||||||
export default component;
|
export default component;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/matter-js": "0.19.7",
|
"@types/matter-js": "0.19.7",
|
||||||
"@types/node": "22.7.8",
|
"@types/node": "22.9.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
```ts
|
```ts
|
||||||
|
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import _ReconnectingWebsocket from 'reconnecting-websocket';
|
||||||
|
|
||||||
// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts
|
// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts
|
||||||
//
|
//
|
||||||
@ -118,6 +119,9 @@ type AdminAnnouncementsUpdateRequest = operations['admin___announcements___updat
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
|
type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
|
type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -253,6 +257,9 @@ type AdminInviteListResponse = operations['admin___invite___list']['responses'][
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json'];
|
type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminNormalizationRequest = operations['admin___normalization']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json'];
|
type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -1244,6 +1251,7 @@ declare namespace entities {
|
|||||||
AdminAbuseReportResolverDeleteRequest,
|
AdminAbuseReportResolverDeleteRequest,
|
||||||
AdminAbuseReportResolverUpdateRequest,
|
AdminAbuseReportResolverUpdateRequest,
|
||||||
AdminAvatarDecorationsCreateRequest,
|
AdminAvatarDecorationsCreateRequest,
|
||||||
|
AdminAvatarDecorationsCreateResponse,
|
||||||
AdminAvatarDecorationsDeleteRequest,
|
AdminAvatarDecorationsDeleteRequest,
|
||||||
AdminAvatarDecorationsListRequest,
|
AdminAvatarDecorationsListRequest,
|
||||||
AdminAvatarDecorationsListResponse,
|
AdminAvatarDecorationsListResponse,
|
||||||
@ -1291,6 +1299,7 @@ declare namespace entities {
|
|||||||
AdminInviteCreateResponse,
|
AdminInviteCreateResponse,
|
||||||
AdminInviteListRequest,
|
AdminInviteListRequest,
|
||||||
AdminInviteListResponse,
|
AdminInviteListResponse,
|
||||||
|
AdminNormalizationRequest,
|
||||||
AdminPromoCreateRequest,
|
AdminPromoCreateRequest,
|
||||||
AdminQueueDeliverDelayedResponse,
|
AdminQueueDeliverDelayedResponse,
|
||||||
AdminQueueInboxDelayedResponse,
|
AdminQueueInboxDelayedResponse,
|
||||||
@ -1614,6 +1623,7 @@ declare namespace entities {
|
|||||||
MiauthGenTokenResponse,
|
MiauthGenTokenResponse,
|
||||||
MuteCreateRequest,
|
MuteCreateRequest,
|
||||||
MuteDeleteRequest,
|
MuteDeleteRequest,
|
||||||
|
MuteEditRequest,
|
||||||
MuteListRequest,
|
MuteListRequest,
|
||||||
MuteListResponse,
|
MuteListResponse,
|
||||||
RenoteMuteCreateRequest,
|
RenoteMuteCreateRequest,
|
||||||
@ -2556,6 +2566,9 @@ type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type MuteEditRequest = operations['mute___edit']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -3024,7 +3037,7 @@ export class Stream extends EventEmitter<StreamEvents> {
|
|||||||
constructor(origin: string, user: {
|
constructor(origin: string, user: {
|
||||||
token: string;
|
token: string;
|
||||||
} | null, options?: {
|
} | null, options?: {
|
||||||
WebSocket?: any;
|
WebSocket?: _ReconnectingWebsocket.Options['WebSocket'];
|
||||||
});
|
});
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
close(): void;
|
close(): void;
|
||||||
|
@ -9,14 +9,14 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "^1.0.0",
|
"@misskey-dev/eslint-plugin": "^1.0.0",
|
||||||
"@readme/openapi-parser": "2.6.0",
|
"@readme/openapi-parser": "2.6.0",
|
||||||
"@types/node": "22.7.8",
|
"@types/node": "22.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"openapi-typescript": "6.7.6",
|
"openapi-typescript": "6.7.6",
|
||||||
"ts-case-convert": "2.1.0",
|
"ts-case-convert": "2.1.0",
|
||||||
"tsx": "4.19.1",
|
"tsx": "4.19.2",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.6.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2024.5.0-oscar.17a",
|
"version": "2024.5.0-oscar.18",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"types": "./built/dts/index.d.ts",
|
"types": "./built/dts/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
@ -35,22 +35,22 @@
|
|||||||
"url": "git+https://github.com/misskey-dev/misskey.js.git"
|
"url": "git+https://github.com/misskey-dev/misskey.js.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "7.47.5",
|
"@microsoft/api-extractor": "7.47.11",
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@swc/jest": "0.2.36",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "29.5.14",
|
||||||
"@types/node": "22.2.0",
|
"@types/node": "22.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.1",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-fetch-mock": "3.0.3",
|
"jest-fetch-mock": "3.0.3",
|
||||||
"jest-websocket-mock": "2.5.0",
|
"jest-websocket-mock": "2.5.0",
|
||||||
"mock-socket": "9.3.1",
|
"mock-socket": "9.3.1",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"nodemon": "3.1.4",
|
"nodemon": "3.1.7",
|
||||||
"tsd": "0.31.1",
|
"tsd": "0.31.2",
|
||||||
"typescript": "5.5.4"
|
"typescript": "5.6.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"built",
|
"built",
|
||||||
@ -58,8 +58,8 @@
|
|||||||
"built/dts"
|
"built/dts"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/cli": "0.3.12",
|
"@swc/cli": "0.5.0",
|
||||||
"@swc/core": "1.5.7",
|
"@swc/core": "1.8.0",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"reconnecting-websocket": "4.4.0"
|
"reconnecting-websocket": "4.4.0"
|
||||||
}
|
}
|
||||||
|
@ -644,6 +644,17 @@ declare module '../api.js' {
|
|||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/normalization', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
@ -3056,6 +3067,17 @@ declare module '../api.js' {
|
|||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||||
|
*/
|
||||||
|
request<E extends 'mute/edit', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -31,6 +31,7 @@ import type {
|
|||||||
AdminAbuseReportResolverDeleteRequest,
|
AdminAbuseReportResolverDeleteRequest,
|
||||||
AdminAbuseReportResolverUpdateRequest,
|
AdminAbuseReportResolverUpdateRequest,
|
||||||
AdminAvatarDecorationsCreateRequest,
|
AdminAvatarDecorationsCreateRequest,
|
||||||
|
AdminAvatarDecorationsCreateResponse,
|
||||||
AdminAvatarDecorationsDeleteRequest,
|
AdminAvatarDecorationsDeleteRequest,
|
||||||
AdminAvatarDecorationsListRequest,
|
AdminAvatarDecorationsListRequest,
|
||||||
AdminAvatarDecorationsListResponse,
|
AdminAvatarDecorationsListResponse,
|
||||||
@ -78,6 +79,7 @@ import type {
|
|||||||
AdminInviteCreateResponse,
|
AdminInviteCreateResponse,
|
||||||
AdminInviteListRequest,
|
AdminInviteListRequest,
|
||||||
AdminInviteListResponse,
|
AdminInviteListResponse,
|
||||||
|
AdminNormalizationRequest,
|
||||||
AdminPromoCreateRequest,
|
AdminPromoCreateRequest,
|
||||||
AdminQueueDeliverDelayedResponse,
|
AdminQueueDeliverDelayedResponse,
|
||||||
AdminQueueInboxDelayedResponse,
|
AdminQueueInboxDelayedResponse,
|
||||||
@ -401,6 +403,7 @@ import type {
|
|||||||
MiauthGenTokenResponse,
|
MiauthGenTokenResponse,
|
||||||
MuteCreateRequest,
|
MuteCreateRequest,
|
||||||
MuteDeleteRequest,
|
MuteDeleteRequest,
|
||||||
|
MuteEditRequest,
|
||||||
MuteListRequest,
|
MuteListRequest,
|
||||||
MuteListResponse,
|
MuteListResponse,
|
||||||
RenoteMuteCreateRequest,
|
RenoteMuteCreateRequest,
|
||||||
@ -610,7 +613,7 @@ export type Endpoints = {
|
|||||||
'admin/abuse-report-resolver/list': { req: AdminAbuseReportResolverListRequest; res: AdminAbuseReportResolverListResponse };
|
'admin/abuse-report-resolver/list': { req: AdminAbuseReportResolverListRequest; res: AdminAbuseReportResolverListResponse };
|
||||||
'admin/abuse-report-resolver/delete': { req: AdminAbuseReportResolverDeleteRequest; res: EmptyResponse };
|
'admin/abuse-report-resolver/delete': { req: AdminAbuseReportResolverDeleteRequest; res: EmptyResponse };
|
||||||
'admin/abuse-report-resolver/update': { req: AdminAbuseReportResolverUpdateRequest; res: EmptyResponse };
|
'admin/abuse-report-resolver/update': { req: AdminAbuseReportResolverUpdateRequest; res: EmptyResponse };
|
||||||
'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: EmptyResponse };
|
'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: AdminAvatarDecorationsCreateResponse };
|
||||||
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
|
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
|
||||||
'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse };
|
'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse };
|
||||||
'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
|
'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
|
||||||
@ -649,6 +652,7 @@ export type Endpoints = {
|
|||||||
'admin/get-user-ips': { req: AdminGetUserIpsRequest; res: AdminGetUserIpsResponse };
|
'admin/get-user-ips': { req: AdminGetUserIpsRequest; res: AdminGetUserIpsResponse };
|
||||||
'admin/invite/create': { req: AdminInviteCreateRequest; res: AdminInviteCreateResponse };
|
'admin/invite/create': { req: AdminInviteCreateRequest; res: AdminInviteCreateResponse };
|
||||||
'admin/invite/list': { req: AdminInviteListRequest; res: AdminInviteListResponse };
|
'admin/invite/list': { req: AdminInviteListRequest; res: AdminInviteListResponse };
|
||||||
|
'admin/normalization': { req: AdminNormalizationRequest; res: EmptyResponse };
|
||||||
'admin/promo/create': { req: AdminPromoCreateRequest; res: EmptyResponse };
|
'admin/promo/create': { req: AdminPromoCreateRequest; res: EmptyResponse };
|
||||||
'admin/queue/clear': { req: EmptyRequest; res: EmptyResponse };
|
'admin/queue/clear': { req: EmptyRequest; res: EmptyResponse };
|
||||||
'admin/queue/deliver-delayed': { req: EmptyRequest; res: AdminQueueDeliverDelayedResponse };
|
'admin/queue/deliver-delayed': { req: EmptyRequest; res: AdminQueueDeliverDelayedResponse };
|
||||||
@ -865,6 +869,7 @@ export type Endpoints = {
|
|||||||
'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse };
|
'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse };
|
||||||
'mute/create': { req: MuteCreateRequest; res: EmptyResponse };
|
'mute/create': { req: MuteCreateRequest; res: EmptyResponse };
|
||||||
'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse };
|
'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse };
|
||||||
|
'mute/edit': { req: MuteEditRequest; res: EmptyResponse };
|
||||||
'mute/list': { req: MuteListRequest; res: MuteListResponse };
|
'mute/list': { req: MuteListRequest; res: MuteListResponse };
|
||||||
'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse };
|
'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse };
|
||||||
'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse };
|
'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse };
|
||||||
|
@ -34,6 +34,7 @@ export type AdminAbuseReportResolverListResponse = operations['admin___abuse-rep
|
|||||||
export type AdminAbuseReportResolverDeleteRequest = operations['admin___abuse-report-resolver___delete']['requestBody']['content']['application/json'];
|
export type AdminAbuseReportResolverDeleteRequest = operations['admin___abuse-report-resolver___delete']['requestBody']['content']['application/json'];
|
||||||
export type AdminAbuseReportResolverUpdateRequest = operations['admin___abuse-report-resolver___update']['requestBody']['content']['application/json'];
|
export type AdminAbuseReportResolverUpdateRequest = operations['admin___abuse-report-resolver___update']['requestBody']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json'];
|
export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json'];
|
||||||
@ -81,6 +82,7 @@ export type AdminInviteCreateRequest = operations['admin___invite___create']['re
|
|||||||
export type AdminInviteCreateResponse = operations['admin___invite___create']['responses']['200']['content']['application/json'];
|
export type AdminInviteCreateResponse = operations['admin___invite___create']['responses']['200']['content']['application/json'];
|
||||||
export type AdminInviteListRequest = operations['admin___invite___list']['requestBody']['content']['application/json'];
|
export type AdminInviteListRequest = operations['admin___invite___list']['requestBody']['content']['application/json'];
|
||||||
export type AdminInviteListResponse = operations['admin___invite___list']['responses']['200']['content']['application/json'];
|
export type AdminInviteListResponse = operations['admin___invite___list']['responses']['200']['content']['application/json'];
|
||||||
|
export type AdminNormalizationRequest = operations['admin___normalization']['requestBody']['content']['application/json'];
|
||||||
export type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json'];
|
export type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json'];
|
||||||
export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json'];
|
export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json'];
|
||||||
export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json'];
|
export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json'];
|
||||||
@ -404,6 +406,7 @@ export type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBod
|
|||||||
export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json'];
|
export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json'];
|
||||||
export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
||||||
export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json'];
|
export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json'];
|
||||||
|
export type MuteEditRequest = operations['mute___edit']['requestBody']['content']['application/json'];
|
||||||
export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
|
||||||
export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json'];
|
export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json'];
|
||||||
export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
|
export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
|
||||||
|
@ -537,6 +537,15 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations['admin___invite___list'];
|
post: operations['admin___invite___list'];
|
||||||
};
|
};
|
||||||
|
'/admin/normalization': {
|
||||||
|
/**
|
||||||
|
* admin/normalization
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user*
|
||||||
|
*/
|
||||||
|
post: operations['admin___normalization'];
|
||||||
|
};
|
||||||
'/admin/promo/create': {
|
'/admin/promo/create': {
|
||||||
/**
|
/**
|
||||||
* admin/promo/create
|
* admin/promo/create
|
||||||
@ -2643,6 +2652,15 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations['mute___delete'];
|
post: operations['mute___delete'];
|
||||||
};
|
};
|
||||||
|
'/mute/edit': {
|
||||||
|
/**
|
||||||
|
* mute/edit
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||||
|
*/
|
||||||
|
post: operations['mute___edit'];
|
||||||
|
};
|
||||||
'/mute/list': {
|
'/mute/list': {
|
||||||
/**
|
/**
|
||||||
* mute/list
|
* mute/list
|
||||||
@ -4587,6 +4605,7 @@ export type components = {
|
|||||||
/** Format: id */
|
/** Format: id */
|
||||||
muteeId: string;
|
muteeId: string;
|
||||||
mutee: components['schemas']['UserDetailedNotMe'];
|
mutee: components['schemas']['UserDetailedNotMe'];
|
||||||
|
withNotification: boolean;
|
||||||
};
|
};
|
||||||
RenoteMuting: {
|
RenoteMuting: {
|
||||||
/**
|
/**
|
||||||
@ -5152,6 +5171,7 @@ export type components = {
|
|||||||
recaptchaSiteKey: string | null;
|
recaptchaSiteKey: string | null;
|
||||||
enableTurnstile: boolean;
|
enableTurnstile: boolean;
|
||||||
turnstileSiteKey: string | null;
|
turnstileSiteKey: string | null;
|
||||||
|
googleAnalyticsId: string | null;
|
||||||
swPublickey: string | null;
|
swPublickey: string | null;
|
||||||
/** @default /assets/ai.png */
|
/** @default /assets/ai.png */
|
||||||
mascotImageUrl: string;
|
mascotImageUrl: string;
|
||||||
@ -5299,6 +5319,7 @@ export type operations = {
|
|||||||
recaptchaSiteKey: string | null;
|
recaptchaSiteKey: string | null;
|
||||||
enableTurnstile: boolean;
|
enableTurnstile: boolean;
|
||||||
turnstileSiteKey: string | null;
|
turnstileSiteKey: string | null;
|
||||||
|
googleAnalyticsId: string | null;
|
||||||
swPublickey: string | null;
|
swPublickey: string | null;
|
||||||
/** @default /assets/ai.png */
|
/** @default /assets/ai.png */
|
||||||
mascotImageUrl: string | null;
|
mascotImageUrl: string | null;
|
||||||
@ -6629,9 +6650,22 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description OK (without any results) */
|
/** @description OK (with results) */
|
||||||
204: {
|
200: {
|
||||||
content: never;
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: id */
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAt: string | null;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
400: {
|
400: {
|
||||||
@ -8828,6 +8862,58 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/normalization
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user*
|
||||||
|
*/
|
||||||
|
admin___normalization: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (without any results) */
|
||||||
|
204: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* admin/promo/create
|
* admin/promo/create
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
@ -10203,6 +10289,7 @@ export type operations = {
|
|||||||
enableTurnstile?: boolean;
|
enableTurnstile?: boolean;
|
||||||
turnstileSiteKey?: string | null;
|
turnstileSiteKey?: string | null;
|
||||||
turnstileSecretKey?: string | null;
|
turnstileSecretKey?: string | null;
|
||||||
|
googleAnalyticsId?: string | null;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote';
|
sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote';
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
@ -22837,6 +22924,8 @@ export type operations = {
|
|||||||
userId: string;
|
userId: string;
|
||||||
/** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */
|
/** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */
|
||||||
expiresAt?: number | null;
|
expiresAt?: number | null;
|
||||||
|
/** @default true */
|
||||||
|
withNotification?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -22935,6 +23024,59 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* mute/edit
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:mutes*
|
||||||
|
*/
|
||||||
|
mute___edit: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
userId: string;
|
||||||
|
withNotification: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (without any results) */
|
||||||
|
204: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* mute/list
|
* mute/list
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
@ -34,7 +34,7 @@ export default class Stream extends EventEmitter<StreamEvents> {
|
|||||||
private idCounter = 0;
|
private idCounter = 0;
|
||||||
|
|
||||||
constructor(origin: string, user: { token: string; } | null, options?: {
|
constructor(origin: string, user: { token: string; } | null, options?: {
|
||||||
WebSocket?: any;
|
WebSocket?: _ReconnectingWebsocket.Options['WebSocket'];
|
||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/node": "22.7.8",
|
"@types/node": "22.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/serviceworker": "0.0.101",
|
"@types/serviceworker": "0.0.102",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
|
4877
pnpm-lock.yaml
4877
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parserOptions: {
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
project: ['./tsconfig.json'],
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'../../packages/shared/.eslintrc.js',
|
|
||||||
],
|
|
||||||
};
|
|
3
scripts/changelog-checker/.gitignore
vendored
3
scripts/changelog-checker/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
coverage
|
|
||||||
.idea
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "changelog-checker",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"run": "vite-node src/index.ts",
|
|
||||||
"test": "vitest run",
|
|
||||||
"test:coverage": "vitest run --coverage"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/mdast": "4.0.3",
|
|
||||||
"@types/node": "20.10.7",
|
|
||||||
"@vitest/coverage-v8": "1.1.3",
|
|
||||||
"mdast-util-to-string": "4.0.0",
|
|
||||||
"remark": "15.0.1",
|
|
||||||
"remark-parse": "11.0.0",
|
|
||||||
"typescript": "5.6.3",
|
|
||||||
"unified": "11.0.4",
|
|
||||||
"vite": "5.0.12",
|
|
||||||
"vite-node": "1.1.3",
|
|
||||||
"vitest": "1.1.3"
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user