diff --git a/.config/docker_example.yml b/.config/docker_example.yml index c6c83a98bf..346a32a86f 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -148,14 +148,14 @@ id: 'aidx' # Job concurrency per worker # deliverJobConcurrency: 128 # inboxJobConcurrency: 16 -# relashionshipJobConcurrency: 16 -# What's relashionshipJob?: +# relationshipJobConcurrency: 16 +# What's relationshipJob?: # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Job rate limiter # deliverJobPerSec: 128 # inboxJobPerSec: 32 -# relashionshipJobPerSec: 64 +# relationshipJobPerSec: 64 # Job attempts # deliverJobMaxAttempts: 12 diff --git a/.config/example.yml b/.config/example.yml index 4aa7757c61..1e649deabb 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -160,14 +160,14 @@ id: 'aidx' # Job concurrency per worker #deliverJobConcurrency: 128 #inboxJobConcurrency: 16 -#relashionshipJobConcurrency: 16 -# What's relashionshipJob?: +#relationshipJobConcurrency: 16 +# What's relationshipJob?: # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Job rate limiter #deliverJobPerSec: 128 #inboxJobPerSec: 32 -#relashionshipJobPerSec: 64 +#relationshipJobPerSec: 64 # Job attempts #deliverJobMaxAttempts: 12 diff --git a/CHANGELOG.md b/CHANGELOG.md index cff0fc07cd..8f6cf2071b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 - Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 - Feat: Add support for TrueMail +- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正 + * すべてのリモートユーザーのリアクション一覧を見えないようにします +- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように ### Client - Feat: 新しいゲームを追加 @@ -45,11 +48,20 @@ - Enhance: ノート作成画面のファイル添付メニューから直接ファイルを削除できるように - Enhance: MFMの属性でオートコンプリートが使用できるように #12735 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように +- Enhance: リモートのユーザーはメニューから直接リモートで表示できるように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 - Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正 +- Fix: Renoteのキーボードショートカットが機能していなかった問題を修正 +- Fix: 投稿フォームでアンケートの日時指定をした状態で再読み込みをすると期日が復元されない問題を修正 +- Fix: アンケートを設定したノートを「削除して編集」をするとアンケートの期日が引き継がれず、リセットされてしまう問題を修正 +- Fix: デッキのプロファイル作成時に名前を空にできる問題を修正 +- Fix: テーマ作成時に名称が空欄でも作成できてしまう問題を修正 +- Fix: プラグインで`Plugin:register_note_post_interruptor`を使用すると、ノートが投稿できなくなる問題を修正 - Enhance: ページ遷移時にPlayerを閉じるように +- Fix: iOSで大きな画像を変換してアップロードできない問題を修正 +- Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正 ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました @@ -62,6 +74,8 @@ - Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更 - Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正 - Fix: properly handle cc followers +- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec +- Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122 ### Service Worker - Enhance: オフライン表示のデザインを改善・多言語対応 diff --git a/cypress/e2e/router.cy.js b/cypress/e2e/router.cy.js new file mode 100644 index 0000000000..81f497b5b8 --- /dev/null +++ b/cypress/e2e/router.cy.js @@ -0,0 +1,30 @@ +describe('Router transition', () => { + describe('Redirect', () => { + // サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い) + before(() => { + cy.resetState(); + + // インスタンス初期セットアップ + cy.registerUser('admin', 'pass', true); + + // ユーザー作成 + cy.registerUser('alice', 'alice1234'); + + cy.login('alice', 'alice1234'); + + // アカウント初期設定ウィザード + // 表示に時間がかかるのでデフォルト秒数だとタイムアウトする + cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click(); + cy.wait(500); + cy.get('[data-cy-modal-dialog-ok]').click(); + }); + + it('redirect to user profile', () => { + // テストのためだけに用意されたリダイレクト用ルートに飛ぶ + cy.visit('/redirect-test'); + + // プロフィールページのURLであることを確認する + cy.url().should('include', '/@alice') + }); + }); +}); diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 59594f85fd..eeafcafed2 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats" reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir." rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes" attachCancel: "Eliminar el fitxer adjunt" +deleteFile: "Esborrar l'arxiu " markAsSensitive: "Marcar com a NSFW" unmarkAsSensitive: "Deixar de marcar com a sensible" enterFileName: "Defineix nom del fitxer" @@ -1039,6 +1040,12 @@ rolesAssignedToMe: "Rols assignats " resetPasswordConfirm: "Vols canviar la teva contrasenya?" sensitiveWords: "Paraules sensibles" sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves." +sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular." +hiddenTags: "Etiquetes ocultes" +hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves." +notesSearchNotAvailable: "La cerca de notes no es troba disponible." +license: "Llicència" +unfavoriteConfirm: "Esborrar dels favorits?" myClips: "Els meus retalls" drivecleaner: "Netejador de Disc" retryAllQueuesNow: "Prova de nou d'executar totes les cues" @@ -1048,6 +1055,14 @@ enableChartsForRemoteUser: "Generar gràfiques d'usuaris remots" enableChartsForFederatedInstances: "Generar gràfiques d'instàncies remotes" showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota" reactionsDisplaySize: "Mida de les reaccions" +limitWidthOfReaction: "Limitar l'amplada màxima de la reacció i mostrar-les en una mida reduïda " +noteIdOrUrl: "ID o URL de la nota" +video: "Vídeo" +videos: "Vídeos " +audio: "So" +audioFiles: "So" +dataSaver: "Economitzador de dades" +accountMigration: "Migració del compte" accountMoved: "Aquest usuari té un compte nou:" accountMovedShort: "Aquest compte ha sigut migrat" operationForbidden: "Operació no permesa " @@ -1098,9 +1113,50 @@ branding: "Marca" enableServerMachineStats: "Publicar estadístiques del maquinari del servidor" enableIdenticonGeneration: "Activar la generació d'icones d'identificació " turnOffToImprovePerformance: "Desactivant aquesta opció es pot millorar el rendiment." +createInviteCode: "Crear codi d'invitació " +createWithOptions: "Crear invitació amb opcions" +createCount: "Comptador d'invitacions " +inviteCodeCreated: "Invitació creada" +inviteLimitExceeded: "Has sobrepassat el límit d'invitacions que pots crear." +createLimitRemaining: "Et queden {limit} invitacions restants" +inviteLimitResetCycle: "Cada {time} {limit} invitacions." +expirationDate: "Data de venciment" +noExpirationDate: "Sense data de venciment" +inviteCodeUsedAt: "Codi d'invitació fet servir el" +registeredUserUsingInviteCode: "Codi d'invitació fet servir per l'usuari " +waitingForMailAuth: "Esperant la verificació per correu electrònic " +inviteCodeCreator: "Invitació creada per" +usedAt: "Utilitzada el" +unused: "Sense utilitzar" +used: "Utilitzada" +expired: "Caducat" +doYouAgree: "Estàs d'acord?" +beSureToReadThisAsItIsImportant: "Llegeix això perquè és molt important." +iHaveReadXCarefullyAndAgree: "He llegit {x} i estic d'acord." +dialog: "Diàleg " icon: "Icona" +forYou: "Per a tu" +currentAnnouncements: "Informes actuals" +pastAnnouncements: "Informes passats" +youHaveUnreadAnnouncements: "Tens informes per llegir." +useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." replies: "Respostes" renotes: "Impulsa" +loadReplies: "Mostrar les respostes" +loadConversation: "Mostrar la conversació " +pinnedList: "Llista fixada" +keepScreenOn: "Mantenir la pantalla encesa" +verifiedLink: "La propietat de l'enllaç ha sigut verificada" +notifyNotes: "Notificar quan hi hagi notes noves" +unnotifyNotes: "Deixar de notificar quan hi hagi notes noves" +authentication: "Autenticació " +authenticationRequiredToContinue: "Si us plau autentificat per continuar" +dateAndTime: "Data i hora" +showRenotes: "Mostrar impulsos" +edited: "Editat" +notificationRecieveConfig: "Paràmetres de notificacions" +mutualFollow: "Seguidor mutu" +fileAttachedOnly: "Només notes amb adjunts" externalServices: "Serveis externs" impressum: "Impressum" impressumUrl: "Adreça URL impressum" @@ -1153,6 +1209,149 @@ _initialAccountSetting: privacySetting: "Configuració de seguretat" theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard." youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard." +_initialTutorial: + _reaction: + letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!" + reactToContinue: "Afegeix una reacció per continuar." + reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes." + reactDone: "Pots desfer una reacció fent clic al botó '-'." + _timeline: + title: "El concepte de les línies de temps" + description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)" + home: "Pots veure notes dels comptes que segueixes" + local: "Pots veure les notes dels usuaris del servidor." + social: "Es mostren les notes de les línies de temps d'Inici i Local." + global: "Pots veure les notes de tots els servidors connectats." + description2: "Pots canviar la línia de temps en qualsevol moment fent servir la barra de la pantalla superior." + description3: "A més hi ha línies de temps per llistes i per canals. Si vols saber més {link}." + _postNote: + title: "Configuració de la publicació de les notes" + description1: "Quan públiques una nota a Misskey hi ha diferents opcions disponibles. El formulari de publicació es veu així" + _visibility: + description: "Pots limitar qui pot veure les teves notes." + public: "La teva nota serà visible per a tots els usuaris." + home: "Publicar només a línia de temps d'Inici. La gent que visiti el teu perfil o mitjançant les remotes també la podran veure." + followers: "Només visible per a seguidors. Només els teus seguidors la podran veure i ningú més. Ningú més podrà fer renotes." + direct: "Només visible per a alguns seguidors, el destinatari rebre una notificació. Es pot fer servir com una alternativa als missatges directes." + doNotSendConfidencialOnDirect1: "Tingues cura quan enviïs informació sensible." + doNotSendConfidencialOnDirect2: "Els administradors del servidor poden veure tot el que escrius. Ves compte quan enviïs informació sensible en enviar notes directes a altres usuaris en servidors de poca confiança." + localOnly: "Publicar amb aquesta opció activada farà que la nota no federi amb altres servidors. Els usuaris d'altres servidors no podran veure la nota directament, sense importar les opcions de visualització." + _cw: + title: "Avís de Contingut (CW)" + description: "En comptes del cos de la nota es mostrarà el que s'escrigui al camp de 'comentaris'. Fent clic a 'Llegir més' es mostrarà el cos." + _exampleNote: + cw: "Això et farà venir gana!" + note: "Acabo de menjar-me un donut de xocolata 🍩😋" + useCases: "Això es fa servir per seguir normes del servidor sobre certes notes o per ocultar contingut sensible O revelador." + _howToMakeAttachmentsSensitive: + title: "Com marcar adjunts com a contingut sensible?" + description: "Per adjunts que sigui requerit per les normes del servidor o que puguin contenir material sensible, s'ha d'afegir l'opció 'sensible'." + tryThisFile: "Prova de marcar la imatge adjunta en aquest formulari com a sensible!" + _exampleNote: + note: "Oops! L'he fet bona en obrir la tapa de Nocilla..." + method: "Per marcar un adjunt com a sensible, fes clic a la miniatura de l'adjunt, obre el menú i fes clic a 'Marcar com a sensible'." + sensitiveSucceeded: "Quan adjuntis fitxers si us plau marca la sensibilitat seguint les normes del servidor." + doItToContinue: "Marca el fitxer adjunt com a sensible per poder continuar." + _done: + title: "Has completat el tutorial 🎉" + description: "Les funcions explicades aquí és una petita mostra. Per una explicació més detallada de com fer servir MissKey consulta {link}." +_timelineDescription: + home: "A la línia de temps d'Inici pots veure les notes dels usuaris que segueixes." + local: "A la línia de temps Local pots veure les notes de tots els usuaris d'aquest servidor." + social: "La línia de temps Social mostren les notes de les línies de temps d'Inici i Local." + global: "A la línia de temps Global pots veure les notes de tots els servidors connectats." +_serverRules: + description: "Un conjunt de regles que seran mostrades abans de registrar-se. Es recomanable configurar un resum dels termes d'ús." +_serverSettings: + iconUrl: "URL de la icona" + appIconDescription: "Especifica la icona que es mostrarà quan el {host} es mostri en una aplicació." + appIconUsageExample: "Per exemple com a PWA, o quan es mostri com un favorit a la pàgina d'inici del telèfon mòbil" + appIconStyleRecommendation: "Com la icona pot ser retallada com un cercle o un quadrat, es recomana fer servir una icona amb un marge acolorit que l'envolti." + appIconResolutionMustBe: "La resolució mínima és {resolution}." + manifestJsonOverride: "Sobreescriure manifest.json" + shortName: "Nom curt" + shortNameDescription: "Una abreviatura del nom de la instància que es poguí mostrar en cas que el nom oficial sigui massa llarg" + fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat." +_achievements: + _types: + _notes100000: + flavor: "Segur que tens moltes coses a dir?" + _login3: + title: "Principiant I" + description: "Vas iniciar sessió fa tres dies" + flavor: "Des d'avui diguem Misskist" + _login7: + title: "Principiant II" + description: "Vas iniciar sessió fa set dies" + flavor: "Ja saps com va funcionant tot?" + _login15: + title: "Principiant III" + description: "Vas iniciar sessió fa quinze dies" + _login30: + title: "Misskist I" + description: "Vas iniciar sessió fa trenta dies" + _login60: + title: "Misskist II" + description: "Vas iniciar sessió fa seixanta dies" + _login100: + title: "Misskist III" + description: "Vas iniciar sessió fa cent dies" + flavor: "Misskist violent" + _login200: + title: "Regular I" + description: "Vas iniciar sessió fa dos-cents dies" + _login300: + title: "Regular II" + description: "Vas iniciar sessió fa tres-cents dies" + _login400: + title: "Regular III" + description: "Vas iniciar sessió fa quatre-cents dies" + _login500: + title: "Expert I" + description: "Vas iniciar sessió fa cinc-cents dies" + flavor: "Amics, he dit massa vegades que soc un amant de les notes" + _login600: + title: "Expert II" + description: "Vas iniciar sessió fa sis-cents dies" + _login700: + title: "Expert III" + description: "Vas iniciar sessió fa set-cents dies" + _login800: + title: "Mestre de les Notes I" + description: "Vas iniciar sessió fa vuit-cents dies " + _login900: + title: "Mestre de les Notes II" + description: "Vas iniciar sessió fa nou-cents dies" + _login1000: + title: "Mestre de les Notes III" + description: "Vas iniciar sessió fa mil dies" + flavor: "Gràcies per fer servir MissKey!" + _noteClipped1: + title: "He de retallar-te!" + description: "Retalla la teva primera nota" + _noteFavorited1: + title: "Quan miro les estrelles" + description: "La primera vegada que vaig registrar el meu favorit" + _myNoteFavorited1: + title: "Vull una estrella" + description: "La meva nota va ser registrada com favorita per una de les altres persones" + _profileFilled: + title: "Estic a punt" + description: "Vaig fer la configuració de perfil" + _markedAsCat: + title: "Soc un gat" + description: "He establert el meu compte com si fos un Gat" + flavor: "Encara no tinc nom" + _following1: + title: "És el meu primer seguiment" + description: "És la primera vegada que et segueixo" + _following10: + title: "Segueix-me... Segueix-me..." + _open3windows: + title: "Multi finestres" + description: "I va obrir més de tres finestres" + _driveFolderCircularReference: + title: "Consulteu la secció de bucle" _role: assignTarget: "Assignar " priority: "Prioritat" @@ -1274,6 +1473,7 @@ _webhookSettings: _moderationLogTypes: suspend: "Suspèn" resetPassword: "Restableix la contrasenya" + createInvitation: "Crear codi d'invitació " _reversi: total: "Total" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 0cbd3db0aa..909ee5d45c 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -122,7 +122,10 @@ add: "Hinzufügen" reaction: "Reaktionen" reactions: "Reaktionen" emojiPicker: "Emoji auswählen" -pinnedEmojisForReactionSettingDescription: "Wähle die Emojis aus, um sie an zu pinnen" +pinnedEmojisForReactionSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie beim Reagieren als Erstes anzuzeigen." +pinnedEmojisSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie in der Emoji-Auswahl als Erstes anzuzeigen" +overwriteFromPinnedEmojisForReaction: "Überschreiben mit den Reaktions-Einstellungen" +overwriteFromPinnedEmojis: "Überschreiben mit den allgemeinen Einstellungen" reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen" rememberNoteVisibility: "Notizsichtbarkeit merken" attachCancel: "Anhang entfernen" @@ -181,7 +184,7 @@ searchWith: "Suchen: {q}" youHaveNoLists: "Du hast keine Listen" followConfirm: "Möchtest du {name} wirklich folgen?" proxyAccount: "Proxy-Benutzerkonto" -proxyAccountDescription: "Ein Proxy-Benutzerkonto ist ein Benutzerkonto, das sich für Nutzer unter bestimmten Konditionen wie ein Follower aus einer fremden Instanz verhält. Zum Beispiel wird die Aktivität eines Nutzers aus einer fremden Instanz nicht an diese Instanz übermittelt, falls es keinen Benutzer dieser Instanz gibt, der diesem Nutzer aus fremder Instanz folgt. In diesem Fall folgt stattdessen das Proxy-Benutzerkonto." +proxyAccountDescription: "Ein Proxy-Konto ist ein Benutzerkonto, das unter bestimmten Bedingungen als Follower für Benutzer fremder Instanzen fungiert. Wenn zum Beispiel ein Benutzer einen Benutzer einer fremden Instanz zu einer Liste hinzufügt, werden die Aktivitäten des entfernten Benutzers nicht an die Instanz übermittelt, wenn kein lokaler Benutzer diesem Benutzer folgt; stattdessen folgt das Proxy-Konto." host: "Hostname" selectUser: "Benutzer auswählen" recipient: "Empfänger" @@ -263,6 +266,7 @@ removed: "Erfolgreich gelöscht" removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?" deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?" resetAreYouSure: "Wirklich zurücksetzen?" +areYouSure: "Bist du sicher?" saved: "Erfolgreich gespeichert" messaging: "Chat" upload: "Hochladen" @@ -357,7 +361,7 @@ enableLocalTimeline: "Lokale Chronik aktivieren" enableGlobalTimeline: "Globale Chronik aktivieren" disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind." registration: "Registrieren" -enableRegistration: "Registration neuer Benutzer erlauben" +enableRegistration: "Registrierung neuer Benutzer erlauben" invite: "Einladen" driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto" driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen" @@ -375,8 +379,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "hCaptcha aktivieren" hcaptchaSiteKey: "Site key" hcaptchaSecretKey: "Secret key" +mcaptcha: "mCaptcha" +enableMcaptcha: "mCaptcha aktivieren" mcaptchaSiteKey: "Site key" mcaptchaSecretKey: "Secret key" +mcaptchaInstanceUrl: "mCaptcha Instanz-URL" recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHA aktivieren" recaptchaSiteKey: "Site key" @@ -434,7 +441,7 @@ lastUsed: "Zuletzt benutzt" lastUsedAt: "Zuletzt verwendet: {t}" unregister: "Deaktivieren" passwordLessLogin: "Passwortloses Anmelden" -passwordLessLoginDescription: "Ermöglicht passwortfreies Einloggen, nur via Security-Token oder Passkey" +passwordLessLoginDescription: "Ermöglicht passwortloses Einloggen mit einem Security-Token oder Passkey" resetPassword: "Passwort zurücksetzen" newPasswordIs: "Das neue Passwort ist „{password}“" reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren" @@ -1174,6 +1181,9 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." doReaction: "Reagieren" code: "Code" +decorate: "Dekorieren" +addMfmFunction: "MFM hinzufügen" +sfx: "Soundeffekte" lastNDays: "Letzten {n} Tage" _announcement: forExistingUsers: "Nur für existierende Nutzer" @@ -1184,6 +1194,7 @@ _announcement: tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren." readConfirmTitle: "Als gelesen markieren?" readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen." + shouldNotBeUsedToPresentPermanentInfo: "Es wird empfohlen, Ankündigungen für aktuelle und zeitlich begrenzte Neuigkeiten zu nutzen, statt für Informationen, die langfristig relevant sind." dialogAnnouncementUxWarn: "Bei der Verwendung von mehr als zwei Meldungen im Dialog-Format wird um Vorsicht geboten, da dies negative Auswirkungen auf die UX haben kann." silence: "Keine Benachrichtigung" silenceDescription: "Wenn aktiviert, gibt diese Meldung keine Nachricht aus und muss nicht als \"gelesen\" markiert werden." @@ -1213,6 +1224,24 @@ _initialTutorial: description: "Hier kannst du sehen, wie Misskey funktioniert" _note: title: "Was sind Notizen?" + description: "Beiträge auf Misskey heißen \"Notizen\". Notizen werden chronologisch in der Chronik angeordnet und in Echtzeit aktualisiert." + reply: "Klicke auf diesen Button, um auf eine Nachricht zu antworten. Es ist auch möglich, auf Antworten zu antworten und die Unterhaltung wie einen Thread fortzusetzen." + _reaction: + title: "Was sind Reaktionen?" + reactToContinue: "Füge eine Reaktion hinzu, um fortzufahren." + reactNotification: "Du erhältst Echtzeit-Benachrichtigungen, wenn jemand auf deine Notiz reagiert." + _postNote: + _visibility: + description: "Du kannst einschränken, wer deine Notiz sehen kann." + public: "Deine Notiz wird für alle Nutzer sichtbar sein." + doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!" + _cw: + title: "Inhaltswarnung" + _done: + title: "Du hast das Tutorial abgeschlossen! 🎉" +_timelineDescription: + local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server." + global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern." _serverRules: description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." _serverSettings: @@ -1225,6 +1254,8 @@ _serverSettings: shortName: "Abkürzung" shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist." fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden." + fanoutTimelineDbFallback: "Auf die Datenbank zurückfallen" + fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. " _accountMigration: moveFrom: "Von einem anderen Konto zu diesem migrieren" moveFromSub: "Alias für ein anderes Konto erstellen" @@ -1482,6 +1513,8 @@ _achievements: _smashTestNotificationButton: title: "Testüberfluss" description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne" + _tutorialCompleted: + description: "Tutorial abgeschlossen" _role: new: "Rolle erstellen" edit: "Rolle bearbeiten" @@ -1492,7 +1525,9 @@ _role: assignTarget: "Zuweisungsart" descriptionOfAssignTarget: "Manuell bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\nKonditional bedeutet, dass die Liste der Benutzer einer Rolle durch eine Bedingung automatisch verwaltet wird." manual: "Manuell" + manualRoles: "Manuelle Rollen" conditional: "Konditional" + conditionalRoles: "Bedingte Rolle" condition: "Bedingung" isConditionalRole: "Dies ist eine konditionale Rolle." isPublic: "Öffentliche Rolle" @@ -1534,13 +1569,14 @@ _role: webhookMax: "Maximale Anzahl an Webhooks" clipMax: "Maximale Anzahl an Clips" noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips" - userListMax: "Maximale Anzahl an Benutzern in einer Benutzerliste" - userEachUserListsMax: "Maximale Anzahl an Benutzerlisten" + userListMax: "Maximale Anzahl an Benutzerlisten" + userEachUserListsMax: "Maximale Anzahl an Benutzern in einer Benutzerliste" rateLimitFactor: "Versuchsanzahl" descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver." canHideAds: "Kann Werbung ausblenden" canSearchNotes: "Nutzung der Notizsuchfunktion" canUseTranslator: "Verwendung des Übersetzers" + avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können" _condition: isLocal: "Lokaler Benutzer" isRemote: "Benutzer fremder Instanz" @@ -1569,6 +1605,7 @@ _emailUnavailable: disposable: "Wegwerf-Email-Adressen können nicht verwendet werden" mx: "Dieser Email-Server ist ungültig" smtp: "Dieser Email-Server antwortet nicht" + banned: "Du kannst dich mit dieser E-Mail-Adresse nicht registrieren" _ffVisibility: public: "Öffentlich" followers: "Nur für Follower sichtbar" @@ -1897,6 +1934,7 @@ _widgets: _userList: chooseList: "Liste auswählen" clicker: "Klickzähler" + birthdayFollowings: "Nutzer, die heute Geburtstag haben" _cw: hide: "Inhalt verbergen" show: "Inhalt anzeigen" @@ -2246,5 +2284,9 @@ _externalResourceInstaller: title: "Das Farbschema konnte nicht installiert werden" description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden." _reversi: + blackOrWhite: "Schwarz/Weiß" + rules: "Regeln" + black: "Schwarz" + white: "Weiß" total: "Gesamt" diff --git a/locales/en-US.yml b/locales/en-US.yml index a97e0e69e9..4c859861f7 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -391,8 +391,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "Enable hCaptcha" hcaptchaSiteKey: "Site key" hcaptchaSecretKey: "Secret key" +mcaptcha: "mCaptcha" +enableMcaptcha: "Enable mCaptcha" mcaptchaSiteKey: "Site key" mcaptchaSecretKey: "Secret key" +mcaptchaInstanceUrl: "mCaptcha instance URL" recaptcha: "reCAPTCHA" enableRecaptcha: "Enable reCAPTCHA" recaptchaSiteKey: "Site key" @@ -645,6 +648,7 @@ medium: "Medium" small: "Small" generateAccessToken: "Generate access token" permission: "Permissions" +adminPermission: "Admin Permissions" enableAll: "Enable all" disableAll: "Disable all" tokenRequested: "Grant access to account" @@ -661,7 +665,7 @@ smtpHost: "Host" smtpPort: "Port" smtpUser: "Username" smtpPass: "Password" -emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verification" +emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP authentication" smtpSecure: "Use implicit SSL/TLS for SMTP connections" smtpSecureInfo: "Turn this off when using STARTTLS" testEmail: "Test email delivery" @@ -688,6 +692,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b other: "Other" regenerateLoginToken: "Regenerate login token" regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." +theKeywordWhenSearchingForCustomEmoji: "This is the keyword when searching for custom emojis." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." fileIdOrUrl: "File ID or URL" behavior: "Behavior" @@ -1091,6 +1096,8 @@ limitWidthOfReaction: "Limits the maximum width of reactions and display them in noteIdOrUrl: "Note ID or URL" video: "Video" videos: "Videos" +audio: "Audio" +audioFiles: "Audio" dataSaver: "Data Saver" accountMigration: "Account Migration" accountMoved: "This user has moved to a new account:" @@ -1212,7 +1219,7 @@ donationUrl: "Donation URL" avatarDecorations: "Avatar decorations" attach: "Attach" detach: "Remove" -detachAll: "Remove all" +detachAll: "Remove All" angle: "Angle" flip: "Flip" showAvatarDecorations: "Show avatar decorations" @@ -1234,8 +1241,23 @@ addMfmFunction: "Add MFM" enableQuickAddMfmFunction: "Show advanced MFM picker" bubbleGame: "Bubble Game" sfx: "Sound Effects" +soundWillBePlayed: "Sound will be played" +showReplay: "View Replay" replay: "Replay" +replaying: "Showing replay" +ranking: "Ranking" lastNDays: "Last {n} days" +backToTitle: "Go back to title" +hemisphere: "Where are you located" +withSensitive: "Include notes with sensitive files" +userSaysSomethingSensitive: "Post by {name} contains sensitive content" +enableHorizontalSwipe: "Swipe to switch tabs" +_bubbleGame: + howToPlay: "How to play" + _howToPlay: + section1: "Adjust the position and drop the object into the box." + section2: "When two objects of the same type touch each other, they will change into a different object and you score points." + section3: "The game is over when objects overflow from the box. Aim for a high score by fusing objects together while you avoid overflowing the box!" _announcement: forExistingUsers: "Existing users only" forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it." @@ -1245,7 +1267,7 @@ _announcement: tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete." readConfirmTitle: "Mark as read?" readConfirmText: "This will mark the contents of \"{title}\" as read." - shouldNotBeUsedToPresentPermanentInfo: "As it may significantly impact the user experience for new users, it is recommended to use notifications in the flow information rather than stock information." + shouldNotBeUsedToPresentPermanentInfo: "It's best to use announcements to publish fresh and time-bound information, not for information that will be relevant in the long term." dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully." silence: "No notification" silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it." @@ -1608,8 +1630,11 @@ _achievements: description: "Tutorial completed" _bubbleGameExplodingHead: title: "🤯" + description: "The biggest object in the bubble game" _bubbleGameDoubleExplodingHead: title: "Double🤯" + description: "Two of the biggest objects in the bubble game at the same time" + flavor: "You can fill a lunch box like this 🤯 🤯 a bit." _role: new: "New role" edit: "Edit role" @@ -2562,6 +2587,53 @@ _dataSaver: _code: title: "Code highlighting" description: "If code highlighting notations are used in MFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data." +_hemisphere: + N: "Northern Hemisphere" + S: "Southern Hemisphere" + caption: "Used in some client settings to determine season." _reversi: + reversi: "Reversi" + gameSettings: "Game settings" + chooseBoard: "Choose a board" + blackOrWhite: "Black/White" + blackIs: "{name} is playing Black" + rules: "Rules" + thisGameIsStartedSoon: "The game will begin shortly" + waitingForOther: "Waiting for opponent's turn" + waitingForMe: "Waiting for your turn" + waitingBoth: "Get ready" + ready: "Ready" + cancelReady: "Not ready" + opponentTurn: "Opponent's turn" + myTurn: "Your turn" + turnOf: "It's {name}'s turn" + pastTurnOf: "{name}'s turn" + surrender: "Surrender" + surrendered: "Surrendered" + timeout: "Out of time" + drawn: "Draw" + won: "{name} wins" + black: "Black" + white: "White" total: "Total" + turnCount: "Turn {count}" + myGames: "My rounds" + allGames: "All rounds" + ended: "Ended" + playing: "Currently playing" + isLlotheo: "The one with fewer stones wins (Llotheo)" + loopedMap: "Looping map" + canPutEverywhere: "Tiles are placeable everywhere" + timeLimitForEachTurn: "Time limit for turn" + freeMatch: "Free Match" + lookingForPlayer: "Finding opponent..." + gameCanceled: "The game has been cancelled." + shareToTlTheGameWhenStart: "Share Game to timeline when started" + iStartedAGame: "The game has begun! #MisskeyReversi" + opponentHasSettingsChanged: "The opponent has changed their settings." + allowIrregularRules: "Irregular rules (completely free)" + disallowIrregularRules: "No irregular rules" +_offlineScreen: + title: "Offline - cannot connect to the server" + header: "Unable to connect to the server" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index c38616d221..ab6cb115d3 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -399,7 +399,7 @@ antennaKeywords: "Mots clés à recevoir" antennaExcludeKeywords: "Mots clés à exclure" antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR." notifyAntenna: "Me notifier pour les nouvelles notes" -withFileAntenna: "Notes ayant des attachements uniquement" +withFileAntenna: "Notes ayant des fichiers joints uniquement" enableServiceworker: "Activer ServiceWorker" antennaUsersDescription: "Saisissez un seul nom d’utilisateur·rice par ligne" caseSensitive: "Sensible à la casse" diff --git a/locales/index.d.ts b/locales/index.d.ts index bb76d611ab..686fa57cd5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5079,7 +5079,7 @@ export interface Locale extends ILocale { */ "readConfirmText": ParameterizedString<"title">; /** - * 特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。 + * 特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。 */ "shouldNotBeUsedToPresentPermanentInfo": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ca12b3cd54..f6879acda1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1269,7 +1269,7 @@ _announcement: tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。" readConfirmTitle: "既読にしますか?" readConfirmText: "「{title}」の内容を読み、既読にします。" - shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。" + shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。" dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。" silence: "非通知" silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 4b32ff359e..5d5b175e0c 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -2035,9 +2035,9 @@ _auth: _antennaSources: all: "みんなのノート" homeTimeline: "フォローしとるユーザーのノート" - users: "選らんだ一人か複数のユーザーのノート" + users: "選んだ一人か複数のユーザーのノート" userList: "選んだリストのユーザーのノート" - userBlacklist: "選んだ1人か複数のユーザーのノート" + userBlacklist: "選んだ一人か複数のユーザーを除いた全てのノート" _weekday: sunday: "日曜日" monday: "月曜日" @@ -2487,6 +2487,8 @@ _reversi: shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで" iStartedAGame: "対局し始めたで! #MisskeyReversi" opponentHasSettingsChanged: "相手が設定変えたで" + allowIrregularRules: "変則許可 (完全フリー)" + disallowIrregularRules: "変則なし" _offlineScreen: title: "オフライン - サーバーに接続できひんで" header: "サーバーに接続できへんわ" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 1d6c6f7bbc..29bfe5394d 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -515,7 +515,7 @@ objectStoragePrefixDesc: "요 Prefix 디렉토리 안에다가 파일이 들어 objectStorageEndpoint: "Endpoint" objectStorageEndpointDesc: "AWS S3을 쓸라멘 요는 비워두고, 아이멘은 그 서비스 가이드에 맞게 endpoint를 넣어 주이소. '' 내지 ':'처럼 넣십니다." objectStorageRegion: "Region" -objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 써먹을 서비스에 region 개념 같은 게 읎다! 카면은 대신에 'us-east-1'을 옇어 놓으이소. AWS 설정 파일이나 환경 변수를 갖다 끌어다 쓸 거면은 요는 비워 두이소." +objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 만약에 내 서비스엔 region 같은 개념이 읎다, 카면은 대신에 'us-east-1'라고 해 두이소. AWS 설정 파일이나 환경 변수를 끌어다 쓰겠다믄 요는 비워 두이소." objectStorageUseSSL: "SSL 쓰기" objectStorageUseSSLDesc: "API 호출할 때 HTTPS 안 쓸거면은 꺼 두이소" objectStorageUseProxy: "연결에 프락시 사용" @@ -538,7 +538,7 @@ volume: "음량" masterVolume: "대빵 음량" notUseSound: "음소거하기" useSoundOnlyWhenActive: "Misskey가 활성화되어 있을 때만 소리 내기" -details: "좀 더" +details: "자세히" chooseEmoji: "이모지 선택" unableToProcess: "작업 다 몬 했십니다" recentUsed: "최근 쓴 놈" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index de4cb3395b..718d04caae 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -2482,6 +2482,11 @@ _reversi: freeMatch: "프리매치" lookingForPlayer: "상대를 찾고 있습니다" gameCanceled: "대국이 취소되었습니다" + shareToTlTheGameWhenStart: "대국 시작 시 타임라인에 대국을 게시" + iStartedAGame: "대국이 시작되었습니다! #MisskeyReversi" + opponentHasSettingsChanged: "상대방이 설정을 변경했습니다" + allowIrregularRules: "규칙변경 허가 (완전 자유)" + disallowIrregularRules: "규칙변경 없음" _offlineScreen: title: "오프라인 - 서버에 접속할 수 없습니다" header: "서버에 접속할 수 없습니다" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index d14a77f5aa..09c210011c 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1185,6 +1185,7 @@ useGroupedNotifications: "分组显示通知" signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" cwNotationRequired: "在启用「隐藏内容」时必须输入注释" doReaction: "回应" +code: "代码" reloadRequiredToApplySettings: "需要重新载入来使设置生效" remainingN: "剩余:{n}" overwriteContentConfirm: "将覆盖现有内容。确定吗?" @@ -1202,9 +1203,14 @@ lastNDays: "最近 {n} 天" backToTitle: "返回标题" hemisphere: "居住地区" withSensitive: "显示包含敏感媒体的帖子" +userSaysSomethingSensitive: "含 {name} 敏感文件的帖子" enableHorizontalSwipe: "滑动切换标签页" _bubbleGame: howToPlay: "游戏说明" + _howToPlay: + section1: "对准位置将Emoji投入盒子。" + section2: "相同的Emoji相互接触合成后会得到新的Emoji,以此获得分数。" + section3: "如果Emoji从箱子中溢出游戏将会结束。在防止Emoji溢出的同时,不断合成新的Emoji,来获取更高的分数吧!" _announcement: forExistingUsers: "仅限现有用户" forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。" @@ -1577,6 +1583,10 @@ _achievements: description: "完成了教学" _bubbleGameExplodingHead: title: "🤯" + description: "你合成出了游戏里最大的Emoji" + _bubbleGameDoubleExplodingHead: + title: "两个🤯" + description: "你合成出了2个游戏里最大的Emoji" _role: new: "创建角色" edit: "编辑角色" @@ -2435,7 +2445,41 @@ _hemisphere: caption: "在某些客户端设置中用来确定季节" _reversi: reversi: "黑白棋" + gameSettings: "对局设置" + blackOrWhite: "先手/后手" + blackIs: "{name}执黑(先手)" + rules: "规则" + thisGameIsStartedSoon: "对局即将开始" + waitingForOther: "等待对手准备" + waitingForMe: "等待你的准备" + waitingBoth: "请准备" + ready: "准备就绪" + cancelReady: "重新准备" + opponentTurn: "对手的回合" + myTurn: "你的回合" + turnOf: "{name}的回合" + pastTurnOf: "{name}的回合" + timeout: "超时" + drawn: "平局" + won: "{name}获胜" + black: "黑" + white: "白" total: "总计" + turnCount: "第{count}回合" + myGames: "我的对局" + allGames: "所有对局" + ended: "结束" + playing: "对局中" + canPutEverywhere: "无限制放置模式" + timeLimitForEachTurn: "1回合的时间限制" + freeMatch: "自由匹配" + lookingForPlayer: "正在寻找对手" + gameCanceled: "对局被取消了" + shareToTlTheGameWhenStart: "开始时在时间线发布对局" + iStartedAGame: "对局开始!#MisskeyReversi" + opponentHasSettingsChanged: "对手更改了设定" + allowIrregularRules: "允许非常规规则(完全自由)" + disallowIrregularRules: "禁止非常规规则" _offlineScreen: title: "离线——无法连接到服务器" header: "无法连接到服务器" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 59f2ed6d2d..872a90bc6a 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1221,7 +1221,7 @@ _announcement: tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。" readConfirmTitle: "標記為已讀嗎?" readConfirmText: "閱讀「{title}」的內容並標記為已讀。" - shouldNotBeUsedToPresentPermanentInfo: "由於可能會破壞使用者體驗,尤其是對於新使用者而言,我們建議使用公告來發布有時效性的資訊而不是固定不變的資訊。" + shouldNotBeUsedToPresentPermanentInfo: "為了避免損害新用戶的使用體驗,建議使用公告來發布即時性的訊息,而不是用於固定不變的資訊。" dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。" silence: "不發送通知" silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。" diff --git a/package.json b/package.json index 487a963558..7af66b8e37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharkey", - "version": "2024.2.0-beta2", + "version": "2024.2.0-beta.9", "codename": "shonk", "repository": { "type": "git", diff --git a/packages/backend/generate_api_json.js b/packages/backend/generate_api_json.js index 5819c60a5f..4079b3bb0a 100644 --- a/packages/backend/generate_api_json.js +++ b/packages/backend/generate_api_json.js @@ -3,6 +3,6 @@ import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js' import { writeFileSync } from "node:fs"; const config = loadConfig(); -const spec = genOpenapiSpec(config); +const spec = genOpenapiSpec(config, true); writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8'); \ No newline at end of file diff --git a/packages/backend/migration/1706791962000-fix-meta-disableRegistration.js b/packages/backend/migration/1706791962000-fix-meta-disableRegistration.js new file mode 100644 index 0000000000..05469d5765 --- /dev/null +++ b/packages/backend/migration/1706791962000-fix-meta-disableRegistration.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class FixMetaDisableRegistration1706791962000 { + name = 'FixMetaDisableRegistration1706791962000' + + async up(queryRunner) { + await queryRunner.query(`alter table meta alter column "disableRegistration" set default true;`); + } + + async down(queryRunner) { + await queryRunner.query(`alter table meta alter column "disableRegistration" set default false;`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index f4865b3d05..296eddfe3c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -65,9 +65,9 @@ "dependencies": { "@aws-sdk/client-s3": "3.412.0", "@aws-sdk/lib-storage": "3.412.0", - "@bull-board/api": "5.10.2", - "@bull-board/fastify": "5.10.2", - "@bull-board/ui": "5.10.2", + "@bull-board/api": "5.14.0", + "@bull-board/fastify": "5.14.0", + "@bull-board/ui": "5.14.0", "@discordapp/twemoji": "15.0.2", "@fastify/accepts": "4.3.0", "@fastify/cookie": "9.3.1", @@ -84,11 +84,11 @@ "@nestjs/testing": "10.2.10", "@peertube/http-signature": "1.7.0", "@transfem-org/sfm-js": "0.24.4", - "@simplewebauthn/server": "9.0.0", + "@simplewebauthn/server": "9.0.1", "@sinonjs/fake-timers": "11.2.2", "@smithy/node-http-handler": "2.1.10", "@swc/cli": "0.1.63", - "@swc/core": "1.3.105", + "@swc/core": "1.3.107", "@twemoji/parser": "15.0.0", "accepts": "1.3.8", "ajv": "8.12.0", @@ -98,7 +98,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "5.1.4", + "bullmq": "5.1.5", "cacheable-lookup": "7.0.0", "cbor": "9.0.1", "chalk": "5.3.0", @@ -117,7 +117,7 @@ "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", "glob": "10.3.10", - "got": "14.0.0", + "got": "14.1.0", "happy-dom": "10.0.3", "hpagent": "1.2.0", "http-link-header": "1.1.1", @@ -148,7 +148,7 @@ "otpauth": "9.2.2", "parse5": "7.1.2", "pg": "8.11.3", - "pkce-challenge": "4.0.1", + "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -169,12 +169,12 @@ "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.21.23", + "systeminformation": "5.21.24", "tinycolor2": "1.6.0", "tmp": "0.2.1", "tsc-alias": "1.8.8", "tsconfig-paths": "4.2.0", - "typeorm": "0.3.19", + "typeorm": "0.3.20", "typescript": "5.3.3", "ulid": "2.3.0", "uuid": "^9.0.1", @@ -186,7 +186,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@misskey-dev/eslint-plugin": "1.0.0", - "@nestjs/platform-express": "10.3.0", + "@nestjs/platform-express": "10.3.1", "@simplewebauthn/typescript-types": "8.3.4", "@swc/jest": "0.2.31", "@types/accepts": "1.3.7", @@ -205,13 +205,13 @@ "@types/jsrsasign": "10.5.12", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "20.11.5", + "@types/node": "20.11.10", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.14", "@types/oauth": "0.9.4", "@types/oauth2orize": "1.11.3", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.10.9", + "@types/pg": "8.11.0", "@types/pug": "2.0.10", "@types/punycode": "2.1.3", "@types/qrcode": "1.5.5", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 972d1be0ee..38003504bc 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -76,10 +76,10 @@ type Source = { deliverJobConcurrency?: number; inboxJobConcurrency?: number; - relashionshipJobConcurrency?: number; + relationshipJobConcurrency?: number; deliverJobPerSec?: number; inboxJobPerSec?: number; - relashionshipJobPerSec?: number; + relationshipJobPerSec?: number; deliverJobMaxAttempts?: number; inboxJobMaxAttempts?: number; @@ -141,10 +141,10 @@ export type Config = { outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined; deliverJobConcurrency: number | undefined; inboxJobConcurrency: number | undefined; - relashionshipJobConcurrency: number | undefined; + relationshipJobConcurrency: number | undefined; deliverJobPerSec: number | undefined; inboxJobPerSec: number | undefined; - relashionshipJobPerSec: number | undefined; + relationshipJobPerSec: number | undefined; deliverJobMaxAttempts: number | undefined; inboxJobMaxAttempts: number | undefined; proxyRemoteFiles: boolean | undefined; @@ -257,10 +257,10 @@ export function loadConfig(): Config { outgoingAddressFamily: config.outgoingAddressFamily, deliverJobConcurrency: config.deliverJobConcurrency, inboxJobConcurrency: config.inboxJobConcurrency, - relashionshipJobConcurrency: config.relashionshipJobConcurrency, + relationshipJobConcurrency: config.relationshipJobConcurrency, deliverJobPerSec: config.deliverJobPerSec, inboxJobPerSec: config.inboxJobPerSec, - relashionshipJobPerSec: config.relashionshipJobPerSec, + relationshipJobPerSec: config.relationshipJobPerSec, deliverJobMaxAttempts: config.deliverJobMaxAttempts, inboxJobMaxAttempts: config.inboxJobMaxAttempts, proxyRemoteFiles: config.proxyRemoteFiles, diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index 350aa6ba24..9d4d9219ef 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -96,7 +96,7 @@ export class AccountMoveService { await this.apDeliverManagerService.deliverToFollowers(src, moveAct); // Publish meUpdated event - const iObj = await this.userEntityService.pack(src.id, src, { detail: true, includeSecrets: true }); + const iObj = await this.userEntityService.pack(src.id, src, { schema: 'MeDetailed', includeSecrets: true }); this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj); // Unfollow after 24 hours diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index dbbe5ecdc0..0b90e88182 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -54,15 +54,15 @@ export interface MainEventTypes { reply: Packed<'Note'>; renote: Packed<'Note'>; follow: Packed<'UserDetailedNotMe'>; - followed: Packed<'User'>; - unfollow: Packed<'User'>; - meUpdated: Packed<'User'>; + followed: Packed<'UserLite'>; + unfollow: Packed<'UserDetailedNotMe'>; + meUpdated: Packed<'MeDetailed'>; pageEvent: { pageId: MiPage['id']; event: string; var: any; userId: MiUser['id']; - user: Packed<'User'>; + user: Packed<'UserDetailed'>; }; urlUploadFinished: { marker?: string | null; @@ -92,7 +92,7 @@ export interface MainEventTypes { }; driveFileCreated: Packed<'DriveFile'>; readAntenna: MiAntenna; - receiveFollowRequest: Packed<'User'>; + receiveFollowRequest: Packed<'UserLite'>; announcementCreated: { announcement: Packed<'Announcement'>; }; @@ -143,8 +143,8 @@ type NoteStreamEventTypes = { }; export interface UserListEventTypes { - userAdded: Packed<'User'>; - userRemoved: Packed<'User'>; + userAdded: Packed<'UserLite'>; + userRemoved: Packed<'UserLite'>; } export interface AntennaEventTypes { diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 39b19325c3..c267849908 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -109,13 +109,13 @@ export class UserBlockingService implements OnModuleInit { if (this.userEntityService.isLocalUser(followee)) { this.userEntityService.pack(followee, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } if (this.userEntityService.isLocalUser(follower) && !silent) { this.userEntityService.pack(followee, follower, { - detail: true, + schema: 'UserDetailedNotMe', }).then(async packed => { this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index d600ffb9d9..93e9fbbd70 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -293,9 +293,9 @@ export class UserFollowingService implements OnModuleInit { if (this.userEntityService.isLocalUser(follower) && !silent) { // Publish follow event this.userEntityService.pack(followee.id, follower, { - detail: true, + schema: 'UserDetailedNotMe', }).then(async packed => { - this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); + this.globalEventService.publishMainStream(follower.id, 'follow', packed); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); for (const webhook of webhooks) { @@ -360,7 +360,7 @@ export class UserFollowingService implements OnModuleInit { if (!silent && this.userEntityService.isLocalUser(follower)) { // Publish unfollow event this.userEntityService.pack(followee.id, follower, { - detail: true, + schema: 'UserDetailedNotMe', }).then(async packed => { this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); @@ -500,7 +500,7 @@ export class UserFollowingService implements OnModuleInit { this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed)); this.userEntityService.pack(followee.id, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); // 通知を作成 @@ -548,7 +548,7 @@ export class UserFollowingService implements OnModuleInit { }); this.userEntityService.pack(followee.id, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } @@ -576,7 +576,7 @@ export class UserFollowingService implements OnModuleInit { } this.userEntityService.pack(followee.id, followee, { - detail: true, + schema: 'MeDetailed', }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } @@ -696,7 +696,7 @@ export class UserFollowingService implements OnModuleInit { @bindThis private async publishUnfollow(followee: Both, follower: Local): Promise { const packedFollowee = await this.userEntityService.pack(followee.id, follower, { - detail: true, + schema: 'UserDetailedNotMe', }); this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee); diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index 8d0a89f2d6..b1cde2f6e2 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -94,6 +94,29 @@ type ToJsonSchema = { }; export function getJsonSchema(schema: S): ToJsonSchema>> { + const unflatten = (str: string, parent: Record) => { + const keys = str.split('.'); + const key = keys.shift(); + const nextKey = keys[0]; + + if (key == null) return; + + if (parent.properties[key] == null) { + parent.properties[key] = nextKey ? { + type: 'object', + properties: {}, + required: [], + } : { + type: 'array', + items: { + type: 'number', + }, + }; + } + + if (nextKey) unflatten(keys.join('.'), parent.properties[key] as Record); + }; + const jsonSchema = { type: 'object', properties: {} as Record, @@ -101,10 +124,7 @@ export function getJsonSchema(schema: S): ToJsonSchema>>; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 97de891ece..e7814161b8 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -38,13 +38,13 @@ export class AbuseUserReportEntityService { targetUserId: report.targetUserId, assigneeId: report.assigneeId, reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, { - detail: true, + schema: 'UserDetailedNotMe', }), targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, { - detail: true, + schema: 'UserDetailedNotMe', }), assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, { - detail: true, + schema: 'UserDetailedNotMe', }) : null, forwarded: report.forwarded, }); diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index b4760346b7..f5abf67322 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -37,7 +37,7 @@ export class BlockingEntityService { createdAt: this.idService.parse(blocking.id).date.toISOString(), blockeeId: blocking.blockeeId, blockee: this.userEntityService.pack(blocking.blockeeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index dc335d9975..70faa2b380 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -42,7 +42,7 @@ export class FlashEntityService { createdAt: this.idService.parse(flash.id).date.toISOString(), updatedAt: flash.updatedAt.toISOString(), userId: flash.userId, - user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意 + user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意 title: flash.title, summary: flash.summary, script: flash.script, diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index 52aa979677..f9bc9fa1ab 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -89,10 +89,10 @@ export class FollowingEntityService { followeeId: following.followeeId, followerId: following.followerId, followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }) : undefined, follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, { - detail: true, + schema: 'UserDetailedNotMe', }) : undefined, }); } diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 6729ca2671..3add9dbc74 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -37,7 +37,7 @@ export class ModerationLogEntityService { info: log.info, userId: log.userId, user: this.userEntityService.pack(log.user ?? log.userId, null, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index 9d672169ba..5326955126 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -39,7 +39,7 @@ export class MutingEntityService { expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index a5d3054462..1cbd5cb70c 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -181,7 +181,7 @@ export class NoteEntityService implements OnModuleInit { return { multiple: poll.multiple, - expiresAt: poll.expiresAt, + expiresAt: poll.expiresAt?.toISOString() ?? null, choices, }; } @@ -342,9 +342,7 @@ export class NoteEntityService implements OnModuleInit { createdAt: this.idService.parse(note.id).date.toISOString(), updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined, userId: note.userId, - user: this.userEntityService.pack(note.user ?? note.userId, me, { - detail: false, - }), + user: this.userEntityService.pack(note.user ?? note.userId, me), text: text, cw: note.cw, visibility: note.visibility, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 704081ed00..a620acc2dc 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -62,7 +62,7 @@ export class NotificationEntityService implements OnModuleInit { }, hint?: { packedNotes: Map>; - packedUsers: Map>; + packedUsers: Map>; }, ): Promise> { const notification = src; @@ -76,9 +76,7 @@ export class NotificationEntityService implements OnModuleInit { const userIfNeed = 'notifierId' in notification ? ( hint?.packedUsers != null ? hint.packedUsers.get(notification.notifierId) - : this.userEntityService.pack(notification.notifierId, { id: meId }, { - detail: false, - }) + : this.userEntityService.pack(notification.notifierId, { id: meId }) ) : undefined; const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined; @@ -131,9 +129,7 @@ export class NotificationEntityService implements OnModuleInit { const users = userIds.length > 0 ? await this.usersRepository.find({ where: { id: In(userIds) }, }) : []; - const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { - detail: false, - }); + const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); // 既に解決されたフォローリクエストの通知を除外 @@ -161,7 +157,7 @@ export class NotificationEntityService implements OnModuleInit { }, hint?: { packedNotes: Map>; - packedUsers: Map>; + packedUsers: Map>; }, ): Promise> { const notification = src; @@ -175,18 +171,14 @@ export class NotificationEntityService implements OnModuleInit { const userIfNeed = 'notifierId' in notification ? ( hint?.packedUsers != null ? hint.packedUsers.get(notification.notifierId) - : this.userEntityService.pack(notification.notifierId, { id: meId }, { - detail: false, - }) + : this.userEntityService.pack(notification.notifierId, { id: meId }) ) : undefined; if (notification.type === 'reaction:grouped') { const reactions = await Promise.all(notification.reactions.map(async reaction => { const user = hint?.packedUsers != null ? hint.packedUsers.get(reaction.userId)! - : await this.userEntityService.pack(reaction.userId, { id: meId }, { - detail: false, - }); + : await this.userEntityService.pack(reaction.userId, { id: meId }); return { user, reaction: reaction.reaction, @@ -206,9 +198,7 @@ export class NotificationEntityService implements OnModuleInit { return packedUser; } - return this.userEntityService.pack(userId, { id: meId }, { - detail: false, - }); + return this.userEntityService.pack(userId, { id: meId }); })); return await awaitAll({ id: notification.id, @@ -275,9 +265,7 @@ export class NotificationEntityService implements OnModuleInit { const users = userIds.length > 0 ? await this.usersRepository.find({ where: { id: In(userIds) }, }) : []; - const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, { - detail: false, - }); + const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); // 既に解決されたフォローリクエストの通知を除外 diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index f39ef949db..bc26362aba 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -90,7 +90,7 @@ export class PageEntityService { createdAt: this.idService.parse(page.id).date.toISOString(), updatedAt: page.updatedAt.toISOString(), userId: page.userId, - user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意 + user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意 content: page.content, variables: page.variables, title: page.title, diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts index 3f9dc9180a..5ad28f2e6a 100644 --- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts +++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts @@ -38,7 +38,7 @@ export class RenoteMutingEntityService { createdAt: this.idService.parse(muting.id).date.toISOString(), muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { - detail: true, + schema: 'UserDetailedNotMe', }), }); } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index adb7dfbf86..6c1a02d9d8 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -30,14 +30,6 @@ import type { NoteEntityService } from './NoteEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; import type { PageEntityService } from './PageEntityService.js'; -type IsUserDetailed = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; -type IsMeAndIsUserDetailed = - Detailed extends true ? - ExpectsMe extends true ? Packed<'MeDetailed'> : - ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : - Packed<'UserDetailed'> : - Packed<'UserLite'>; - const Ajv = _Ajv.default; const ajv = new Ajv(); @@ -304,17 +296,17 @@ export class UserEntityService implements OnModuleInit { return `${this.config.url}/users/${userId}`; } - public async pack( + public async pack( src: MiUser['id'] | MiUser, me?: { id: MiUser['id']; } | null | undefined, options?: { - detail?: D, + schema?: S, includeSecrets?: boolean, userProfile?: MiUserProfile, }, - ): Promise> { + ): Promise> { const opts = Object.assign({ - detail: false, + schema: 'UserLite', includeSecrets: false, }, options); @@ -346,19 +338,20 @@ export class UserEntityService implements OnModuleInit { }); } + const isDetailed = opts.schema !== 'UserLite'; const meId = me ? me.id : null; const isMe = meId === user.id; const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; - const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; - const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin') + const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null; + const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin') .where('pin.userId = :userId', { userId: user.id }) .innerJoinAndSelect('pin.note', 'note') .orderBy('pin.id', 'DESC') .getMany() : []; - const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; + const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null; - const mastoapi = !opts.detail ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null; + const mastoapi = !isDetailed ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null; const followingCount = profile == null ? null : (profile.followingVisibility === 'public') || isMe ? user.followingCount : @@ -370,17 +363,16 @@ export class UserEntityService implements OnModuleInit { (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; - const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null; - const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null; - const unreadAnnouncements = isMe && opts.detail ? + const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null; + const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null; + const unreadAnnouncements = isMe && isDetailed ? (await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({ createdAt: this.idService.parse(announcement.id).date.toISOString(), ...announcement, })) : null; const checkHost = user.host == null ? this.config.host : user.host; - - const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null; + const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null; const packed = { id: user.id, @@ -425,7 +417,7 @@ export class UserEntityService implements OnModuleInit { displayOrder: r.displayOrder, }))) : undefined, - ...(opts.detail ? { + ...(isDetailed ? { url: profile!.url, uri: user.uri, movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null, @@ -453,7 +445,7 @@ export class UserEntityService implements OnModuleInit { }), pinnedPageId: profile!.pinnedPageId, pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null, - publicReactions: profile!.publicReactions, + publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964 followersVisibility: profile!.followersVisibility, followingVisibility: profile!.followingVisibility, twoFactorEnabled: profile!.twoFactorEnabled, @@ -480,7 +472,7 @@ export class UserEntityService implements OnModuleInit { moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined, } : {}), - ...(opts.detail && isMe ? { + ...(isDetailed && isMe ? { avatarId: user.avatarId, bannerId: user.bannerId, backgroundId: user.backgroundId, @@ -554,19 +546,19 @@ export class UserEntityService implements OnModuleInit { notify: relation.following?.notify ?? 'none', withReplies: relation.following?.withReplies ?? false, } : {}), - } as Promiseable> as Promiseable>; + } as Promiseable>; return await awaitAll(packed); } - public packMany( + public packMany( users: (MiUser['id'] | MiUser)[], me?: { id: MiUser['id'] } | null | undefined, options?: { - detail?: D, + schema?: S, includeSecrets?: boolean, }, - ): Promise[]> { + ): Promise[]> { return Promise.all(users.map(u => this.pack(u, me, options))); } } diff --git a/packages/backend/src/misc/clone.ts b/packages/backend/src/misc/clone.ts index 9d20deac3b..52e6c825f9 100644 --- a/packages/backend/src/misc/clone.ts +++ b/packages/backend/src/misc/clone.ts @@ -6,7 +6,7 @@ // structredCloneが遅いため // SEE: http://var.blog.jp/archives/86038606.html -type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[]; +type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[]; export function deepClone(x: T): T { if (typeof x === 'object') { @@ -14,7 +14,7 @@ export function deepClone(x: T): T { if (Array.isArray(x)) return x.map(deepClone) as T; const obj = {} as Record; for (const [k, v] of Object.entries(x)) { - obj[k] = deepClone(v); + obj[k] = v === undefined ? undefined : deepClone(v); } return obj as T; } else { diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index b4f0541712..dc29003681 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -25,7 +25,7 @@ import { packedBlockingSchema } from '@/models/json-schema/blocking.js'; import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; -import { packedPageSchema } from '@/models/json-schema/page.js'; +import { packedPageSchema, packedPageBlockSchema } from '@/models/json-schema/page.js'; import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js'; import { packedChannelSchema } from '@/models/json-schema/channel.js'; import { packedAntennaSchema } from '@/models/json-schema/antenna.js'; @@ -37,7 +37,7 @@ import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/jso import { packedFlashSchema } from '@/models/json-schema/flash.js'; import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js'; import { packedSigninSchema } from '@/models/json-schema/signin.js'; -import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js'; +import { packedRoleLiteSchema, packedRoleSchema, packedRolePoliciesSchema } from '@/models/json-schema/role.js'; import { packedAdSchema } from '@/models/json-schema/ad.js'; import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js'; @@ -67,6 +67,7 @@ export const refs = { Hashtag: packedHashtagSchema, InviteCode: packedInviteCodeSchema, Page: packedPageSchema, + PageBlock: packedPageBlockSchema, Channel: packedChannelSchema, QueueCount: packedQueueCountSchema, Antenna: packedAntennaSchema, @@ -79,12 +80,16 @@ export const refs = { Signin: packedSigninSchema, RoleLite: packedRoleLiteSchema, Role: packedRoleSchema, + RolePolicies: packedRolePoliciesSchema, ReversiGameLite: packedReversiGameLiteSchema, ReversiGameDetailed: packedReversiGameDetailedSchema, }; export type Packed = SchemaType; +export type KeyOf = PropertiesToUnion; +type PropertiesToUnion

= p['properties'] extends NonNullable ? keyof p['properties'] : never; + type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any'; type StringDefToType = T extends 'null' ? null : @@ -114,6 +119,7 @@ export interface Schema extends OfSchema { readonly example?: any; readonly format?: string; readonly ref?: keyof typeof refs; + readonly selfRef?: boolean; readonly enum?: ReadonlyArray; readonly default?: (this['type'] extends TypeStringef ? StringDefToType : any) | null; readonly maxLength?: number; diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts index 8f8be88fed..c2d9e9878c 100644 --- a/packages/backend/src/models/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -38,7 +38,7 @@ export class MiAnnouncement { length: 256, nullable: false, default: 'info', }) - public icon: string; + public icon: 'info' | 'warning' | 'error' | 'success'; // normal ... お知らせページ掲載 // banner ... お知らせページ掲載 + バナー表示 @@ -47,7 +47,7 @@ export class MiAnnouncement { length: 256, nullable: false, default: 'normal', }) - public display: string; + public display: 'normal' | 'banner' | 'dialog'; @Column('boolean', { default: false, diff --git a/packages/backend/src/models/json-schema/announcement.ts b/packages/backend/src/models/json-schema/announcement.ts index 78a98872b2..57fd7d605d 100644 --- a/packages/backend/src/models/json-schema/announcement.ts +++ b/packages/backend/src/models/json-schema/announcement.ts @@ -37,10 +37,12 @@ export const packedAnnouncementSchema = { icon: { type: 'string', optional: false, nullable: false, + enum: ['info', 'warning', 'error', 'success'], }, display: { type: 'string', optional: false, nullable: false, + enum: ['dialog', 'normal', 'banner'], }, needConfirmationToRead: { type: 'boolean', diff --git a/packages/backend/src/models/json-schema/blocking.ts b/packages/backend/src/models/json-schema/blocking.ts index 0b58f1f8d7..1b3227d455 100644 --- a/packages/backend/src/models/json-schema/blocking.ts +++ b/packages/backend/src/models/json-schema/blocking.ts @@ -25,7 +25,7 @@ export const packedBlockingSchema = { blockee: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/following.ts b/packages/backend/src/models/json-schema/following.ts index e92cff20a1..dd3234ee5a 100644 --- a/packages/backend/src/models/json-schema/following.ts +++ b/packages/backend/src/models/json-schema/following.ts @@ -30,12 +30,12 @@ export const packedFollowingSchema = { followee: { type: 'object', optional: true, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, follower: { type: 'object', optional: true, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/muting.ts b/packages/backend/src/models/json-schema/muting.ts index dde9dc0288..c3d0bb603c 100644 --- a/packages/backend/src/models/json-schema/muting.ts +++ b/packages/backend/src/models/json-schema/muting.ts @@ -30,7 +30,7 @@ export const packedMutingSchema = { mutee: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 2b7722129b..929f697e8a 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -69,6 +69,7 @@ export const packedNoteSchema = { visibility: { type: 'string', optional: false, nullable: false, + enum: ['public', 'home', 'followers', 'specified'], }, mentions: { type: 'array', @@ -117,6 +118,48 @@ export const packedNoteSchema = { poll: { type: 'object', optional: true, nullable: true, + properties: { + expiresAt: { + type: 'string', + optional: true, nullable: true, + format: 'date-time', + }, + multiple: { + type: 'boolean', + optional: false, nullable: false, + }, + choices: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + isVoted: { + type: 'boolean', + optional: false, nullable: false, + }, + text: { + type: 'string', + optional: false, nullable: false, + }, + votes: { + type: 'number', + optional: false, nullable: false, + }, + }, + }, + }, + }, + }, + emojis: { + type: 'object', + optional: true, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'string', + }], + }, }, channelId: { type: 'string', @@ -162,9 +205,23 @@ export const packedNoteSchema = { type: 'string', optional: false, nullable: true, }, + reactionEmojis: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'string', + }], + }, + }, reactions: { type: 'object', optional: false, nullable: false, + additionalProperties: { + anyOf: [{ + type: 'number', + }], + }, }, renoteCount: { type: 'number', @@ -196,7 +253,7 @@ export const packedNoteSchema = { }, myReaction: { - type: 'object', + type: 'string', optional: true, nullable: true, }, }, diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index c6d6e84317..6286950de5 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -5,7 +5,7 @@ import { notificationTypes } from '@/types.js'; -export const packedNotificationSchema = { +const baseSchema = { type: 'object', properties: { id: { @@ -23,68 +23,368 @@ export const packedNotificationSchema = { optional: false, nullable: false, enum: [...notificationTypes, 'reaction:grouped', 'renote:grouped'], }, - user: { - type: 'object', - ref: 'UserLite', - optional: true, nullable: true, - }, - userId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - }, - note: { - type: 'object', - ref: 'Note', - optional: true, nullable: true, - }, - reaction: { - type: 'string', - optional: true, nullable: true, - }, - achievement: { - type: 'string', - optional: true, nullable: false, - }, - body: { - type: 'string', - optional: true, nullable: true, - }, - header: { - type: 'string', - optional: true, nullable: true, - }, - icon: { - type: 'string', - optional: true, nullable: true, - }, - reactions: { - type: 'array', - optional: true, nullable: true, - items: { - type: 'object', - properties: { - user: { - type: 'object', - ref: 'UserLite', - optional: false, nullable: false, - }, - reaction: { - type: 'string', - optional: false, nullable: false, - }, - }, - required: ['user', 'reaction'], + }, +} as const; + +export const packedNotificationSchema = { + type: 'object', + oneOf: [{ + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['note'], }, - }, - users: { - type: 'array', - optional: true, nullable: true, - items: { + user: { type: 'object', ref: 'UserLite', optional: false, nullable: false, }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, }, - }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['mention'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['reply'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['renote'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['quote'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['reaction'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + reaction: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['pollEnded'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['follow'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['receiveFollowRequest'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['followRequestAccepted'], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['roleAssigned'], + }, + role: { + type: 'object', + ref: 'Role', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['achievementEarned'], + }, + achievement: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['app'], + }, + body: { + type: 'string', + optional: false, nullable: false, + }, + header: { + type: 'string', + optional: false, nullable: false, + }, + icon: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['reaction:grouped'], + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + reactions: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + properties: { + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + reaction: { + type: 'string', + optional: false, nullable: false, + }, + }, + required: ['user', 'reaction'], + }, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['renote:grouped'], + }, + note: { + type: 'object', + ref: 'Note', + optional: false, nullable: false, + }, + users: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['test'], + }, + }, + }], } as const; diff --git a/packages/backend/src/models/json-schema/page.ts b/packages/backend/src/models/json-schema/page.ts index 9baacd6884..e4d844645e 100644 --- a/packages/backend/src/models/json-schema/page.ts +++ b/packages/backend/src/models/json-schema/page.ts @@ -3,6 +3,108 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +const blockBaseSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; + +const textBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['text'], + }, + text: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; + +const sectionBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['section'], + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + children: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'PageBlock', + selfRef: true, + }, + }, + }, +} as const; + +const imageBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['image'], + }, + fileId: { + type: 'string', + optional: false, nullable: true, + }, + }, +} as const; + +const noteBlockSchema = { + type: 'object', + properties: { + ...blockBaseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['note'], + }, + detailed: { + type: 'boolean', + optional: false, nullable: false, + }, + note: { + type: 'string', + optional: false, nullable: true, + }, + }, +} as const; + +export const packedPageBlockSchema = { + type: 'object', + oneOf: [ + textBlockSchema, + sectionBlockSchema, + imageBlockSchema, + noteBlockSchema, + ], +} as const; + export const packedPageSchema = { type: 'object', properties: { @@ -38,6 +140,7 @@ export const packedPageSchema = { items: { type: 'object', optional: false, nullable: false, + ref: 'PageBlock', }, }, variables: { diff --git a/packages/backend/src/models/json-schema/renote-muting.ts b/packages/backend/src/models/json-schema/renote-muting.ts index feed1ceb09..769b33f515 100644 --- a/packages/backend/src/models/json-schema/renote-muting.ts +++ b/packages/backend/src/models/json-schema/renote-muting.ts @@ -25,7 +25,7 @@ export const packedRenoteMutingSchema = { mutee: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, } as const; diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index b0c6804bb8..55348d4f3d 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -1,26 +1,103 @@ -const rolePolicyValue = { +export const packedRolePoliciesSchema = { type: 'object', + optional: false, nullable: false, properties: { - value: { - oneOf: [ - { - type: 'integer', - optional: false, nullable: false, - }, - { - type: 'boolean', - optional: false, nullable: false, - }, - ], + gtlAvailable: { + type: 'boolean', + optional: false, nullable: false, }, - priority: { + ltlAvailable: { + type: 'boolean', + optional: false, nullable: false, + }, + canPublicNote: { + type: 'boolean', + optional: false, nullable: false, + }, + canInvite: { + type: 'boolean', + optional: false, nullable: false, + }, + inviteLimit: { type: 'integer', optional: false, nullable: false, }, - useDefault: { + inviteLimitCycle: { + type: 'integer', + optional: false, nullable: false, + }, + inviteExpirationTime: { + type: 'integer', + optional: false, nullable: false, + }, + canManageCustomEmojis: { type: 'boolean', optional: false, nullable: false, }, + canManageAvatarDecorations: { + type: 'boolean', + optional: false, nullable: false, + }, + canSearchNotes: { + type: 'boolean', + optional: false, nullable: false, + }, + canUseTranslator: { + type: 'boolean', + optional: false, nullable: false, + }, + canHideAds: { + type: 'boolean', + optional: false, nullable: false, + }, + driveCapacityMb: { + type: 'integer', + optional: false, nullable: false, + }, + alwaysMarkNsfw: { + type: 'boolean', + optional: false, nullable: false, + }, + pinLimit: { + type: 'integer', + optional: false, nullable: false, + }, + antennaLimit: { + type: 'integer', + optional: false, nullable: false, + }, + wordMuteLimit: { + type: 'integer', + optional: false, nullable: false, + }, + webhookLimit: { + type: 'integer', + optional: false, nullable: false, + }, + clipLimit: { + type: 'integer', + optional: false, nullable: false, + }, + noteEachClipsLimit: { + type: 'integer', + optional: false, nullable: false, + }, + userListLimit: { + type: 'integer', + optional: false, nullable: false, + }, + userEachUserListsLimit: { + type: 'integer', + optional: false, nullable: false, + }, + rateLimitFactor: { + type: 'integer', + optional: false, nullable: false, + }, + avatarDecorationLimit: { + type: 'integer', + optional: false, nullable: false, + }, }, } as const; @@ -121,31 +198,28 @@ export const packedRoleSchema = { policies: { type: 'object', optional: false, nullable: false, - properties: { - pinLimit: rolePolicyValue, - canInvite: rolePolicyValue, - clipLimit: rolePolicyValue, - canHideAds: rolePolicyValue, - inviteLimit: rolePolicyValue, - antennaLimit: rolePolicyValue, - gtlAvailable: rolePolicyValue, - ltlAvailable: rolePolicyValue, - webhookLimit: rolePolicyValue, - canPublicNote: rolePolicyValue, - userListLimit: rolePolicyValue, - wordMuteLimit: rolePolicyValue, - alwaysMarkNsfw: rolePolicyValue, - canSearchNotes: rolePolicyValue, - driveCapacityMb: rolePolicyValue, - rateLimitFactor: rolePolicyValue, - inviteLimitCycle: rolePolicyValue, - noteEachClipsLimit: rolePolicyValue, - inviteExpirationTime: rolePolicyValue, - canManageCustomEmojis: rolePolicyValue, - userEachUserListsLimit: rolePolicyValue, - canManageAvatarDecorations: rolePolicyValue, - canUseTranslator: rolePolicyValue, - avatarDecorationLimit: rolePolicyValue, + additionalProperties: { + anyOf: [{ + type: 'object', + properties: { + value: { + oneOf: [ + { + type: 'integer', + }, + { + type: 'boolean', + }, + ], + }, + priority: { + type: 'integer', + }, + useDefault: { + type: 'boolean', + }, + }, + }], }, }, usersCount: { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index af67e62afa..c343178fa2 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -628,104 +628,7 @@ export const packedMeDetailedOnlySchema = { policies: { type: 'object', nullable: false, optional: false, - properties: { - gtlAvailable: { - type: 'boolean', - nullable: false, optional: false, - }, - ltlAvailable: { - type: 'boolean', - nullable: false, optional: false, - }, - canPublicNote: { - type: 'boolean', - nullable: false, optional: false, - }, - canInvite: { - type: 'boolean', - nullable: false, optional: false, - }, - inviteLimit: { - type: 'number', - nullable: false, optional: false, - }, - inviteLimitCycle: { - type: 'number', - nullable: false, optional: false, - }, - inviteExpirationTime: { - type: 'number', - nullable: false, optional: false, - }, - canManageCustomEmojis: { - type: 'boolean', - nullable: false, optional: false, - }, - canManageAvatarDecorations: { - type: 'boolean', - nullable: false, optional: false, - }, - canSearchNotes: { - type: 'boolean', - nullable: false, optional: false, - }, - canUseTranslator: { - type: 'boolean', - nullable: false, optional: false, - }, - canHideAds: { - type: 'boolean', - nullable: false, optional: false, - }, - driveCapacityMb: { - type: 'number', - nullable: false, optional: false, - }, - alwaysMarkNsfw: { - type: 'boolean', - nullable: false, optional: false, - }, - pinLimit: { - type: 'number', - nullable: false, optional: false, - }, - antennaLimit: { - type: 'number', - nullable: false, optional: false, - }, - wordMuteLimit: { - type: 'number', - nullable: false, optional: false, - }, - webhookLimit: { - type: 'number', - nullable: false, optional: false, - }, - clipLimit: { - type: 'number', - nullable: false, optional: false, - }, - noteEachClipsLimit: { - type: 'number', - nullable: false, optional: false, - }, - userListLimit: { - type: 'number', - nullable: false, optional: false, - }, - userEachUserListsLimit: { - type: 'number', - nullable: false, optional: false, - }, - rateLimitFactor: { - type: 'number', - nullable: false, optional: false, - }, - avatarDecorationLimit: { - type: 'number', - nullable: false, optional: false, - }, - }, + ref: 'RolePolicies', }, //#region secrets email: { @@ -820,13 +723,5 @@ export const packedUserSchema = { type: 'object', ref: 'UserDetailed', }, - { - type: 'object', - ref: 'UserDetailedNotMe', - }, - { - type: 'object', - ref: 'MeDetailed', - }, ], } as const; diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index cdca744b88..d9923ade13 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -295,9 +295,9 @@ export class QueueProcessorService implements OnApplicationShutdown { }, { ...baseQueueOptions(this.config, QUEUE.RELATIONSHIP), autorun: false, - concurrency: this.config.relashionshipJobConcurrency ?? 16, + concurrency: this.config.relationshipJobConcurrency ?? 16, limiter: { - max: this.config.relashionshipJobPerSec ?? 64, + max: this.config.relationshipJobPerSec ?? 64, duration: 1000, }, }); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 3b43b931ae..4a0f109a08 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -206,7 +206,7 @@ export class ServerService implements OnApplicationShutdown { }); this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 1758c03aca..386e593636 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -157,7 +157,7 @@ export class ApiServerService { return { ok: true, token: token.token, - user: await this.userEntityService.pack(token.userId, null, { detail: true }), + user: await this.userEntityService.pack(token.userId, null, { schema: 'UserDetailedNotMe' }), }; } else { return { diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 8788a1fd64..d2e6185aa3 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -260,7 +260,7 @@ export class SignupApiService { }); const res = await this.userEntityService.pack(account, account, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, }); diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 8de5171475..9a4e45522d 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -4,8 +4,7 @@ */ import { permissions } from 'misskey-js'; -import type { Schema } from '@/misc/json-schema.js'; -import { RolePolicies } from '@/core/RoleService.js'; +import type { KeyOf, Schema } from '@/misc/json-schema.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; @@ -802,7 +801,7 @@ interface IEndpointMetaBase { */ readonly requireAdmin?: boolean; - readonly requireRolePolicy?: keyof RolePolicies; + readonly requireRolePolicy?: KeyOf<'RolePolicies'>; /** * 引っ越し済みのユーザーによるリクエストを禁止するか diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 3484d6707a..4ffefa05b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -62,17 +62,17 @@ export const meta = { reporter: { type: 'object', nullable: false, optional: false, - ref: 'User', + ref: 'UserDetailedNotMe', }, targetUser: { type: 'object', nullable: false, optional: false, - ref: 'User', + ref: 'UserDetailedNotMe', }, assignee: { type: 'object', nullable: true, optional: true, - ref: 'User', + ref: 'UserDetailedNotMe', }, }, }, diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index f54d567fff..b18a7e0e41 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -11,6 +11,7 @@ import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { localUsernameSchema, passwordSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; +import { Packed } from '@/misc/json-schema.js'; export const meta = { tags: ['admin'], @@ -18,7 +19,7 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'MeDetailed', properties: { token: { type: 'string', @@ -60,11 +61,11 @@ export default class extends Endpoint { // eslint- }); const res = await this.userEntityService.pack(account, account, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, - }); + }) as Packed<'MeDetailed'> & { token: string }; - (res as any).token = secret; + res.token = secret; return res; }); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts index 93673453d6..80b198eb80 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts @@ -27,7 +27,7 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'User', + ref: 'UserDetailedNotMe', }, } as const; @@ -58,7 +58,7 @@ export default class extends Endpoint { // eslint- } const res = await this.userEntityService.pack(profile.user!, null, { - detail: true, + schema: 'UserDetailedNotMe', }); return res; diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 66f4d9d26b..1e05685991 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -40,7 +40,7 @@ export const meta = { }, required: ['id', 'createdAt', 'user'], }, - } + }, } as const; export const paramDef = { @@ -92,7 +92,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(assigns.map(async assign => ({ id: assign.id, createdAt: this.idService.parse(assign.id).date.toISOString(), - user: await this.userEntityService.pack(assign.user!, me, { detail: true }), + user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }), expiresAt: assign.expiresAt?.toISOString() ?? null, }))); }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index f3601be9bb..51b5a02600 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -50,7 +50,7 @@ export const meta = { user: { type: 'object', optional: false, nullable: false, - ref: 'UserDetailed', + ref: 'UserDetailedNotMe', }, }, }, diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 6267fb97b2..710ca69e55 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -115,7 +115,7 @@ export default class extends Endpoint { // eslint- const users = await query.getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 8ab16880fa..eb3ce40313 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -148,7 +148,7 @@ export default class extends Endpoint { // eslint- if (user != null) { return { type: 'User', - object: await this.userEntityService.pack(user, me, { detail: true }), + object: await this.userEntityService.pack(user, me, { schema: 'UserDetailedNotMe' }), }; } else if (note != null) { try { diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index ffddda090b..eeb580cead 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -112,7 +112,7 @@ export default class extends Endpoint { // eslint- return { accessToken: accessToken.token, user: await this.userEntityService.pack(session.userId, null, { - detail: true, + schema: 'UserDetailedNotMe', }), }; }); diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 3c7d7ac8cd..1dc4563180 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -102,7 +102,7 @@ export default class extends Endpoint { // eslint- await this.userBlockingService.block(blocker, blockee); return await this.userEntityService.pack(blockee.id, blocker, { - detail: true, + schema: 'UserDetailedNotMe', }); }); } diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 0ce334d559..a6e6bcb5b3 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -103,7 +103,7 @@ export default class extends Endpoint { // eslint- await this.userBlockingService.unblock(blocker, blockee); return await this.userEntityService.pack(blockee.id, blocker, { - detail: true, + schema: 'UserDetailedNotMe', }); }); } diff --git a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts index 9c057760ca..731bcc5b65 100644 --- a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts +++ b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts @@ -61,7 +61,7 @@ export default class extends Endpoint { // eslint- relations: ['user'], }); - const users = await this.userEntityService.packMany(records.map(r => r.user!), null, { detail: false }); + const users = await this.userEntityService.packMany(records.map(r => r.user!), null); return records.map(r => ({ id: r.id, diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index d97171865a..df8b66ab44 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -54,7 +54,7 @@ export default class extends Endpoint { // eslint- .limit(ps.limit) .getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailedNotMe' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 8302d2380f..5071dd22b7 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -76,7 +76,7 @@ export default class extends Endpoint { // eslint- const users = await query.limit(ps.limit).getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index c24e049180..c613794589 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -71,8 +71,8 @@ export default class extends Endpoint { // eslint- userProfile.loggedInDates = [...userProfile.loggedInDates, today]; } - return await this.userEntityService.pack(userProfile.user!, userProfile.user!, { - detail: true, + return await this.userEntityService.pack(userProfile.user!, userProfile.user!, { + schema: 'MeDetailed', includeSecrets: isSecure, userProfile, }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 9f8e2894b8..7aaf3982d1 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -64,7 +64,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 4161553d28..0494af2767 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -112,7 +112,7 @@ export default class extends Endpoint { // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index 2ed701014d..b68f23bf8a 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -74,7 +74,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 21e848fb5c..159a311d37 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -99,7 +99,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 8dd880c9fa..8503c72a4a 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -77,7 +77,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts index 7056ec5a58..e491cf59e1 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts @@ -69,7 +69,7 @@ export default class extends Endpoint { // eslint- // Publish meUpdated event this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, })); diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index c89cdfa3a4..71182cc29a 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -66,8 +66,8 @@ export default class extends Endpoint { // eslint- throw err; }); - return await this.userEntityService.pack(me.id, me, { - detail: true, + return await this.userEntityService.pack(me.id, me, { + schema: 'MeDetailed', }); }); } diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index b59c0e954f..1e5f66f4a8 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -51,8 +51,8 @@ export default class extends Endpoint { // eslint- throw err; }); - return await this.userEntityService.pack(me.id, me, { - detail: true, + return await this.userEntityService.pack(me.id, me, { + schema: 'MeDetailed', }); }); } diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 090b07be3c..58809871c7 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -44,7 +44,7 @@ export const meta = { res: { type: 'object', - ref: 'UserDetailed', + ref: 'MeDetailed', }, } as const; @@ -107,7 +107,7 @@ export default class extends Endpoint { // eslint- }); const iObj = await this.userEntityService.pack(me.id, me, { - detail: true, + schema: 'MeDetailed', includeSecrets: true, }); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index bfb3fffdc6..b0b9b09e31 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -478,8 +478,8 @@ export default class extends Endpoint { // eslint- verifiedLinks: [], }); - const iObj = await this.userEntityService.pack(user.id, user, { - detail: true, + const iObj = await this.userEntityService.pack(user.id, user, { + schema: 'MeDetailed', includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 8367536ad9..cb52bf6b51 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -314,6 +314,11 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + policies: { + type: 'object', + optional: false, nullable: false, + ref: 'RolePolicies', + }, }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 0a68516586..49fcaf061b 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -55,7 +55,7 @@ export default class extends Endpoint { // eslint- var: ps.var, userId: me.id, user: await this.userEntityService.pack(me.id, { id: page.userId }, { - detail: true, + schema: 'UserDetailed', }), }); }); diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 390042c815..415633e8b6 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -52,7 +52,7 @@ export default class extends Endpoint { // eslint- host: acct.host ?? IsNull(), }))); - return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { detail: true }); + return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/retention.ts b/packages/backend/src/server/api/endpoints/retention.ts index dac6d65407..2631693139 100644 --- a/packages/backend/src/server/api/endpoints/retention.ts +++ b/packages/backend/src/server/api/endpoints/retention.ts @@ -14,6 +14,32 @@ export const meta = { requireCredential: false, res: { + type: 'array', + items: { + type: 'object', + properties: { + createdAt: { + type: 'string', + format: 'date-time', + }, + users: { + type: 'number', + }, + data: { + type: 'object', + additionalProperties: { + anyOf: [{ + type: 'number', + }], + }, + }, + }, + required: [ + 'createdAt', + 'users', + 'data', + ], + }, }, allowGet: true, diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index d304d075b2..2e43af5c52 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -33,11 +33,11 @@ export const meta = { properties: { id: { type: 'string', - format: 'misskey:id' + format: 'misskey:id', }, user: { type: 'object', - ref: 'User' + ref: 'UserDetailed', }, }, required: ['id', 'user'], @@ -94,7 +94,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(assigns.map(async assign => ({ id: assign.id, - user: await this.userEntityService.pack(assign.user!, me, { detail: true }), + user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }), }))); }); } diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 8dc5841314..b886dd4a60 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -89,7 +89,7 @@ export default class extends Endpoint { // eslint- const users = await query.getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index d6fb65cecb..6c04a06cbc 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -122,7 +122,7 @@ export default class extends Endpoint { // eslint- // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await this.userEntityService.pack(user, me, { detail: true }), + user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }), weight: repliedUsers[user] / peak, }))); diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts index 985141515e..1fd3232de6 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts @@ -46,7 +46,7 @@ export const meta = { }, user: { type: 'object', - ref: 'User', + ref: 'UserLite', }, withReplies: { type: 'boolean', diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 372ab80c4c..69a842dbfb 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -9,6 +9,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; import { DI } from '@/di-symbols.js'; +import { CacheService } from '@/core/CacheService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -34,6 +37,11 @@ export const meta = { code: 'REACTIONS_NOT_PUBLIC', id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', }, + isRemoteUser: { + message: 'Currently unavailable to display reactions of remote users. See https://github.com/misskey-dev/misskey/issues/12964', + code: 'IS_REMOTE_USER', + id: '6b95fa98-8cf9-2350-e284-f0ffdb54a805', + }, }, } as const; @@ -59,14 +67,24 @@ export default class extends Endpoint { // eslint- @Inject(DI.noteReactionsRepository) private noteReactionsRepository: NoteReactionsRepository, + private cacheService: CacheService, + private userEntityService: UserEntityService, private noteReactionEntityService: NoteReactionEntityService, private queryService: QueryService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); + const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users + if (!iAmModerator) { + const user = await this.cacheService.findUserById(ps.userId); + if (this.userEntityService.isRemoteUser(user)) { + throw new ApiError(meta.errors.isRemoteUser); + } - if ((me == null || me.id !== ps.userId) && !profile.publicReactions) { - throw new ApiError(meta.errors.reactionsNotPublic); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); + if ((me == null || me.id !== ps.userId) && !profile.publicReactions) { + throw new ApiError(meta.errors.reactionsNotPublic); + } } const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'), diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 1b30e99b15..c73495e3c7 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -76,7 +76,7 @@ export default class extends Endpoint { // eslint- const users = await query.limit(ps.limit).offset(ps.offset).getMany(); - return await this.userEntityService.packMany(users, me, { detail: true }); + return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 4bf25d9fbb..7d36f6f932 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -131,7 +131,7 @@ export default class extends Endpoint { // eslint- .getMany(); } - return await this.userEntityService.packMany(users, me, { detail: !!ps.detail }); + return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 32b5c12372..bc86136592 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -141,7 +141,7 @@ export default class extends Endpoint { // eslint- } } - return await this.userEntityService.packMany(users, me, { detail: ps.detail }); + return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' }); }); } } diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 389497301d..4cf3858494 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -116,7 +116,7 @@ export default class extends Endpoint { // eslint- } return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, { - detail: true, + schema: 'UserDetailed', }))); } else { // Lookup user @@ -146,7 +146,7 @@ export default class extends Endpoint { // eslint- } return await this.userEntityService.pack(user, me, { - detail: true, + schema: 'UserDetailed', }); } }); diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 971a6116bf..5dbd4a01e1 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -6,9 +6,9 @@ import type { Config } from '@/config.js'; import endpoints, { IEndpoint } from '../endpoints.js'; import { errors as basicErrors } from './errors.js'; -import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; +import { getSchemas, convertSchemaToOpenApiSchema } from './schemas.js'; -export function genOpenapiSpec(config: Config) { +export function genOpenapiSpec(config: Config, includeSelfRef = false) { const spec = { openapi: '3.1.0', @@ -30,7 +30,7 @@ export function genOpenapiSpec(config: Config) { paths: {} as any, components: { - schemas: schemas, + schemas: getSchemas(includeSelfRef), securitySchemes: { bearerAuth: { @@ -56,7 +56,7 @@ export function genOpenapiSpec(config: Config) { } } - const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res') : {}; + const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res', includeSelfRef) : {}; let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n'; @@ -71,7 +71,7 @@ export function genOpenapiSpec(config: Config) { } const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; - const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param') }; + const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param', false) }; if (endpoint.meta.requireFile) { schema.properties = { diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index a862a7b742..61ce913b74 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -6,10 +6,10 @@ import type { Schema } from '@/misc/json-schema.js'; import { refs } from '@/misc/json-schema.js'; -export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res') { +export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any { // optional, nullable, refはスキーマ定義に含まれないので分離しておく // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { optional, nullable, ref, ...res }: any = schema; + const { optional, nullable, ref, selfRef, ...res }: any = schema; if (schema.type === 'object' && schema.properties) { if (type === 'res') { @@ -21,20 +21,20 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're } for (const k of Object.keys(schema.properties)) { - res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type); + res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type, includeSelfRef); } } if (schema.type === 'array' && schema.items) { - res.items = convertSchemaToOpenApiSchema(schema.items, type); + res.items = convertSchemaToOpenApiSchema(schema.items, type, includeSelfRef); } for (const o of ['anyOf', 'oneOf', 'allOf'] as const) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type)); + if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type, includeSelfRef)); } - if (type === 'res' && schema.ref) { + if (type === 'res' && schema.ref && (!schema.selfRef || includeSelfRef)) { const $ref = `#/components/schemas/${schema.ref}`; if (schema.nullable || schema.optional) { res.allOf = [{ $ref }]; @@ -54,35 +54,37 @@ export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 're return res; } -export const schemas = { - Error: { - type: 'object', - properties: { - error: { - type: 'object', - description: 'An error object.', - properties: { - code: { - type: 'string', - description: 'An error code. Unique within the endpoint.', - }, - message: { - type: 'string', - description: 'An error message.', - }, - id: { - type: 'string', - format: 'uuid', - description: 'An error ID. This ID is static.', +export function getSchemas(includeSelfRef: boolean) { + return { + Error: { + type: 'object', + properties: { + error: { + type: 'object', + description: 'An error object.', + properties: { + code: { + type: 'string', + description: 'An error code. Unique within the endpoint.', + }, + message: { + type: 'string', + description: 'An error message.', + }, + id: { + type: 'string', + format: 'uuid', + description: 'An error ID. This ID is static.', + }, }, + required: ['code', 'id', 'message'], }, - required: ['code', 'id', 'message'], }, + required: ['error'], }, - required: ['error'], - }, - ...Object.fromEntries( - Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res')]), - ), -}; + ...Object.fromEntries( + Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res', includeSelfRef)]), + ), + }; +} diff --git a/packages/frontend/@types/global.d.ts b/packages/frontend/@types/global.d.ts index 7d9335cc52..936e74decf 100644 --- a/packages/frontend/@types/global.d.ts +++ b/packages/frontend/@types/global.d.ts @@ -16,3 +16,8 @@ declare const _DATA_TRANSFER_DECK_COLUMN_: string; // for dev-mode declare const _LANGS_FULL_: string[][]; + +// TagCanvas +interface Window { + TagCanvas: any; +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index f0bb1bb769..707c15f5c7 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -20,7 +20,7 @@ "@discordapp/twemoji": "15.0.2", "@github/webauthn-json": "2.1.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@misskey-dev/browser-image-resizer": "2.2.1-misskey.10", + "@misskey-dev/browser-image-resizer": "2024.1.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", @@ -28,8 +28,8 @@ "@syuilo/aiscript": "0.17.0", "@phosphor-icons/web": "^2.0.3", "@twemoji/parser": "15.0.0", - "@vitejs/plugin-vue": "5.0.2", - "@vue/compiler-sfc": "3.4.3", + "@vitejs/plugin-vue": "5.0.3", + "@vue/compiler-sfc": "3.4.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6", "astring": "1.8.6", "broadcast-channel": "7.0.0", @@ -40,7 +40,7 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "10.3.1", + "chromatic": "10.6.1", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "2.30.0", @@ -61,10 +61,10 @@ "rollup": "4.9.6", "sanitize-html": "2.11.0", "sass": "1.70.0", - "shiki": "0.14.7", + "shiki": "1.0.0-beta.3", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", - "three": "0.160.0", + "three": "0.160.1", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tsc-alias": "1.8.8", @@ -77,8 +77,8 @@ "vuedraggable": "next" }, "devDependencies": { - "@misskey-dev/eslint-plugin": "^1.0.0", - "@misskey-dev/summaly": "^5.0.3", + "@misskey-dev/eslint-plugin": "1.0.0", + "@misskey-dev/summaly": "5.0.3", "@storybook/addon-actions": "7.6.10", "@storybook/addon-essentials": "7.6.10", "@storybook/addon-interactions": "7.6.10", @@ -102,12 +102,12 @@ "@types/estree": "1.0.5", "@types/matter-js": "0.19.6", "@types/micromatch": "4.0.6", - "@types/node": "20.11.5", + "@types/node": "20.11.10", "@types/punycode": "2.1.3", "@types/sanitize-html": "2.9.5", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", - "@types/uuid": "9.0.7", + "@types/uuid": "9.0.8", "@types/ws": "8.5.10", "@typescript-eslint/eslint-plugin": "6.18.1", "@typescript-eslint/parser": "6.18.1", @@ -135,7 +135,8 @@ "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", - "vue-eslint-parser": "9.4.0", + "vue-component-type-helpers": "^1.8.27", + "vue-eslint-parser": "9.4.2", "vue-tsc": "1.8.27" } } diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 2d1d0b8011..f6a3e40305 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -22,7 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { miLocalStorage } from '@/local-storage.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; -import { setupRouter } from '@/global/router/definition.js'; +import { setupRouter } from '@/router/definition.js'; export async function common(createVue: () => App) { console.info(`Sharkey v${version}`); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 8f74e927a4..3d72c04b3d 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -19,7 +19,7 @@ import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; -import { mainRouter } from '@/global/router/main.js'; +import { mainRouter } from '@/router/main.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( @@ -79,13 +79,18 @@ export async function mainBoot() { // ▼南半球 if (month === 7 || month === 8) { const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; - new SnowfallEffect().render(); + new SnowfallEffect({}).render(); } } else { // ▼北半球 if (month === 12 || month === 1) { const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; - new SnowfallEffect().render(); + new SnowfallEffect({}).render(); + } else if (month === 3 || month === 4) { + const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; + new SakuraEffect({ + sakura: true, + }).render(); } } } diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue index 6819630b74..2dfb49d450 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.vue +++ b/packages/frontend/src/components/MkAbuseReportWindow.vue @@ -39,7 +39,7 @@ import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ - user: Misskey.entities.User; + user: Misskey.entities.UserDetailed; initialComment?: string; }>(); diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index 9a7078c2f9..08256de5e6 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only - {{ c.text }} - + {{ c.text }} + {{ c.text }}

{{ button.text }} @@ -20,19 +20,19 @@ SPDX-License-Identifier: AGPL-3.0-only - + - + - + - + @@ -42,8 +42,8 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
@@ -68,7 +68,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkSelect from '@/components/MkSelect.vue'; -import { AsUiComponent } from '@/scripts/aiscript/ui.js'; +import { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js'; import MkFolder from '@/components/MkFolder.vue'; import MkPostForm from '@/components/MkPostForm.vue'; @@ -85,20 +85,32 @@ const props = withDefaults(defineProps<{ const c = props.component; function g(id) { - return props.components.find(x => x.value.id === id).value; + const v = props.components.find(x => x.value.id === id)?.value; + if (v) return v; + + return { + id: 'dummy', + type: 'root', + children: [], + } as AsUiRoot; } -const valueForSwitch = ref(c.default ?? false); +const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false); function onSwitchUpdate(v) { valueForSwitch.value = v; - if (c.onChange) c.onChange(v); + if ('onChange' in c && c.onChange) { + c.onChange(v as never); + } } function openPostForm() { + const form = (c as AsUiPostFormButton).form; + if (!form) return; + os.post({ - initialText: c.form.text, - initialCw: c.form.cw, + initialText: form.text, + initialCw: form.cw, instant: true, }); } diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 976ba190ff..0b10605307 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -431,7 +431,7 @@ function applySelect() { function chooseUser() { props.close(); - os.selectUser().then(user => { + os.selectUser({ includeSelf: true }).then(user => { complete('user', user); props.textarea.focus(); }); diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index da13f8a067..c0ef8a89a0 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index f60c721eae..7aa08cf51f 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only