diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a78d91900..0583a6696 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers-contrib/features/pnpm:2": { - "version": "8.8.0" + "version": "8.9.2" }, "ghcr.io/devcontainers/features/node:1": { "version": "20.5.1" diff --git a/CHANGELOG.md b/CHANGELOG.md index 50334560c..d0db7ed65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,24 @@ ## 2023.x.x (unreleased) ### General -- +- Feat: アンテナでローカルの投稿のみ収集できるようになりました +- Feat: サーバーサイレンス機能が追加されました +- Enhance: 新規にフォローした人の返信をデフォルトでTLに追加できるオプションを追加 +- Enhance: HTLとLTLを2023.10.0アップデート以前まで遡れるように +- Enhance: フォロー/フォロー解除したときに過去分のHTLにも含まれる投稿が反映されるように +- Enhance: ローカリゼーションの更新 +- Enhance: 依存関係の更新 ### Client - Enhance: TLの返信表示オプションを記憶するように ### Server -- +- Enhance: タイムライン取得時のパフォーマンスを向上 +- Enhance: ストリーミングAPIのパフォーマンスを向上 +- Fix: users/notesでDBから参照した際にチャンネル投稿のみ取得される問題を修正 +- Fix: コントロールパネルの設定項目が正しく保存できない問題を修正 +- Change: ユーザーのisCatがtrueでも、サーバーではnyaizeが行われなくなりました + - isCatな場合、クライアントでnyaize処理を行うことを推奨します ## 2023.10.1 ### General @@ -38,6 +49,7 @@ ### NOTE - 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました - アップデートを行うと、タイムラインが一時的にリセットされます + - アンテナ内のノートも含む - ソフトミュート設定はクライアントではなくサーバー側に保存されるようになったため、アップデートを行うとソフトミュートの設定がリセットされます ### Changes diff --git a/locales/de-DE.yml b/locales/de-DE.yml index e4725ca72..0fc0e9d61 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -195,6 +195,7 @@ perHour: "Pro Stunde" perDay: "Pro Tag" stopActivityDelivery: "Senden von Aktivitäten einstellen" blockThisInstance: "Diese Instanz blockieren" +silenceThisInstance: "Instanz stummschalten" operations: "Aktionen" software: "Software" version: "Version" @@ -214,6 +215,8 @@ clearCachedFiles: "Cache leeren" clearCachedFilesConfirm: "Sollen alle im Cache gespeicherten Dateien von anderen Instanzen wirklich gelöscht werden?" blockedInstances: "Blockierte Instanzen" blockedInstancesDescription: "Gib die Hostnamen der Instanzen, welche blockiert werden sollen, durch Zeilenumbrüche getrennt an. Blockierte Instanzen können mit dieser instanz nicht mehr kommunizieren." +silencedInstances: "Stummgeschaltete Instanzen" +silencedInstancesDescription: "Gib die Hostnamen der Instanzen, welche stummgeschaltet werden sollen, durch Zeilenumbrüche getrennt an. Alle Konten dieser Instanzen werden als stummgeschaltet behandelt, können nur noch Follow-Anfragen stellen und wenn nicht gefolgt keine lokalen Konten erwähnen. Blockierte Instanzen sind davon nicht betroffen." muteAndBlock: "Stummschaltungen und Blockierungen" mutedUsers: "Stummgeschaltete Benutzer" blockedUsers: "Blockierte Benutzer" @@ -531,6 +534,7 @@ serverLogs: "Serverprotokolle" deleteAll: "Alle löschen" showFixedPostForm: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen" showFixedPostFormInChannel: "Bereich zum Schreiben neuer Notizen am Anfang der Chronik anzeigen (Kanäle)" +withRepliesByDefaultForNewlyFollowed: "Standardmäßig Antworten von neu gefolgten Benutzern in der Chronik anzeigen" newNoteRecived: "Es gibt neue Notizen" sounds: "Töne" sound: "Töne" @@ -794,7 +798,7 @@ active: "Aktiv" offline: "Offline" notRecommended: "Nicht empfohlen" botProtection: "Schutz vor Bots" -instanceBlocking: "Blockierte Instanzen" +instanceBlocking: "Blockierte/Stummgeschaltete Instanzen" selectAccount: "Benutzerkonto auswählen" switchAccount: "Konto wechseln" enabled: "Aktiviert" @@ -1921,6 +1925,7 @@ _exportOrImport: userLists: "Listen" excludeMutingUsers: "Stummgeschaltete Benutzer aussortieren" excludeInactiveUsers: "Inaktive Benutzer aussortieren" + withReplies: "Antworten von importierten Benutzern in der Chronik beinhalten" _charts: federation: "Föderation" apRequest: "Anfragen" diff --git a/locales/en-US.yml b/locales/en-US.yml index 66825eaa7..f131b18cb 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -195,6 +195,7 @@ perHour: "Per Hour" perDay: "Per Day" stopActivityDelivery: "Stop sending activities" blockThisInstance: "Block this instance" +silenceThisInstance: "Silence this instance" operations: "Operations" software: "Software" version: "Version" @@ -213,7 +214,9 @@ clearQueueConfirmText: "Any undelivered notes remaining in the queue will not be clearCachedFiles: "Clear cache" clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?" blockedInstances: "Blocked Instances" -blockedInstancesDescription: "List the hostnames of the instances that you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance." +blockedInstancesDescription: "List the hostnames of the instances you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance." +silencedInstances: "Silenced instances" +silencedInstancesDescription: "List the hostnames of the instances that you want to silence. All accounts of the listed instances will be treated as silenced, can only make follow requests, and cannot mention local accounts if not followed. This will not affect blocked instances." muteAndBlock: "Mutes and Blocks" mutedUsers: "Muted users" blockedUsers: "Blocked users" @@ -531,6 +534,7 @@ serverLogs: "Server logs" deleteAll: "Delete all" showFixedPostForm: "Display the posting form at the top of the timeline" showFixedPostFormInChannel: "Display the posting form at the top of the timeline (Channels)" +withRepliesByDefaultForNewlyFollowed: "Include replies by newly followed users in the timeline by default" newNoteRecived: "There are new notes" sounds: "Sounds" sound: "Sounds" @@ -794,7 +798,7 @@ active: "Active" offline: "Offline" notRecommended: "Not recommended" botProtection: "Bot Protection" -instanceBlocking: "Blocked Instances" +instanceBlocking: "Blocked/Silenced Instances" selectAccount: "Select account" switchAccount: "Switch account" enabled: "Enabled" @@ -1126,8 +1130,8 @@ edited: "Edited" notificationRecieveConfig: "Notification Settings" mutualFollow: "Mutual follow" fileAttachedOnly: "Only notes with files" -showRepliesToOthersInTimeline: "Show replies to others in TL" -hideRepliesToOthersInTimeline: "Hide replies to others from TL" +showRepliesToOthersInTimeline: "Show replies to others in timeline" +hideRepliesToOthersInTimeline: "Hide replies to others from timeline" externalServices: "External Services" impressum: "Impressum" impressumUrl: "Impressum URL" @@ -1738,7 +1742,7 @@ _2fa: step2Click: "Clicking on this QR code will allow you to register 2FA to your security key or phone authenticator app." step2Uri: "Enter the following URI if you are using a desktop program" step3Title: "Enter an authentication code" - step3: "Enter the token provided by your app to finish setup." + step3: "Enter the authentication code (token) provided by your app to finish setup." setupCompleted: "Setup complete" step4: "From now on, any future login attempts will ask for such a login token." securityKeyNotSupported: "Your browser does not support security keys." @@ -1921,6 +1925,7 @@ _exportOrImport: userLists: "User lists" excludeMutingUsers: "Exclude muted users" excludeInactiveUsers: "Exclude inactive users" + withReplies: "Include replies from imported users in the timeline" _charts: federation: "Federation" apRequest: "Requests" @@ -2073,7 +2078,7 @@ _deck: introduction: "Create the perfect interface for you by arranging columns freely!" introduction2: "Click on the + on the right of the screen to add new colums whenever you want." widgetsIntroduction: "Please select \"Edit widgets\" in the column menu and add a widget." - useSimpleUiForNonRootPages: "Use simplified UI to navigated pages" + useSimpleUiForNonRootPages: "Use simple UI for navigated pages" usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled" flexible: "Auto-adjust width" _columns: diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 3a01f40df..9e2acc9d2 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -195,6 +195,7 @@ perHour: "por hora" perDay: "por día" stopActivityDelivery: "Dejar de enviar actividades" blockThisInstance: "Bloquear instancia" +silenceThisInstance: "Silenciar esta instancia" operations: "Operaciones" software: "Software" version: "Versión" @@ -214,6 +215,8 @@ clearCachedFiles: "Limpiar caché" clearCachedFilesConfirm: "¿Desea borrar todos los archivos remotos cacheados?" blockedInstances: "Instancias bloqueadas" blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia." +silencedInstances: "Instancias silenciadas" +silencedInstancesDescription: "Listar los hostname de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas." muteAndBlock: "Silenciar y bloquear" mutedUsers: "Usuarios silenciados" blockedUsers: "Usuarios bloqueados" @@ -531,6 +534,7 @@ serverLogs: "Registros del servidor" deleteAll: "Eliminar todos" showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo" showFixedPostFormInChannel: "Mostrar el formulario de publicación por encima de la cronología (Canales)" +withRepliesByDefaultForNewlyFollowed: "Incluir por defecto respuestas de usuarios recién seguidos en la línea de tiempo" newNoteRecived: "Tienes una nota nueva" sounds: "Sonidos" sound: "Sonidos" @@ -1121,6 +1125,20 @@ unnotifyNotes: "Dejar de notificar nuevas notas" authentication: "Autenticación" authenticationRequiredToContinue: "Por favor, autentifícate para continuar" dateAndTime: "Fecha y hora" +showRenotes: "Mostrar renotas" +edited: "Editado" +notificationRecieveConfig: "Ajustes de Notificaciones" +mutualFollow: "Os seguís mutuamente" +fileAttachedOnly: "Solo notas con archivos" +showRepliesToOthersInTimeline: "Mostrar respuestas a otros en la línea de tiempo" +hideRepliesToOthersInTimeline: "Ocultar respuestas a otros en la línea de tiempo" +externalServices: "Servicios Externos" +impressum: "Impressum" +impressumUrl: "Impressum URL" +impressumDescription: "En algunos países, como Alemania, la inclusión del operador de datos (el Impressum) es requerido legalmente para sitios web comerciales." +privacyPolicy: "Política de Privacidad" +privacyPolicyUrl: "URL de la Política de Privacidad" +tosAndPrivacyPolicy: "Condiciones de Uso y Política de Privacidad" _announcement: forExistingUsers: "Solo para usuarios registrados" forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán." @@ -1470,6 +1488,7 @@ _role: descriptionOfRateLimitFactor: "Límites más bajos son menos restrictivos, más altos menos restrictivos" canHideAds: "Puede ocultar anuncios" canSearchNotes: "Uso de la búsqueda de notas" + canUseTranslator: "Uso de traductor" _condition: isLocal: "Usuario local" isRemote: "Usuario remoto" @@ -1518,6 +1537,10 @@ _ad: reduceFrequencyOfThisAd: "Mostrar menos este anuncio." hide: "No mostrar" timezoneinfo: "El día de la semana está determidado por la zona horaria del servidor." + adsSettings: "Ajustes de anuncios" + notesPerOneAd: "Intervalo de actualización de anuncios en tiempo real (Notas por cada anuncio)" + setZeroToDisable: "Establece este valor a 0 para deshabilitar la actualización de anuncios en tiempo real" + adsTooClose: "El intervalo de anuncios actual puede empeorar la experiencia del usuario por ser demasiado bajo." _forgotPassword: enterEmail: "Ingrese el correo usado para registrar la cuenta. Se enviará un link para resetear la contraseña." ifNoEmail: "Si no utilizó un correo para crear la cuenta, contáctese con el administrador." @@ -1902,6 +1925,7 @@ _exportOrImport: userLists: "Listas" excludeMutingUsers: "Excluir usuarios silenciados" excludeInactiveUsers: "Excluir usuarios inactivos" + withReplies: "Incluir respuestas de los usuarios importados en la línea de tiempo" _charts: federation: "Federación" apRequest: "Pedidos" @@ -2119,3 +2143,14 @@ _moderationLogTypes: unmarkSensitiveDriveFile: "Archivo marcado como no sensible" resolveAbuseReport: "Reporte resuelto" createInvitation: "Generar invitación" + createAd: "Anuncio creado" + deleteAd: "Anuncio eliminado" + updateAd: "Anuncio actualizado" +_fileViewer: + title: "Detalles del archivo" + type: "Tipo de archivo" + size: "Tamaño del archivo" + url: "URL" + uploadedAt: "Subido el" + attachedNotes: "Notas adjuntas" + thisPageCanBeSeenFromTheAuthor: "Esta página solo puede ser vista por el autor." diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 0ad1247ff..b2440c2b6 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -184,7 +184,7 @@ selectUser: "Sélectionner un·e utilisateur·rice" recipient: "Destinataire" annotation: "Commentaires" federation: "Fédération" -instances: "Instance" +instances: "Instances" registeredAt: "Premier contact le" latestRequestReceivedAt: "Dernière requête reçue" latestStatus: "Dernier statut" @@ -194,6 +194,7 @@ perHour: "par heure" perDay: "par jour" stopActivityDelivery: "Arrêter l’envoi de l’activité" blockThisInstance: "Bloquer cette instance" +silenceThisInstance: "Mettre cette instance en sourdine" operations: "Opérations" software: "Logiciel" version: "Version" @@ -213,6 +214,8 @@ clearCachedFiles: "Vider le cache" clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers distants ?" blockedInstances: "Instances bloquées" blockedInstancesDescription: "Listez les instances que vous désirez bloquer, une par ligne. Ces instances ne seront plus en capacité d'interagir avec votre instance." +silencedInstances: "Instances mises en sourdine" +silencedInstancesDescription: "Énumérer les noms d'hôte des instances à mettre en sourdine. Tous les comptes des instances énumérées seront traités comme mis en sourdine, ne peuvent faire que des demandes de suivi et ne peuvent pas mentionner les comptes locaux s'ils ne sont pas suivis. Cela n'affectera pas les instances bloquées." muteAndBlock: "Masqué·e·s / Bloqué·e·s" mutedUsers: "Utilisateur·rice·s en sourdine" blockedUsers: "Utilisateur·rice·s bloqué·e·s" @@ -927,6 +930,7 @@ remoteOnly: "Distant uniquement" failedToUpload: "Échec du transfert" cannotUploadBecauseInappropriate: "Impossible de télécharger le document car il a été déterminé qu'il pouvait contenir un contenu inapproprié." cannotUploadBecauseNoFreeSpace: "Impossible de télécharger en raison d'un manque d'espace libre sur le disque.\n" +cannotUploadBecauseExceedsFileSizeLimit: "Ce fichier ne peut pas être téléchargé parce qu'il dépasse la taille maximale." beta: "Bêta" enableAutoSensitive: "Détermination automatique de NSFW" enableAutoSensitiveDescription: "S'il est disponible, le drapeau NSFW est automatiquement défini sur le média en utilisant l'apprentissage automatique. Même si cette fonction est désactivée, elle peut être réglée automatiquement dans certains cas." @@ -948,12 +952,14 @@ caption: "Libellé" loggedInAsBot: "Connecté actuellement en tant que bot" tools: "Outils" cannotLoad: "Chargement impossible" +numberOfProfileView: "Nombre de vues du profil" like: "J'aime" unlike: "Ne plus aimer" numberOfLikes: "Favoris" show: "Affichage" neverShow: "Ne plus afficher" remindMeLater: "Peut-être plus tard" +didYouLikeMisskey: "Avez-vous aimé Misskey ?" roles: "Rôles" role: "Rôles" noRole: "Aucun rôle" @@ -963,9 +969,13 @@ assign: "Attribuer" unassign: "Retirer" color: "Couleur" manageCustomEmojis: "Gestion des émojis personnalisés" +youCannotCreateAnymore: "Vous avez atteint la limite de création." +cannotPerformTemporary: "Temporairement indisponible" +invalidParamError: "Paramètres invalides" preset: "Préréglage" selectFromPresets: "Sélectionner à partir des préréglages" achievements: "Accomplissements" +gotInvalidResponseError: "Réponse du serveur invalide" thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes." thisPostMayBeAnnoyingHome: "Publier vers le fil principal" thisPostMayBeAnnoyingCancel: "Annuler" @@ -980,6 +990,8 @@ sensitiveWords: "Mots sensibles" notesSearchNotAvailable: "La recherche de notes n'est pas disponible." license: "Licence" myClips: "Mes clips" +showClipButtonInNoteFooter: "Ajouter « Clip » au menu d'action de la note" +noteIdOrUrl: "Identifiant de la note ou URL" video: "Vidéo" videos: "Vidéos" dataSaver: "Économiseur de données" @@ -987,6 +999,7 @@ accountMigration: "Migration de compte" accountMoved: "Cet·te utilisateur·rice a migré son compte vers :" accountMovedShort: "Ce compte a migré" operationForbidden: "Opération non autorisée" +forceShowAds: "Toujours afficher les publicités" addMemo: "Ajouter un mémo" reactionsList: "Réactions" renotesList: "Liste de renotes" @@ -995,9 +1008,12 @@ leftTop: "En haut à gauche" rightTop: "En haut à droite" leftBottom: "En bas à gauche" rightBottom: "En bas à droite" +stackAxis: "Direction d'empilement" vertical: "Vertical" horizontal: "Latéral" +position: "Position" serverRules: "Règles du serveur" +preservedUsernames: "Nom d'utilisateur·rice réservé" archive: "Archive" displayOfNote: "Affichage de la note" youFollowing: "Abonné·e" @@ -1531,7 +1547,7 @@ _visibility: _postForm: replyPlaceholder: "Répondre à cette note ..." quotePlaceholder: "Citez cette note ..." - channelPlaceholder: "Publier vers le canal" + channelPlaceholder: "Publier au canal…" _placeholders: a: "Quoi de neuf ?" b: "Il s'est passé quelque chose ?" diff --git a/locales/index.d.ts b/locales/index.d.ts index 2494c1709..363032eaa 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -198,6 +198,7 @@ export interface Locale { "perDay": string; "stopActivityDelivery": string; "blockThisInstance": string; + "silenceThisInstance": string; "operations": string; "software": string; "version": string; @@ -217,6 +218,8 @@ export interface Locale { "clearCachedFilesConfirm": string; "blockedInstances": string; "blockedInstancesDescription": string; + "silencedInstances": string; + "silencedInstancesDescription": string; "muteAndBlock": string; "mutedUsers": string; "blockedUsers": string; @@ -534,6 +537,7 @@ export interface Locale { "deleteAll": string; "showFixedPostForm": string; "showFixedPostFormInChannel": string; + "withRepliesByDefaultForNewlyFollowed": string; "newNoteRecived": string; "sounds": string; "sound": string; @@ -2051,6 +2055,7 @@ export interface Locale { "userLists": string; "excludeMutingUsers": string; "excludeInactiveUsers": string; + "withReplies": string; }; "_charts": { "federation": string; diff --git a/locales/it-IT.yml b/locales/it-IT.yml index fa8670d11..15b2c7cbd 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -116,8 +116,8 @@ inChannelQuote: "Cita nel canale" pinnedNote: "Nota in primo piano" pinned: "Fissa sul profilo" you: "Tu" -clickToShow: "Clicca per visualizzare" -sensitive: "Esplicito" +clickToShow: "Contenuto occultato, cliccare solo se si intende vedere" +sensitive: "Allegato esplicito" add: "Aggiungi" reaction: "Reazioni" reactions: "Reazioni" @@ -195,6 +195,7 @@ perHour: "orario" perDay: "giornaliero" stopActivityDelivery: "Interrompi la distribuzione di attività" blockThisInstance: "Blocca questa istanza" +silenceThisInstance: "Silenzia l'istanza" operations: "Operazioni" software: "Software" version: "Versione" @@ -214,6 +215,8 @@ clearCachedFiles: "Svuota cache" clearCachedFilesConfirm: "Vuoi davvero svuotare la cache da tutti i file remoti?" blockedInstances: "Istanze bloccate" blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza." +silencedInstances: "Istanze silenziate" +silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate." muteAndBlock: "Silenziati / Bloccati" mutedUsers: "Profili silenziati" blockedUsers: "Profili bloccati" @@ -278,7 +281,7 @@ agreeTo: "Sono d'accordo con {0}" agree: "Accetto" agreeBelow: "Accetto quanto riportato sotto" basicNotesBeforeCreateAccount: "Note importanti" -termsOfService: "Informativa ai sensi degli artt. 13 e 14 del Regolamento UE 2016/679 per la protezione dei dati personali (GDPR)" +termsOfService: "Condizioni d'uso del servizio" start: "Inizia!" home: "Home" remoteUserCaution: "Le informazioni potrebbero essere incomplete poiché questo profilo remoto potrebbe non essere completamente federato." @@ -1132,9 +1135,9 @@ externalServices: "Servizi esterni" impressum: "Dichiarazione di proprietà" impressumUrl: "URL della dichiarazione di proprietà" impressumDescription: "La dichiarazione di proprietà, è obbligatoria in alcuni paesi come la Germania (Impressum)." -privacyPolicy: "Informativa sulla privacy" +privacyPolicy: "Informativa ai sensi degli artt. 13 e 14 del Regolamento UE 2016/679 per la protezione dei dati personali (GDPR)" privacyPolicyUrl: "URL della informativa privacy" -tosAndPrivacyPolicy: "Condizioni d'uso e informativa sulla privacy" +tosAndPrivacyPolicy: "Condizioni d'uso e informativa privacy" _announcement: forExistingUsers: "Solo ai profili attuali" forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio." @@ -1879,9 +1882,9 @@ _poll: remainingSeconds: "Rimangono {s} secondi" _visibility: public: "Pubblica" - publicDescription: "Visibile per tutti sul Fediverso" + publicDescription: "Visibilità pubblica" home: "Home" - homeDescription: "Visibile solo sulla timeline locale" + homeDescription: "Visibile solo nella Home" followers: "Follower" followersDescription: "Visibile solo ai tuoi follower" specified: "Nota diretta" @@ -2141,3 +2144,11 @@ _moderationLogTypes: createAd: "Banner creato" deleteAd: "Banner eliminato" updateAd: "Banner aggiornato" +_fileViewer: + title: "Dettagli del file" + type: "Tipo di file" + size: "Dimensioni file" + url: "URL" + uploadedAt: "Caricato il" + attachedNotes: "Note a cui è allegato" + thisPageCanBeSeenFromTheAuthor: "Questa pagina può essere vista solo da chi ha caricato il file." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9adc4381a..f1b57f8bd 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -195,6 +195,7 @@ perHour: "1時間ごと" perDay: "1日ごと" stopActivityDelivery: "アクティビティの配送を停止" blockThisInstance: "このサーバーをブロック" +silenceThisInstance: "サーバーをサイレンス" operations: "操作" software: "ソフトウェア" version: "バージョン" @@ -213,7 +214,9 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。 clearCachedFiles: "キャッシュをクリア" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" blockedInstances: "ブロックしたサーバー" -blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このサーバーとやり取りできなくなります。サブドメインもブロックされます。" +blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。" +silencedInstances: "サイレンスしたサーバー" +silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。" muteAndBlock: "ミュートとブロック" mutedUsers: "ミュートしたユーザー" blockedUsers: "ブロックしたユーザー" @@ -531,6 +534,7 @@ serverLogs: "サーバーログ" deleteAll: "全て削除" showFixedPostForm: "タイムライン上部に投稿フォームを表示する" showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)" +withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする" newNoteRecived: "新しいノートがあります" sounds: "サウンド" sound: "サウンド" @@ -589,7 +593,7 @@ poll: "アンケート" useCw: "内容を隠す" enablePlayer: "プレイヤーを開く" disablePlayer: "プレイヤーを閉じる" -expandTweet: "ツイートを展開する" +expandTweet: "ポストを展開する" themeEditor: "テーマエディター" description: "説明" describeFile: "キャプションを付ける" @@ -794,7 +798,7 @@ active: "アクティブ" offline: "オフライン" notRecommended: "非推奨" botProtection: "Botプロテクション" -instanceBlocking: "サーバーブロック" +instanceBlocking: "サーバーブロック・サイレンス" selectAccount: "アカウントを選択" switchAccount: "アカウントを切り替え" enabled: "有効" @@ -1966,6 +1970,7 @@ _exportOrImport: userLists: "リスト" excludeMutingUsers: "ミュートしているユーザーを除外" excludeInactiveUsers: "使われていないアカウントを除外" + withReplies: "インポートした人による返信をTLに含むようにする" _charts: federation: "連合" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 1c655f588..a706d3990 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1117,15 +1117,25 @@ keepScreenOn: "เปิดหน้าจอไว้" notifyNotes: "แจ้งเตือนเกี่ยวกับโพสต์ใหม่" unnotifyNotes: "หยุดการแจ้งเตือนเกี่ยวกับโน้ตใหม่" authentication: "การตรวจสอบสิทธิ์" +authenticationRequiredToContinue: "กรุณาตรวจสอบการรับรองความถูกต้องเพื่อดำเนินการต่อ" dateAndTime: "เวลาประทับ" showRenotes: "แสดงรีโน้ต" edited: "แก้ไขแล้ว" notificationRecieveConfig: "การตั้งค่าการแจ้งเตือน" mutualFollow: "ติดตามซึ่งกันและกัน" fileAttachedOnly: "เฉพาะโน้ตที่มีไฟล์เท่านั้น" +showRepliesToOthersInTimeline: "แสดงการตอบกลับไปยังอื่นๆในไทม์ไลน์" +hideRepliesToOthersInTimeline: "ซ่อนการตอบกลับไปยังอื่นๆจากไทม์ไลน์" +externalServices: "บริการภายนอก" +impressum: "อิมเพรสชั่น" +impressumUrl: "URL อิมเพรสชั่น" +privacyPolicy: "นโยบายความเป็นส่วนตัว" +privacyPolicyUrl: "URL นโยบายความเป็นส่วนตัว" +tosAndPrivacyPolicy: "เงื่อนไขในการให้บริการและนโยบายความเป็นส่วนตัว" _announcement: forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น" forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน" + needConfirmationToRead: "จำเป็นต้องยืนยันเพื่อทำเครื่องหมายบอกว่าอ่านแล้ว" needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\"" end: "ประกาศเก็บถาวร" tooManyActiveAnnouncementDescription: "การมีประกาศที่ใช้งานมากเกินไปนั้นอาจจะทำให้ประสบการณ์ของผู้ใช้งานนั้นดูแย่ลง โปรดกรุณาพิจารณาการเก็บประกาศที่ล้าสมัยด้วยนะค่ะ" @@ -1150,6 +1160,8 @@ _serverRules: description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" _serverSettings: iconUrl: "ไอคอน URL" + appIconUsageExample: "E.g. เป็น PWA หรือเมื่อแสดงผลเป็นบุ๊กมาร์กหน้าจอหลักบนโทรศัพท์" + appIconResolutionMustBe: "ความละเอียดขั้นต่ำไว้คือ {resolution}." manifestJsonOverride: "manifest.json โอเวอร์ลาย" shortName: "ชื่อย่อ" _accountMigration: @@ -1515,6 +1527,8 @@ _ad: reduceFrequencyOfThisAd: "แสดงโฆษณานี้ให้น้อยลง" hide: "ไม่ต้องแสดง" timezoneinfo: "วันในสัปดาห์นี้จะถูกกำหนดจากโซนเวลาของเซิร์ฟเวอร์" + adsSettings: "ตั้งค่าการโฆษณา" + setZeroToDisable: "ตั้งค่านี้ให้เป็น 0 เพื่อปิดใช้งานโฆษณาอัปเดตแบบเรียลไทม์" _forgotPassword: enterEmail: "ป้อนที่อยู่อีเมลที่คุณเคยใช้ในการลงทะเบียนไว้ ลิงก์ที่คุณสามารถรีเซ็ตรหัสผ่านได้นั้นจะถูกส่งไปนะ" ifNoEmail: "ถ้าหากคุณไม่ได้ใช้อีเมลระหว่างการลงทะเบียน กรุณาติดต่อผู้ดูแลระบบอินสแตนซ์แทนนะ" @@ -1714,6 +1728,7 @@ _2fa: step1: "ขั้นตอนแรก ติดตั้งแอปยืนยันตัวตน (เช่น {a} หรือ {b}) บนอุปกรณ์ของคุณ" step2: "จากนั้นสแกนรหัส QR ที่แสดงบนหน้าจอนี้" step2Click: "การคลิกที่รหัส QR นี้จะช่วยให้คุณนั้นสามารถลงทะเบียน 2FA กับคีย์ความปลอดภัยหรือแอปตรวจสอบความถูกต้องของโทรศัพท์ได้" + step2Uri: "ป้อนใส่ URL ดังต่อไปนี้ถ้าหากคุณใช้โปรแกรมเดสก์ท็อป" step3Title: "ป้อนรหัสยืนยัน" step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า" setupCompleted: "ตั้งค่าสำเร็จแล้ว" @@ -1732,6 +1747,8 @@ _2fa: renewTOTPOk: "ตั้งค่าคอนฟิกใหม่" renewTOTPCancel: "ไม่เป็นไร" backupCodes: "รหัสสำรองข้อมูล" + backupCodeUsedWarning: "มีการใช้รหัสสำรองแล้ว โปรดกรุณากำหนดค่าการตรวจสอบสิทธิ์แบบสองปัจจัยโดยเร็วที่สุดถ้าหากคุณยังไม่สามารถใช้งานได้อีกต่อไป" + backupCodesExhaustedWarning: "รหัสสำรองทั้งหมดถูกใช้แล้วถ้าหากคุณยังสูญเสียการเข้าถึงแอปการตรวจสอบสิทธิ์แบบสองปัจจัยคุณจะไม่สามารถเข้าถึงบัญชีนี้ได้ กรุณากำหนดค่าการรับรองความถูกต้องด้วยการยืนยันสองชั้น" _permissions: "read:account": "ดูข้อมูลบัญชีของคุณ" "write:account": "แก้ไขข้อมูลบัญชีของคุณ" @@ -1894,6 +1911,7 @@ _exportOrImport: userLists: "รายการ" excludeMutingUsers: "ยกเว้นผู้ใช้ที่ปิดเสียง" excludeInactiveUsers: "ยกเว้นผู้ใช้ที่ไม่ได้ใช้งาน" + withReplies: "รวมการตอบกลับจากผู้ใช้ที่นำเข้าไว้ในไทม์ไลน์" _charts: federation: "สหพันธ์" apRequest: "คำขอ" @@ -2114,3 +2132,11 @@ _moderationLogTypes: createAd: "สร้างโฆษณาแล้ว" deleteAd: "ลบโฆษณาออกแล้ว" updateAd: "อัปเดตโฆษณาแล้ว" +_fileViewer: + title: "รายละเอียดไฟล์" + type: "ประเภทไฟล์" + size: "ขนาดไฟล์" + url: "URL" + uploadedAt: "วันที่เข้าร่วม" + attachedNotes: "โน้ตที่แนบมาด้วย" + thisPageCanBeSeenFromTheAuthor: "หน้าเพจนี้จะสามารถปรากฏได้โดยผู้ใช้ที่อัปโหลดไฟล์นี้เท่านั้น" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index ccdb87379..acb4dfa5e 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1125,8 +1125,8 @@ showRenotes: "顯示轉發貼文" edited: "已編輯" notificationRecieveConfig: "接受通知的設定" mutualFollow: "互相追隨" -fileAttachedOnly: "包含附件" -showRepliesToOthersInTimeline: "在時間軸上顯示給其他人的回覆" +fileAttachedOnly: "顯示包含附件的貼文" +showRepliesToOthersInTimeline: "顯示給其他人的回覆" hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆" externalServices: "外部服務" impressum: "營運者資訊" diff --git a/package.json b/package.json index a9b2b1353..a60c641e6 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2023.10.1", + "version": "2023.10.2-beta.2", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@8.8.0", + "packageManager": "pnpm@8.9.2", "workspaces": [ "packages/frontend", "packages/backend", @@ -51,10 +51,10 @@ "typescript": "5.2.2" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.7.5", - "@typescript-eslint/parser": "6.7.5", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", "cross-env": "7.0.3", - "cypress": "13.3.0", + "cypress": "13.3.1", "eslint": "8.51.0", "start-server-and-test": "2.0.1" }, diff --git a/packages/backend/migration/1697247230117-InstanceSilence.js b/packages/backend/migration/1697247230117-InstanceSilence.js new file mode 100644 index 000000000..5fdbca3b2 --- /dev/null +++ b/packages/backend/migration/1697247230117-InstanceSilence.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class InstanceSilence1697247230117 { + name = 'InstanceSilence1697247230117' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "silencedHosts" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`); + } +} diff --git a/packages/backend/migration/1697420555911-deleteCreatedAt.js b/packages/backend/migration/1697420555911-deleteCreatedAt.js new file mode 100644 index 000000000..958d61a34 --- /dev/null +++ b/packages/backend/migration/1697420555911-deleteCreatedAt.js @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class DeleteCreatedAt1697420555911 { + name = 'DeleteCreatedAt1697420555911' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_02878d441ceae15ce060b73daf"`); + await queryRunner.query(`DROP INDEX "public"."IDX_c8dfad3b72196dd1d6b5db168a"`); + await queryRunner.query(`DROP INDEX "public"."IDX_e11e649824a45d8ed01d597fd9"`); + await queryRunner.query(`DROP INDEX "public"."IDX_db2098070b2b5a523c58181f74"`); + await queryRunner.query(`DROP INDEX "public"."IDX_048a757923ed8b157e9895da53"`); + await queryRunner.query(`DROP INDEX "public"."IDX_1129c2ef687fc272df040bafaa"`); + await queryRunner.query(`DROP INDEX "public"."IDX_118ec703e596086fc4515acb39"`); + await queryRunner.query(`DROP INDEX "public"."IDX_b9a354f7941c1e779f3b33aea6"`); + await queryRunner.query(`DROP INDEX "public"."IDX_71cb7b435b7c0d4843317e7e16"`); + await queryRunner.query(`DROP INDEX "public"."IDX_11e71f2511589dcc8a4d3214f9"`); + await queryRunner.query(`DROP INDEX "public"."IDX_735a5544f9249d412255f47f95"`); + await queryRunner.query(`DROP INDEX "public"."IDX_582f8fab771a9040a12961f3e7"`); + await queryRunner.query(`DROP INDEX "public"."IDX_8f1a239bd077c8864a20c62c2c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f86d57fbca33c7a4e6897490cc"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`); + await queryRunner.query(`DROP INDEX "public"."IDX_fbb4297c927a9b85e9cefa2eb1"`); + await queryRunner.query(`DROP INDEX "public"."IDX_0fb627e1c2f753262a74f0562d"`); + await queryRunner.query(`DROP INDEX "public"."IDX_149d2e44785707548c82999b01"`); + await queryRunner.query(`ALTER TABLE "drive_folder" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "app" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "announcement_read" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "auth_session" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "blocking" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "channel_following" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "channel_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "clip" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "clip_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "gallery_post" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "gallery_like" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "moderation_log" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "renote_muting" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note_reaction" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note_thread_muting" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "page_like" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "password_reset_request" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "poll_vote" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "promo_read" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "registration_ticket" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "signin" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "sw_subscription" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_list_membership" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_note_pining" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_pending" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "role_assignment" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "flash" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "flash_like" DROP COLUMN "createdAt"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "flash_like" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "flash" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "role_assignment" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "role" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "webhook" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_pending" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_note_pining" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_list_membership" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "sw_subscription" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "signin" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "registry_item" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "registration_ticket" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "promo_read" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "poll_vote" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "password_reset_request" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "page_like" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "page" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_thread_muting" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_reaction" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "renote_muting" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "muting" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "moderation_log" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "gallery_like" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "gallery_post" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "follow_request" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "following" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "clip_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "clip" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "channel_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "channel_following" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "channel" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "blocking" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "auth_session" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "antenna" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_list" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "announcement_read" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "announcement" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "ad" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "access_token" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "app" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "drive_file" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "drive_folder" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`CREATE INDEX "IDX_149d2e44785707548c82999b01" ON "flash" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_0fb627e1c2f753262a74f0562d" ON "poll_vote" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_fbb4297c927a9b85e9cefa2eb1" ON "page" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_f86d57fbca33c7a4e6897490cc" ON "muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_8f1a239bd077c8864a20c62c2c" ON "gallery_post" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_582f8fab771a9040a12961f3e7" ON "following" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_735a5544f9249d412255f47f95" ON "channel_favorite" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_11e71f2511589dcc8a4d3214f9" ON "channel_following" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_71cb7b435b7c0d4843317e7e16" ON "channel" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_b9a354f7941c1e779f3b33aea6" ON "blocking" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_118ec703e596086fc4515acb39" ON "announcement" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_1129c2ef687fc272df040bafaa" ON "ad" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_048a757923ed8b157e9895da53" ON "app" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_db2098070b2b5a523c58181f74" ON "abuse_user_report" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_e11e649824a45d8ed01d597fd9" ON "user" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_c8dfad3b72196dd1d6b5db168a" ON "drive_file" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_02878d441ceae15ce060b73daf" ON "drive_folder" ("createdAt") `); + } +} diff --git a/packages/backend/migration/1697436246389-antenna-localOnly.js b/packages/backend/migration/1697436246389-antenna-localOnly.js new file mode 100644 index 000000000..022867329 --- /dev/null +++ b/packages/backend/migration/1697436246389-antenna-localOnly.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AntennaLocalOnly1697436246389 { + name = 'AntennaLocalOnly1697436246389' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" ADD "localOnly" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "localOnly"`); + } +} diff --git a/packages/backend/migration/1697441463087-FollowRequestWithReplies.js b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js new file mode 100644 index 000000000..214c6f668 --- /dev/null +++ b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + + +export class FollowRequestWithReplies1697441463087 { + name = 'FollowRequestWithReplies1697441463087' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "follow_request" ADD "withReplies" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "withReplies"`); + } +} diff --git a/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js b/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js new file mode 100644 index 000000000..fe0ea282d --- /dev/null +++ b/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + + +export class NoteReactionAndUserPairCache1697673894459 { + name = 'NoteReactionAndUserPairCache1697673894459' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "reactionAndUserPairCache" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "reactionAndUserPairCache"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index bc24b4938..30ca2e110 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -59,9 +59,9 @@ "@aws-sdk/client-s3": "3.412.0", "@aws-sdk/lib-storage": "3.412.0", "@smithy/node-http-handler": "2.1.5", - "@bull-board/api": "5.8.4", - "@bull-board/fastify": "5.8.4", - "@bull-board/ui": "5.8.4", + "@bull-board/api": "5.9.1", + "@bull-board/fastify": "5.9.1", + "@bull-board/ui": "5.9.1", "@discordapp/twemoji": "14.1.2", "@fastify/accepts": "4.2.0", "@fastify/cookie": "9.1.0", @@ -75,10 +75,10 @@ "@nestjs/core": "10.2.7", "@nestjs/testing": "10.2.7", "@peertube/http-signature": "1.7.0", - "@simplewebauthn/server": "8.2.0", + "@simplewebauthn/server": "8.3.2", "@sinonjs/fake-timers": "11.1.0", "@swc/cli": "0.1.62", - "@swc/core": "1.3.92", + "@swc/core": "1.3.93", "accepts": "1.3.8", "ajv": "8.12.0", "archiver": "6.0.1", @@ -86,7 +86,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "4.12.3", + "bullmq": "4.12.4", "cacheable-lookup": "7.0.0", "cbor": "9.0.1", "chalk": "5.3.0", @@ -97,7 +97,7 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "4.23.2", + "fastify": "4.24.2", "feed": "4.2.2", "file-type": "18.5.0", "fluent-ffmpeg": "2.1.2", @@ -121,13 +121,13 @@ "mime-types": "2.1.35", "misskey-js": "workspace:*", "ms": "3.0.0-canary.1", - "nanoid": "5.0.1", + "nanoid": "5.0.2", "nested-property": "4.0.0", "node-fetch": "3.3.2", "nodemailer": "6.9.6", "nsfwjs": "2.4.2", "oauth": "0.10.0", - "oauth2orize": "1.11.1", + "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", "otpauth": "9.1.5", @@ -155,7 +155,7 @@ "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "summaly": "github:misskey-dev/summaly", - "systeminformation": "5.21.11", + "systeminformation": "5.21.12", "tinycolor2": "1.6.0", "tmp": "0.2.1", "tsc-alias": "1.8.8", @@ -173,36 +173,36 @@ "@jest/globals": "29.7.0", "@simplewebauthn/typescript-types": "8.0.0", "@swc/jest": "0.2.29", - "@types/accepts": "1.3.5", - "@types/archiver": "5.3.3", - "@types/bcryptjs": "2.4.4", - "@types/body-parser": "1.19.3", + "@types/accepts": "1.3.6", + "@types/archiver": "5.3.4", + "@types/bcryptjs": "2.4.5", + "@types/body-parser": "1.19.4", "@types/cbor": "6.0.0", - "@types/color-convert": "2.0.1", - "@types/content-disposition": "0.5.6", + "@types/color-convert": "2.0.2", + "@types/content-disposition": "0.5.7", "@types/fluent-ffmpeg": "2.1.22", "@types/http-link-header": "1.0.3", "@types/jest": "29.5.5", - "@types/js-yaml": "4.0.6", + "@types/js-yaml": "4.0.7", "@types/jsdom": "21.1.3", "@types/jsonld": "1.5.10", "@types/jsrsasign": "10.5.9", "@types/mime-types": "2.1.2", "@types/ms": "0.7.32", - "@types/node": "20.8.4", + "@types/node": "20.8.6", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.11", "@types/oauth": "0.9.2", "@types/oauth2orize": "1.11.1", "@types/oauth2orize-pkce": "0.1.0", - "@types/pg": "8.10.4", + "@types/pg": "8.10.5", "@types/pug": "2.0.7", "@types/punycode": "2.1.0", "@types/qrcode": "1.5.2", "@types/random-seed": "0.3.3", "@types/ratelimiter": "3.4.4", "@types/rename": "1.0.5", - "@types/sanitize-html": "2.9.1", + "@types/sanitize-html": "2.9.2", "@types/semver": "7.5.3", "@types/sharp": "0.32.0", "@types/simple-oauth2": "5.0.5", @@ -211,9 +211,9 @@ "@types/tmp": "0.2.4", "@types/vary": "1.1.1", "@types/web-push": "3.6.1", - "@types/ws": "8.5.6", - "@typescript-eslint/eslint-plugin": "6.7.5", - "@typescript-eslint/parser": "6.7.5", + "@types/ws": "8.5.7", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", "aws-sdk-client-mock": "3.0.0", "cross-env": "7.0.3", "eslint": "8.51.0", diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index db64f4275..350aa6ba2 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -180,13 +180,13 @@ export class AccountMoveService { { muteeId: dst.id, expiresAt: IsNull() }, ).then(mutings => mutings.map(muting => muting.muterId)); - const newMutings: Map = new Map(); + const newMutings: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { let id: string; do { - id = this.idService.genId(); + id = this.idService.gen(); } while (newMutings.has(id)); return id; }; @@ -194,7 +194,6 @@ export class AccountMoveService { if (existingMutingsMuterUserIds.includes(muting.muterId)) continue; // skip if already muted indefinitely newMutings.set(genId(), { ...muting, - createdAt: new Date(), muteeId: dst.id, }); } @@ -228,20 +227,19 @@ export class AccountMoveService { }, }).then(memberships => memberships.map(membership => membership.userListId)); - const newMemberships: Map = new Map(); + const newMemberships: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { let id: string; do { - id = this.idService.genId(); + id = this.idService.gen(); } while (newMemberships.has(id)); return id; }; for (const membership of oldMemberships) { if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list newMemberships.set(genId(), { - createdAt: new Date(), userId: dst.id, userListId: membership.userListId, userListUserId: membership.userListUserId, diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index a5330db53..ec1a082d7 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -53,7 +53,7 @@ export class AnnouncementService { })) .andWhere(new Brackets(qb => { qb.orWhere('announcement.forExistingUsers = false'); - qb.orWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt }); + qb.orWhere('announcement.id > :userId', { userId: user.id }); })) .andWhere(`announcement.id NOT IN (${ readsQuery.getQuery() })`); @@ -65,8 +65,7 @@ export class AnnouncementService { @bindThis public async create(values: Partial, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { const announcement = await this.announcementsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: null, title: values.title, text: values.text, @@ -179,8 +178,7 @@ export class AnnouncementService { public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise { try { await this.announcementReadsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), announcementId: announcementId, userId: user.id, }); @@ -204,7 +202,7 @@ export class AnnouncementService { const reads = me ? (options?.reads ?? await this.getReads(me.id)) : []; return announcements.map(announcement => ({ id: announcement.id, - createdAt: announcement.createdAt.toISOString(), + createdAt: this.idService.parse(announcement.id).date.toISOString(), updatedAt: announcement.updatedAt?.toISOString() ?? null, text: announcement.text, title: announcement.title, diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index ca7624b1d..65be27554 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -16,7 +16,7 @@ import type { AntennasRepository, UserListMembershipsRepository } from '@/models import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -39,7 +39,7 @@ export class AntennaService implements OnApplicationShutdown { private utilityService: UtilityService, private globalEventService: GlobalEventService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { this.antennasFetched = false; this.antennas = []; @@ -57,14 +57,12 @@ export class AntennaService implements OnApplicationShutdown { case 'antennaCreated': this.antennas.push({ ...body, - createdAt: new Date(body.createdAt), lastUsedAt: new Date(body.lastUsedAt), }); break; case 'antennaUpdated': this.antennas[this.antennas.findIndex(a => a.id === body.id)] = { ...body, - createdAt: new Date(body.createdAt), lastUsedAt: new Date(body.lastUsedAt), }; break; @@ -86,7 +84,7 @@ export class AntennaService implements OnApplicationShutdown { const redisPipeline = this.redisForTimelines.pipeline(); for (const antenna of matchedAntennas) { - this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); + this.funoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); this.globalEventService.publishAntennaStream(antenna.id, 'note', note); } @@ -100,6 +98,8 @@ export class AntennaService implements OnApplicationShutdown { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; + if (antenna.localOnly && noteUser.host != null) return false; + if (!antenna.withReplies && note.replyId != null) return false; if (antenna.src === 'home') { diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts index 3d9982e80..e94f1eb53 100644 --- a/packages/backend/src/core/ClipService.ts +++ b/packages/backend/src/core/ClipService.ts @@ -46,8 +46,7 @@ export class ClipService { } const clip = await this.clipsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: name, isPublic: isPublic, @@ -109,7 +108,7 @@ export class ClipService { try { await this.clipNotesRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), noteId: noteId, clipId: clip.id, }); diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 0dc025d99..e7e66646f 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -61,7 +61,7 @@ import { FileInfoService } from './FileInfoService.js'; import { SearchService } from './SearchService.js'; import { ClipService } from './ClipService.js'; import { FeaturedService } from './FeaturedService.js'; -import { RedisTimelineService } from './RedisTimelineService.js'; +import { FunoutTimelineService } from './FunoutTimelineService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -190,7 +190,7 @@ const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: Fi const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService }; const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService }; -const $RedisTimelineService: Provider = { provide: 'RedisTimelineService', useExisting: RedisTimelineService }; +const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -323,7 +323,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SearchService, ClipService, FeaturedService, - RedisTimelineService, + FunoutTimelineService, ChartLoggerService, FederationChart, NotesChart, @@ -449,7 +449,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SearchService, $ClipService, $FeaturedService, - $RedisTimelineService, + $FunoutTimelineService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -576,7 +576,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SearchService, ClipService, FeaturedService, - RedisTimelineService, + FunoutTimelineService, FederationChart, NotesChart, UsersChart, @@ -701,7 +701,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SearchService, $ClipService, $FeaturedService, - $RedisTimelineService, + $FunoutTimelineService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 3419d0b49..6b475316f 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -52,8 +52,7 @@ export class CreateSystemUserService { if (exist) throw new Error('the user is already exists'); account = await transactionalEntityManager.insert(MiUser, { - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), username: username, usernameLower: username.toLowerCase(), host: null, diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 145c224f6..9a8267b46 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -69,7 +69,7 @@ export class CustomEmojiService implements OnApplicationShutdown { roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; }, moderator?: MiUser): Promise { const emoji = await this.emojisRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), updatedAt: new Date(), name: data.name, category: data.category, @@ -331,7 +331,7 @@ export class CustomEmojiService implements OnApplicationShutdown { const queryOrNull = async () => (await this.emojisRepository.findOneBy({ name, - host: host ?? IsNull(), + host, })) ?? null; const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index cecbec963..484f4fc52 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -564,8 +564,7 @@ export class DriveService { const folder = await fetchFolder(); let file = new MiDriveFile(); - file.id = this.idService.genId(); - file.createdAt = new Date(); + file.id = this.idService.gen(); file.userId = user ? user.id : null; file.userHost = user ? user.host : null; file.folderId = folder !== null ? folder.id : null; diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 61806583c..e41f010e4 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -56,7 +56,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { if (index == null) { const i = await this.instancesRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), host, firstRetrievedAt: new Date(), }).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/FunoutTimelineService.ts similarity index 95% rename from packages/backend/src/core/RedisTimelineService.ts rename to packages/backend/src/core/FunoutTimelineService.ts index 94541759c..c633c329e 100644 --- a/packages/backend/src/core/RedisTimelineService.ts +++ b/packages/backend/src/core/FunoutTimelineService.ts @@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; @Injectable() -export class RedisTimelineService { +export class FunoutTimelineService { constructor( @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, @@ -77,4 +77,9 @@ export class RedisTimelineService { ); }); } + + @bindThis + public purge(name: string) { + return this.redisForTimelines.del('list:' + name); + } } diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index ddff28359..d37899990 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -45,7 +45,7 @@ export class HashtagService { await this.updateHashtag(user, tag, true, true); } - for (const tag of (user.tags ?? []).filter(x => !tags.includes(x))) { + for (const tag of user.tags.filter(x => !tags.includes(x))) { await this.updateHashtag(user, tag, true, false); } } @@ -120,7 +120,7 @@ export class HashtagService { } else { if (isUserAttached) { this.hashtagsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), name: tag, mentionedUserIds: [], mentionedUsersCount: 0, @@ -137,7 +137,7 @@ export class HashtagService { } as MiHashtag); } else { this.hashtagsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), name: tag, mentionedUserIds: [user.id], mentionedUsersCount: 1, diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 06c58ad8a..c98b8ea6f 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -26,17 +26,21 @@ export class IdService { this.method = config.id.toLowerCase(); } + /** + * 時間を元にIDを生成します(省略時は現在日時) + * @param time 日時 + */ @bindThis - public genId(date?: Date): string { - if (!date || (date > new Date())) date = new Date(); + public gen(time?: number): string { + const t = (!time || (time > Date.now())) ? Date.now() : time; switch (this.method) { - case 'aid': return genAid(date); - case 'aidx': return genAidx(date); - case 'meid': return genMeid(date); - case 'meidg': return genMeidg(date); - case 'ulid': return ulid(date.getTime()); - case 'objectid': return genObjectId(date); + case 'aid': return genAid(t); + case 'aidx': return genAidx(t); + case 'meid': return genMeid(t); + case 'meidg': return genMeidg(t); + case 'ulid': return ulid(t); + case 'objectid': return genObjectId(t); default: throw new Error('unrecognized id generation method'); } } diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index f7f9063d9..8b78d0204 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -24,8 +24,7 @@ export class ModerationLogService { @bindThis public async log(moderator: { id: MiUser['id'] }, type: T, info?: ModerationLogPayloads[T]) { await this.moderationLogsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: moderator.id, type: type, info: (info as any) ?? {}, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 4496be3e7..f5cfe0312 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -54,8 +54,9 @@ import { RoleService } from '@/core/RoleService.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { nyaize } from '@/misc/nyaize.js'; +import { UtilityService } from '@/core/UtilityService.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -196,7 +197,7 @@ export class NoteCreateService implements OnApplicationShutdown { private idService: IdService, private globalEventService: GlobalEventService, private queueService: QueueService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, private noteReadService: NoteReadService, private notificationService: NotificationService, private relayService: RelayService, @@ -215,6 +216,7 @@ export class NoteCreateService implements OnApplicationShutdown { private perUserNotesChart: PerUserNotesChart, private activeUsersChart: ActiveUsersChart, private instanceChart: InstanceChart, + private utilityService: UtilityService, ) { } @bindThis @@ -222,12 +224,9 @@ export class NoteCreateService implements OnApplicationShutdown { id: MiUser['id']; username: MiUser['username']; host: MiUser['host']; - createdAt: MiUser['createdAt']; isBot: MiUser['isBot']; isCat: MiUser['isCat']; }, data: Option, silent = false): Promise { - let patsedText: mfm.MfmNode[] | null = null; - // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { @@ -251,8 +250,10 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.localOnly = true; + const meta = await this.metaService.fetch(); + if (data.visibility === 'public' && data.channel == null) { - const sensitiveWords = (await this.metaService.fetch()).sensitiveWords; + const sensitiveWords = meta.sensitiveWords; if (this.isSensitive(data, sensitiveWords)) { data.visibility = 'home'; } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { @@ -260,6 +261,12 @@ export class NoteCreateService implements OnApplicationShutdown { } } + const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); + + if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { + data.visibility = 'home'; + } + if (data.renote) { switch (data.renote.visibility) { case 'public': @@ -306,25 +313,6 @@ export class NoteCreateService implements OnApplicationShutdown { data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH); } data.text = data.text.trim(); - - if (user.isCat) { - patsedText = patsedText ?? mfm.parse(data.text); - function nyaizeNode(node: mfm.MfmNode) { - if (node.type === 'quote') return; - if (node.type === 'text') { - node.props.text = nyaize(node.props.text); - } - if (node.children) { - for (const child of node.children) { - nyaizeNode(child); - } - } - } - for (const node of patsedText) { - nyaizeNode(node); - } - data.text = mfm.toString(patsedText); - } } else { data.text = null; } @@ -335,7 +323,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Parse MFM if needed if (!tags || !emojis || !mentionedUsers) { - const tokens = patsedText ?? (data.text ? mfm.parse(data.text)! : []); + const tokens = (data.text ? mfm.parse(data.text)! : []); const cwTokens = data.cw ? mfm.parse(data.cw)! : []; const choiceTokens = data.poll && data.poll.choices ? concat(data.poll.choices.map(choice => mfm.parse(choice)!)) @@ -350,7 +338,7 @@ export class NoteCreateService implements OnApplicationShutdown { mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens); } - tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32); + tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32); if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { mentionedUsers.push(await this.usersRepository.findOneByOrFail({ id: data.reply!.userId })); @@ -383,8 +371,7 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private async insertNote(user: { id: MiUser['id']; host: MiUser['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new MiNote({ - id: this.idService.genId(data.createdAt!), - createdAt: data.createdAt!, + id: this.idService.gen(data.createdAt?.getTime()), fileIds: data.files ? data.files.map(file => file.id) : [], replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, @@ -483,7 +470,6 @@ export class NoteCreateService implements OnApplicationShutdown { id: MiUser['id']; username: MiUser['username']; host: MiUser['host']; - createdAt: MiUser['createdAt']; isBot: MiUser['isBot']; }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { const meta = await this.metaService.fetch(); @@ -577,7 +563,7 @@ export class NoteCreateService implements OnApplicationShutdown { } // Pack the note - const noteObj = await this.noteEntityService.pack(note); + const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true }); this.globalEventService.publishNotesStream(noteObj); @@ -844,9 +830,9 @@ export class NoteCreateService implements OnApplicationShutdown { const r = this.redisForTimelines.pipeline(); if (note.channelId) { - this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); + this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); - this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.funoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); const channelFollowings = await this.channelFollowingsRepository.find({ where: { @@ -856,9 +842,9 @@ export class NoteCreateService implements OnApplicationShutdown { }); for (const channelFollowing of channelFollowings) { - this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.funoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); } } } else { @@ -896,9 +882,9 @@ export class NoteCreateService implements OnApplicationShutdown { if (!following.withReplies) continue; } - this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.funoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); } } @@ -914,36 +900,36 @@ export class NoteCreateService implements OnApplicationShutdown { if (!userListMembership.withReplies) continue; } - this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); + this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); } } if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL - this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); } } // 自分自身以外への返信 if (note.replyId && note.replyUserId !== note.userId) { - this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.funoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); if (note.visibility === 'public' && note.userHost == null) { - this.redisTimelineService.push('localTimelineWithReplies', note.id, 300, r); + this.funoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); } } else { - this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.funoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); } if (note.visibility === 'public' && note.userHost == null) { - this.redisTimelineService.push('localTimeline', note.id, 1000, r); + this.funoutTimelineService.push('localTimeline', note.id, 1000, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push('localTimelineWithFiles', note.id, 500, r); + this.funoutTimelineService.push('localTimelineWithFiles', note.id, 500, r); } } } diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index 147554ee9..52abb4c2a 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -71,8 +71,7 @@ export class NotePiningService { } await this.userNotePiningsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, noteId: note.id, } as MiUserNotePining); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 422e0192c..03c1735e0 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -57,7 +57,7 @@ export class NoteReadService implements OnApplicationShutdown { if (isThreadMuted) return; const unread = { - id: this.idService.genId(), + id: this.idService.gen(), noteId: note.id, userId: userId, isSpecified: params.isSpecified, diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 32d54d257..c6d5023e6 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -125,7 +125,7 @@ export class NotificationService implements OnApplicationShutdown { } const notification = { - id: this.idService.genId(), + id: this.idService.gen(), createdAt: new Date(), type: type, notifierId: notifierId, diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 570f2350f..9e1b5ca78 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -72,10 +72,8 @@ export class PollService { throw new Error('already voted'); } - // Create vote await this.pollVotesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: user.id, choice: choice, diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 50d1d2e65..ae8f8a3f1 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -52,14 +52,14 @@ export class QueryService { q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); q.orderBy(`${q.alias}.id`, 'DESC'); } else if (sinceDate && untilDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.genId(new Date(sinceDate)) }); - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.genId(new Date(untilDate)) }); + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); + q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); q.orderBy(`${q.alias}.id`, 'DESC'); } else if (sinceDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.genId(new Date(sinceDate)) }); + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); q.orderBy(`${q.alias}.id`, 'ASC'); } else if (untilDate) { - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.genId(new Date(untilDate)) }); + q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); q.orderBy(`${q.alias}.id`, 'DESC'); } else { q.orderBy(`${q.alias}.id`, 'DESC'); diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index d8c725003..be378a899 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -237,10 +237,11 @@ export class QueueService { } @bindThis - public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id']) { + public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id'], withReplies?: boolean) { return this.dbQueue.add('importFollowing', { user: { id: user.id }, fileId: fileId, + withReplies, }, { removeOnComplete: true, removeOnFail: true, @@ -248,8 +249,8 @@ export class QueueService { } @bindThis - public createImportFollowingToDbJob(user: ThinUser, targets: string[]) { - const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel })); + public createImportFollowingToDbJob(user: ThinUser, targets: string[], withReplies?: boolean) { + const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel, withReplies })); return this.dbQueue.addBulk(jobs); } @@ -342,7 +343,7 @@ export class QueueService { } @bindThis - public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean }[]) { + public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean, withReplies?: boolean }[]) { const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel)); return this.relationshipQueue.addBulk(jobs); } @@ -384,6 +385,7 @@ export class QueueService { to: { id: data.to.id }, silent: data.silent, requestId: data.requestId, + withReplies: data.withReplies, }, opts: { removeOnComplete: true, diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index e7bbd4492..4233b8d4c 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -30,6 +30,7 @@ import { RoleService } from '@/core/RoleService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; const FALLBACK = '❤'; +const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16; const legacies: Record = { 'like': '👍', @@ -148,13 +149,12 @@ export class ReactionService { reaction = FALLBACK; } } else { - reaction = this.normalize(reaction ?? null); + reaction = this.normalize(reaction); } } const record: MiNoteReaction = { - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: user.id, reaction, @@ -188,6 +188,9 @@ export class ReactionService { await this.notesRepository.createQueryBuilder().update() .set({ reactions: () => sql, + ...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? { + reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`, + } : {}), }) .where('id = :id', { id: note.id }) .execute(); @@ -294,6 +297,7 @@ export class ReactionService { await this.notesRepository.createQueryBuilder().update() .set({ reactions: () => sql, + reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`, }) .where('id = :id', { id: note.id }) .execute(); diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 7171bf84c..d40cd080c 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -54,7 +54,7 @@ export class RelayService { @bindThis public async addRelay(inbox: string): Promise { const relay = await this.relaysRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), inbox, status: 'requesting', }).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 2c3547e4a..2c2ff7af1 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -20,7 +20,7 @@ import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import type { Packed } from '@/misc/json-schema.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export type RolePolicies = { @@ -103,7 +103,7 @@ export class RoleService implements OnApplicationShutdown { private globalEventService: GlobalEventService, private idService: IdService, private moderationLogService: ModerationLogService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { //this.onMessage = this.onMessage.bind(this); @@ -125,7 +125,6 @@ export class RoleService implements OnApplicationShutdown { if (cached) { cached.push({ ...body, - createdAt: new Date(body.createdAt), updatedAt: new Date(body.updatedAt), lastUsedAt: new Date(body.lastUsedAt), }); @@ -139,7 +138,6 @@ export class RoleService implements OnApplicationShutdown { if (i > -1) { cached[i] = { ...body, - createdAt: new Date(body.createdAt), updatedAt: new Date(body.updatedAt), lastUsedAt: new Date(body.lastUsedAt), }; @@ -159,7 +157,6 @@ export class RoleService implements OnApplicationShutdown { if (cached) { cached.push({ ...body, - createdAt: new Date(body.createdAt), expiresAt: body.expiresAt ? new Date(body.expiresAt) : null, }); } @@ -198,10 +195,10 @@ export class RoleService implements OnApplicationShutdown { return this.userEntityService.isRemoteUser(user); } case 'createdLessThan': { - return user.createdAt.getTime() > (Date.now() - (value.sec * 1000)); + return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000)); } case 'createdMoreThan': { - return user.createdAt.getTime() < (Date.now() - (value.sec * 1000)); + return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000)); } case 'followersLessThanOrEq': { return user.followersCount <= value.value; @@ -382,7 +379,7 @@ export class RoleService implements OnApplicationShutdown { @bindThis public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise { - const now = new Date(); + const now = Date.now(); const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); @@ -392,7 +389,7 @@ export class RoleService implements OnApplicationShutdown { }); if (existing) { - if (existing.expiresAt && (existing.expiresAt.getTime() < now.getTime())) { + if (existing.expiresAt && (existing.expiresAt.getTime() < now)) { await this.roleAssignmentsRepository.delete({ roleId: roleId, userId: userId, @@ -403,8 +400,7 @@ export class RoleService implements OnApplicationShutdown { } const created = await this.roleAssignmentsRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now), expiresAt: expiresAt, roleId: roleId, userId: userId, @@ -474,7 +470,7 @@ export class RoleService implements OnApplicationShutdown { const redisPipeline = this.redisClient.pipeline(); for (const role of roles) { - this.redisTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline); + this.funoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline); this.globalEventService.publishRoleTimelineStream(role.id, 'note', note); } @@ -485,8 +481,7 @@ export class RoleService implements OnApplicationShutdown { public async create(values: Partial, moderator?: MiUser): Promise { const date = new Date(); const created = await this.rolesRepository.insert({ - id: this.idService.genId(), - createdAt: date, + id: this.idService.gen(date.getTime()), updatedAt: date, lastUsedAt: date, name: values.name, diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 3ef321dd3..b6d2bcabc 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -131,7 +131,7 @@ export class SearchService { await this.meilisearchNoteIndex?.addDocuments([{ id: note.id, - createdAt: note.createdAt.getTime(), + createdAt: this.idService.parse(note.id).date.getTime(), userId: note.userId, userHost: note.userHost, channelId: note.channelId, diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index dfec0cfcf..b9e3ded46 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -120,8 +120,7 @@ export class SignupService { if (exist) throw new Error(' the username is already used'); account = await transactionalEntityManager.save(new MiUser({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), username: username, usernameLower: username.toLowerCase(), host: this.utilityService.toPunyNullable(host), diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 087dfd921..39b19325c 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -68,8 +68,7 @@ export class UserBlockingService implements OnModuleInit { ]); const blocking = { - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), blocker, blockerId: blocker.id, blockee, diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index beffcc2e9..4d7e14f68 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { IsNull } from 'typeorm'; import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; @@ -28,6 +28,8 @@ import { MetaService } from '@/core/MetaService.js'; import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import Logger from '../logger.js'; const logger = new Logger('following/create'); @@ -71,6 +73,7 @@ export class UserFollowingService implements OnModuleInit { private instancesRepository: InstancesRepository, private cacheService: CacheService, + private utilityService: UtilityService, private userEntityService: UserEntityService, private idService: IdService, private queueService: QueueService, @@ -81,6 +84,7 @@ export class UserFollowingService implements OnModuleInit { private webhookService: WebhookService, private apRendererService: ApRendererService, private accountMoveService: AccountMoveService, + private funoutTimelineService: FunoutTimelineService, private perUserFollowingChart: PerUserFollowingChart, private instanceChart: InstanceChart, ) { @@ -91,7 +95,15 @@ export class UserFollowingService implements OnModuleInit { } @bindThis - public async follow(_follower: { id: MiUser['id'] }, _followee: { id: MiUser['id'] }, requestId?: string, silent = false): Promise { + public async follow( + _follower: { id: MiUser['id'] }, + _followee: { id: MiUser['id'] }, + { requestId, silent = false, withReplies }: { + requestId?: string, + silent?: boolean, + withReplies?: boolean, + } = {}, + ): Promise { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), this.usersRepository.findOneByOrFail({ id: _followee.id }), @@ -118,15 +130,16 @@ export class UserFollowingService implements OnModuleInit { } const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id }); - // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or - // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである + // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or + // フォロワーがローカルユーザーであり、フォロー対象がサイレンスされているサーバーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく if ( followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || - (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') + (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') || + (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host)) ) { let autoAccept = false; @@ -168,12 +181,12 @@ export class UserFollowingService implements OnModuleInit { } if (!autoAccept) { - await this.createFollowRequest(follower, followee, requestId); + await this.createFollowRequest(follower, followee, requestId, withReplies); return; } } - await this.insertFollowingDoc(followee, follower, silent); + await this.insertFollowingDoc(followee, follower, silent, withReplies); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); @@ -190,16 +203,17 @@ export class UserFollowingService implements OnModuleInit { id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'] }, silent = false, + withReplies?: boolean, ): Promise { if (follower.id === followee.id) return; let alreadyFollowed = false as boolean; await this.followingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), followerId: follower.id, followeeId: followee.id, + withReplies: withReplies, // 非正規化 followerHost: follower.host, @@ -276,8 +290,8 @@ export class UserFollowingService implements OnModuleInit { this.perUserFollowingChart.update(follower, followee, true); } - // Publish follow event if (this.userEntityService.isLocalUser(follower) && !silent) { + // Publish follow event this.userEntityService.pack(followee.id, follower, { detail: true, }).then(async packed => { @@ -290,6 +304,8 @@ export class UserFollowingService implements OnModuleInit { }); } }); + + this.funoutTimelineService.purge(`homeTimeline:${follower.id}`); } // Publish followed event @@ -343,8 +359,8 @@ export class UserFollowingService implements OnModuleInit { this.decrementFollowing(following.follower, following.followee); - // Publish unfollow event if (!silent && this.userEntityService.isLocalUser(follower)) { + // Publish unfollow event this.userEntityService.pack(followee.id, follower, { detail: true, }).then(async packed => { @@ -357,6 +373,8 @@ export class UserFollowingService implements OnModuleInit { }); } }); + + this.funoutTimelineService.purge(`homeTimeline:${follower.id}`); } if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { @@ -452,6 +470,7 @@ export class UserFollowingService implements OnModuleInit { id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, requestId?: string, + withReplies?: boolean, ): Promise { if (follower.id === followee.id) return; @@ -465,11 +484,11 @@ export class UserFollowingService implements OnModuleInit { if (blocked) throw new Error('blocked'); const followRequest = await this.followRequestsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), followerId: follower.id, followeeId: followee.id, requestId, + withReplies, // 非正規化 followerHost: follower.host, @@ -554,7 +573,7 @@ export class UserFollowingService implements OnModuleInit { throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.'); } - await this.insertFollowingDoc(followee, follower); + await this.insertFollowingDoc(followee, follower, false, request.withReplies); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee)); @@ -694,4 +713,12 @@ export class UserFollowingService implements OnModuleInit { }); } } + + @bindThis + public getFollowees(userId: MiUser['id']) { + return this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: userId }) + .getMany(); + } } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 5b4e7a711..702c731fc 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -93,8 +93,7 @@ export class UserListService implements OnApplicationShutdown { } await this.userListMembershipsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: target.id, userListId: list.id, userListUserId: list.userId, diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 2387c9d64..397e6bdd5 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -26,8 +26,7 @@ export class UserMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { await this.mutingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), expiresAt: expiresAt ?? null, muterId: user.id, muteeId: target.id, diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index d2d2776bd..b95e41167 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -35,6 +35,12 @@ export class UtilityService { return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } + @bindThis + public isSilencedHost(silencedHosts: string[] | undefined, host: string | null): boolean { + if (!silencedHosts || host == null) return false; + return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); + } + @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index ff70f7bc0..930e6ef64 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -51,7 +51,6 @@ export class WebhookService implements OnApplicationShutdown { if (body.active) { this.webhooks.push({ ...body, - createdAt: new Date(body.createdAt), latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null, }); } @@ -62,13 +61,11 @@ export class WebhookService implements OnApplicationShutdown { if (i > -1) { this.webhooks[i] = { ...body, - createdAt: new Date(body.createdAt), latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null, }; } else { this.webhooks.push({ ...body, - createdAt: new Date(body.createdAt), latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null, }); } diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index b921ee745..7aba14068 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -164,7 +164,7 @@ export class ApInboxService { } // don't queue because the sender may attempt again when timeout - await this.userFollowingService.follow(actor, followee, activity.id); + await this.userFollowingService.follow(actor, followee, { requestId: activity.id }); return 'ok'; } @@ -514,8 +514,7 @@ export class ApInboxService { if (users.length < 1) return 'skip'; await this.abuseUserReportsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), targetUserId: users[0].id, targetUserHost: users[0].host, reporterId: actor.id, diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 7a9d2e21d..e29bc1d09 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -27,6 +27,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil import { bindThis } from '@/decorators.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { isNotNull } from '@/misc/is-not-null.js'; +import { IdService } from '@/core/IdService.js'; import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js'; @@ -59,6 +60,7 @@ export class ApRendererService { private userKeypairService: UserKeypairService, private apMfmService: ApMfmService, private mfmService: MfmService, + private idService: IdService, ) { } @@ -105,7 +107,7 @@ export class ApRendererService { id: `${this.config.url}/notes/${note.id}/activity`, actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Announce', - published: note.createdAt.toISOString(), + published: this.idService.parse(note.id).date.toISOString(), to, cc, object, @@ -137,7 +139,7 @@ export class ApRendererService { id: `${this.config.url}/notes/${note.id}/activity`, actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Create', - published: note.createdAt.toISOString(), + published: this.idService.parse(note.id).date.toISOString(), object, }; @@ -437,7 +439,7 @@ export class ApRendererService { }, _misskey_quote: quote, quoteUrl: quote, - published: note.createdAt.toISOString(), + published: this.idService.parse(note.id).date.toISOString(), to, cc, inReplyTo, diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 573dff5b9..1979cdda9 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -386,7 +386,7 @@ export class ApNoteService { this.logger.info(`register emoji host=${host}, name=${name}`); return await this.emojisRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), host, name, uri: tag.id, diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index ea6488339..47f8d7313 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -295,10 +295,9 @@ export class ApPersonService implements OnModuleInit { // Start transaction await this.db.transaction(async transactionalEntityManager => { user = await transactionalEntityManager.save(new MiUser({ - id: this.idService.genId(), + id: this.idService.gen(), avatarId: null, bannerId: null, - createdAt: new Date(), lastFetchedAt: new Date(), name: truncate(person.name, nameLength), isLocked: person.manuallyApprovesFollowers, @@ -607,8 +606,7 @@ export class ApPersonService implements OnModuleInit { for (const note of featuredNotes.filter((note): note is MiNote => note != null)) { td -= 1000; transactionalEntityManager.insert(MiUserNotePining, { - id: this.idService.genId(new Date(Date.now() + td)), - createdAt: new Date(), + id: this.idService.gen(Date.now() + td), userId: user.id, noteId: note.id, }); diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 55da1469e..f0918e059 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -9,6 +9,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/active-users.js'; @@ -29,6 +30,7 @@ export default class ActiveUsersChart extends Chart { // eslint-d private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, + private idService: IdService, ) { super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } @@ -42,20 +44,21 @@ export default class ActiveUsersChart extends Chart { // eslint-d } @bindThis - public async read(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise { + public async read(user: { id: MiUser['id'], host: null }): Promise { + const createdAt = this.idService.parse(user.id).date; await this.commit({ 'read': [user.id], - 'registeredWithinWeek': (Date.now() - user.createdAt.getTime() < week) ? [user.id] : [], - 'registeredWithinMonth': (Date.now() - user.createdAt.getTime() < month) ? [user.id] : [], - 'registeredWithinYear': (Date.now() - user.createdAt.getTime() < year) ? [user.id] : [], - 'registeredOutsideWeek': (Date.now() - user.createdAt.getTime() > week) ? [user.id] : [], - 'registeredOutsideMonth': (Date.now() - user.createdAt.getTime() > month) ? [user.id] : [], - 'registeredOutsideYear': (Date.now() - user.createdAt.getTime() > year) ? [user.id] : [], + 'registeredWithinWeek': (Date.now() - createdAt.getTime() < week) ? [user.id] : [], + 'registeredWithinMonth': (Date.now() - createdAt.getTime() < month) ? [user.id] : [], + 'registeredWithinYear': (Date.now() - createdAt.getTime() < year) ? [user.id] : [], + 'registeredOutsideWeek': (Date.now() - createdAt.getTime() > week) ? [user.id] : [], + 'registeredOutsideMonth': (Date.now() - createdAt.getTime() > month) ? [user.id] : [], + 'registeredOutsideYear': (Date.now() - createdAt.getTime() > year) ? [user.id] : [], }); } @bindThis - public async write(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise { + public async write(user: { id: MiUser['id'], host: null }): Promise { await this.commit({ 'write': [user.id], }); diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 0e65a10d2..97de891ec 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -9,6 +9,7 @@ import type { AbuseUserReportsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -18,6 +19,7 @@ export class AbuseUserReportEntityService { private abuseUserReportsRepository: AbuseUserReportsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -29,7 +31,7 @@ export class AbuseUserReportEntityService { return await awaitAll({ id: report.id, - createdAt: report.createdAt.toISOString(), + createdAt: this.idService.parse(report.id).date.toISOString(), comment: report.comment, resolved: report.resolved, reporterId: report.reporterId, diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index ed108f2ce..265a61e8a 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -9,12 +9,15 @@ import type { AntennasRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import type { MiAntenna } from '@/models/Antenna.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class AntennaEntityService { constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, + + private idService: IdService, ) { } @@ -26,7 +29,7 @@ export class AntennaEntityService { return { id: antenna.id, - createdAt: antenna.createdAt.toISOString(), + createdAt: this.idService.parse(antenna.id).date.toISOString(), name: antenna.name, keywords: antenna.keywords, excludeKeywords: antenna.excludeKeywords, @@ -34,6 +37,7 @@ export class AntennaEntityService { userListId: antenna.userListId, users: antenna.users, caseSensitive: antenna.caseSensitive, + localOnly: antenna.localOnly, notify: antenna.notify, withReplies: antenna.withReplies, withFile: antenna.withFile, diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 44466e24e..b4760346b 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { MiBlocking } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -20,6 +21,7 @@ export class BlockingEntityService { private blockingsRepository: BlockingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -32,7 +34,7 @@ export class BlockingEntityService { return await awaitAll({ id: blocking.id, - createdAt: blocking.createdAt.toISOString(), + createdAt: this.idService.parse(blocking.id).date.toISOString(), blockeeId: blocking.blockeeId, blockee: this.userEntityService.pack(blocking.blockeeId, me, { detail: true, diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 094de4d2d..dd72953c7 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -11,6 +11,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiChannel } from '@/models/Channel.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; import { NoteEntityService } from './NoteEntityService.js'; import { In } from 'typeorm'; @@ -38,6 +39,7 @@ export class ChannelEntityService { private noteEntityService: NoteEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -81,7 +83,7 @@ export class ChannelEntityService { return { id: channel.id, - createdAt: channel.createdAt.toISOString(), + createdAt: this.idService.parse(channel.id).date.toISOString(), lastNotedAt: channel.lastNotedAt ? channel.lastNotedAt.toISOString() : null, name: channel.name, description: channel.description, diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index e141db03f..96422894f 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiClip } from '@/models/Clip.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -23,6 +24,7 @@ export class ClipEntityService { private clipFavoritesRepository: ClipFavoritesRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -36,7 +38,7 @@ export class ClipEntityService { return await awaitAll({ id: clip.id, - createdAt: clip.createdAt.toISOString(), + createdAt: this.idService.parse(clip.id).date.toISOString(), lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null, userId: clip.userId, user: this.userEntityService.pack(clip.user ?? clip.userId), diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 23273b041..14be00036 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -17,6 +17,7 @@ import { deepClone } from '@/misc/clone.js'; import { bindThis } from '@/decorators.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; import { isNotNull } from '@/misc/is-not-null.js'; +import { IdService } from '@/core/IdService.js'; import { UtilityService } from '../UtilityService.js'; import { VideoProcessingService } from '../VideoProcessingService.js'; import { UserEntityService } from './UserEntityService.js'; @@ -44,6 +45,7 @@ export class DriveFileEntityService { private utilityService: UtilityService, private driveFolderEntityService: DriveFolderEntityService, private videoProcessingService: VideoProcessingService, + private idService: IdService, ) { } @@ -88,7 +90,7 @@ export class DriveFileEntityService { if (file.type.startsWith('video')) { if (file.thumbnailUrl) return file.thumbnailUrl; - return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url ?? file.uri); + return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url); } else if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { // 動画ではなくリモートかつメディアプロキシ return this.getProxiedUrl(file.uri, 'static'); @@ -143,7 +145,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -155,7 +157,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -167,7 +169,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -179,7 +181,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -196,7 +198,7 @@ export class DriveFileEntityService { return await awaitAll>({ id: file.id, - createdAt: file.createdAt.toISOString(), + createdAt: this.idService.parse(file.id).date.toISOString(), name: file.name, type: file.type, md5: file.md5, @@ -231,7 +233,7 @@ export class DriveFileEntityService { return await awaitAll>({ id: file.id, - createdAt: file.createdAt.toISOString(), + createdAt: this.idService.parse(file.id).date.toISOString(), name: file.name, type: file.type, md5: file.md5, diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index 55014284b..8fa78154b 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiDriveFolder } from '@/models/DriveFolder.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class DriveFolderEntityService { @@ -20,6 +21,8 @@ export class DriveFolderEntityService { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + + private idService: IdService, ) { } @@ -38,7 +41,7 @@ export class DriveFolderEntityService { return await awaitAll({ id: folder.id, - createdAt: folder.createdAt.toISOString(), + createdAt: this.idService.parse(folder.id).date.toISOString(), name: folder.name, parentId: folder.parentId, diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 4701cddcb..dc335d997 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiFlash } from '@/models/Flash.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -24,6 +25,7 @@ export class FlashEntityService { private flashLikesRepository: FlashLikesRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -37,7 +39,7 @@ export class FlashEntityService { return await awaitAll({ id: flash.id, - createdAt: flash.createdAt.toISOString(), + 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 } すると無限ループするので注意 diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index 9f6eb51e8..52aa97967 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiFollowing } from '@/models/Following.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; type LocalFollowerFollowing = MiFollowing & { @@ -45,6 +46,7 @@ export class FollowingEntityService { private followingsRepository: FollowingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -83,7 +85,7 @@ export class FollowingEntityService { return await awaitAll({ id: following.id, - createdAt: following.createdAt.toISOString(), + createdAt: this.idService.parse(following.id).date.toISOString(), followeeId: following.followeeId, followerId: following.followerId, followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, { diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index bbaf70f0f..d7b960e0d 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiGalleryPost } from '@/models/GalleryPost.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -26,6 +27,7 @@ export class GalleryPostEntityService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -39,7 +41,7 @@ export class GalleryPostEntityService { return await awaitAll({ id: post.id, - createdAt: post.createdAt.toISOString(), + createdAt: this.idService.parse(post.id).date.toISOString(), updatedAt: post.updatedAt.toISOString(), userId: post.userId, user: this.userEntityService.pack(post.user ?? post.userId, me), diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 0e27e9df7..9afe87eab 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -3,9 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/Blocking.js'; import type { MiInstance } from '@/models/Instance.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; @@ -43,6 +42,7 @@ export class InstanceEntityService { description: instance.description, maintainerName: instance.maintainerName, maintainerEmail: instance.maintainerEmail, + isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host), iconUrl: instance.iconUrl, faviconUrl: instance.faviconUrl, themeColor: instance.themeColor, diff --git a/packages/backend/src/core/entities/InviteCodeEntityService.ts b/packages/backend/src/core/entities/InviteCodeEntityService.ts index 914eaafe6..0f15fb5ab 100644 --- a/packages/backend/src/core/entities/InviteCodeEntityService.ts +++ b/packages/backend/src/core/entities/InviteCodeEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { MiUser } from '@/models/User.js'; import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -20,6 +21,7 @@ export class InviteCodeEntityService { private registrationTicketsRepository: RegistrationTicketsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -39,7 +41,7 @@ export class InviteCodeEntityService { id: target.id, code: target.code, expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null, - createdAt: target.createdAt.toISOString(), + createdAt: this.idService.parse(target.id).date.toISOString(), createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null, usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null, usedAt: target.usedAt ? target.usedAt.toISOString() : null, diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 83b024d83..6729ca267 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -10,6 +10,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import type { } from '@/models/Blocking.js'; import type { MiModerationLog } from '@/models/ModerationLog.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -19,6 +20,7 @@ export class ModerationLogEntityService { private moderationLogsRepository: ModerationLogsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -30,7 +32,7 @@ export class ModerationLogEntityService { return await awaitAll({ id: log.id, - createdAt: log.createdAt.toISOString(), + createdAt: this.idService.parse(log.id).date.toISOString(), type: log.type, info: log.info, userId: log.userId, diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index e3d5d2e21..9d672169b 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiMuting } from '@/models/Muting.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -21,6 +22,7 @@ export class MutingEntityService { private mutingsRepository: MutingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -33,7 +35,7 @@ export class MutingEntityService { return await awaitAll({ id: muting.id, - createdAt: muting.createdAt.toISOString(), + createdAt: this.idService.parse(muting.id).date.toISOString(), expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 316367f23..c100b92ee 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -16,6 +16,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; import { DebounceLoader } from '@/misc/loader.js'; +import { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -28,6 +29,7 @@ export class NoteEntityService implements OnModuleInit { private driveFileEntityService: DriveFileEntityService; private customEmojiService: CustomEmojiService; private reactionService: ReactionService; + private idService: IdService; private noteLoader = new DebounceLoader(this.findNoteOrFail); constructor( @@ -66,6 +68,7 @@ export class NoteEntityService implements OnModuleInit { this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.customEmojiService = this.moduleRef.get('CustomEmojiService'); this.reactionService = this.moduleRef.get('ReactionService'); + this.idService = this.moduleRef.get('IdService'); } @bindThis @@ -167,21 +170,31 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async populateMyReaction(note: MiNote, meId: MiUser['id'], _hint_?: { - myReactions: Map; + public async populateMyReaction(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: { + myReactions: Map; }) { if (_hint_?.myReactions) { const reaction = _hint_.myReactions.get(note.id); if (reaction) { - return this.reactionService.convertLegacyReaction(reaction.reaction); - } else if (reaction === null) { + return this.reactionService.convertLegacyReaction(reaction); + } else { + return undefined; + } + } + + const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) return undefined; + if (note.reactionAndUserPairCache && reactionsCount <= note.reactionAndUserPairCache.length) { + const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); + if (pair) { + return this.reactionService.convertLegacyReaction(pair.split('/')[1]); + } else { return undefined; } - // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない } // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない - if (note.createdAt.getTime() + 2000 > Date.now()) { + if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) { return undefined; } @@ -273,8 +286,9 @@ export class NoteEntityService implements OnModuleInit { options?: { detail?: boolean; skipHide?: boolean; + withReactionAndUserPairCache?: boolean; _hint_?: { - myReactions: Map; + myReactions: Map; packedFiles: Map | null>; }; }, @@ -282,6 +296,7 @@ export class NoteEntityService implements OnModuleInit { const opts = Object.assign({ detail: true, skipHide: false, + withReactionAndUserPairCache: false, }, options); const meId = me ? me.id : null; @@ -307,7 +322,7 @@ export class NoteEntityService implements OnModuleInit { const packed: Packed<'Note'> = await awaitAll({ id: note.id, - createdAt: note.createdAt.toISOString(), + createdAt: this.idService.parse(note.id).date.toISOString(), userId: note.userId, user: this.userEntityService.pack(note.user ?? note.userId, me, { detail: false, @@ -315,13 +330,14 @@ export class NoteEntityService implements OnModuleInit { text: text, cw: note.cw, visibility: note.visibility, - localOnly: note.localOnly ?? undefined, + localOnly: note.localOnly, reactionAcceptance: note.reactionAcceptance, visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, renoteCount: note.renoteCount, repliesCount: note.repliesCount, reactions: this.reactionService.convertLegacyReactions(note.reactions), reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host), + reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined, emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined, tags: note.tags.length > 0 ? note.tags : undefined, fileIds: note.fileIds, @@ -344,17 +360,19 @@ export class NoteEntityService implements OnModuleInit { reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, { detail: false, + withReactionAndUserPairCache: opts.withReactionAndUserPairCache, _hint_: options?._hint_, }) : undefined, renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, { detail: true, + withReactionAndUserPairCache: opts.withReactionAndUserPairCache, _hint_: options?._hint_, }) : undefined, poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, - ...(meId ? { + ...(meId && Object.keys(note.reactions).length > 0 ? { myReaction: this.populateMyReaction(note, meId, options?._hint_), } : {}), } : {}), @@ -379,18 +397,48 @@ export class NoteEntityService implements OnModuleInit { if (notes.length === 0) return []; const meId = me ? me.id : null; - const myReactionsMap = new Map(); + const myReactionsMap = new Map(); if (meId) { - const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); - // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない - const targets = [...notes.filter(n => n.createdAt.getTime() + 2000 < Date.now()).map(n => n.id), ...renoteIds]; - const myReactions = await this.noteReactionsRepository.findBy({ - userId: meId, - noteId: In(targets), - }); + const idsNeedFetchMyReaction = new Set(); - for (const target of targets) { - myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) ?? null); + // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない + const oldId = this.idService.gen(Date.now() - 2000); + + for (const note of notes) { + if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote + const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.renote.id, null); + } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) { + const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null); + } else { + idsNeedFetchMyReaction.add(note.renote.id); + } + } else { + if (note.id < oldId) { + const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.id, null); + } else if (reactionsCount <= note.reactionAndUserPairCache.length) { + const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); + } else { + idsNeedFetchMyReaction.add(note.id); + } + } else { + myReactionsMap.set(note.id, null); + } + } + } + + const myReactions = idsNeedFetchMyReaction.size > 0 ? await this.noteReactionsRepository.findBy({ + userId: meId, + noteId: In(Array.from(idsNeedFetchMyReaction)), + }) : []; + + for (const id of idsNeedFetchMyReaction) { + myReactionsMap.set(id, myReactions.find(reaction => reaction.noteId === id)?.reaction ?? null); } } diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index 808c8c9f6..1c9aed413 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -10,6 +10,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiNoteFavorite } from '@/models/NoteFavorite.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { NoteEntityService } from './NoteEntityService.js'; @Injectable() @@ -19,6 +20,7 @@ export class NoteFavoriteEntityService { private noteFavoritesRepository: NoteFavoritesRepository, private noteEntityService: NoteEntityService, + private idService: IdService, ) { } @@ -31,7 +33,7 @@ export class NoteFavoriteEntityService { return { id: favorite.id, - createdAt: favorite.createdAt.toISOString(), + createdAt: this.idService.parse(favorite.id).date.toISOString(), noteId: favorite.noteId, note: await this.noteEntityService.pack(favorite.note ?? favorite.noteId, me), }; diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 9701f37fd..f4aba3e54 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { NoteReactionsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; @@ -22,6 +23,7 @@ export class NoteReactionEntityService implements OnModuleInit { private userEntityService: UserEntityService; private noteEntityService: NoteEntityService; private reactionService: ReactionService; + private idService: IdService; constructor( private moduleRef: ModuleRef, @@ -32,6 +34,7 @@ export class NoteReactionEntityService implements OnModuleInit { //private userEntityService: UserEntityService, //private noteEntityService: NoteEntityService, //private reactionService: ReactionService, + //private idService: IdService, ) { } @@ -39,6 +42,7 @@ export class NoteReactionEntityService implements OnModuleInit { this.userEntityService = this.moduleRef.get('UserEntityService'); this.noteEntityService = this.moduleRef.get('NoteEntityService'); this.reactionService = this.moduleRef.get('ReactionService'); + this.idService = this.moduleRef.get('IdService'); } @bindThis @@ -57,7 +61,7 @@ export class NoteReactionEntityService implements OnModuleInit { return { id: reaction.id, - createdAt: reaction.createdAt.toISOString(), + createdAt: this.idService.parse(reaction.id).date.toISOString(), user: await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), ...(opts.withNote ? { diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index e3a1e19dd..f39ef949d 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -13,6 +13,7 @@ import type { MiUser } from '@/models/User.js'; import type { MiPage } from '@/models/Page.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -30,6 +31,7 @@ export class PageEntityService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -85,7 +87,7 @@ export class PageEntityService { return await awaitAll({ id: page.id, - createdAt: page.createdAt.toISOString(), + 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 } すると無限ループするので注意 diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts index 7111fab08..3f9dc9180 100644 --- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts +++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -21,6 +22,7 @@ export class RenoteMutingEntityService { private renoteMutingsRepository: RenoteMutingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -33,7 +35,7 @@ export class RenoteMutingEntityService { return await awaitAll({ id: muting.id, - createdAt: muting.createdAt.toISOString(), + createdAt: this.idService.parse(muting.id).date.toISOString(), muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { detail: true, diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 79375a700..5563f9a1a 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -12,6 +12,7 @@ import type { MiUser } from '@/models/User.js'; import type { MiRole } from '@/models/Role.js'; import { bindThis } from '@/decorators.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class RoleEntityService { @@ -21,6 +22,8 @@ export class RoleEntityService { @Inject(DI.roleAssignmentsRepository) private roleAssignmentsRepository: RoleAssignmentsRepository, + + private idService: IdService, ) { } @@ -51,7 +54,7 @@ export class RoleEntityService { return await awaitAll({ id: role.id, - createdAt: role.createdAt.toISOString(), + createdAt: this.idService.parse(role.id).date.toISOString(), updatedAt: role.updatedAt.toISOString(), name: role.name, description: role.description, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ee67634da..4a3ca0084 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { AnnouncementService } from '../AnnouncementService.js'; import type { CustomEmojiService } from '../CustomEmojiService.js'; @@ -60,6 +61,7 @@ export class UserEntityService implements OnModuleInit { private announcementService: AnnouncementService; private roleService: RoleService; private federatedInstanceService: FederatedInstanceService; + private idService: IdService; constructor( private moduleRef: ModuleRef, @@ -111,13 +113,6 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, - - //private noteEntityService: NoteEntityService, - //private driveFileEntityService: DriveFileEntityService, - //private pageEntityService: PageEntityService, - //private customEmojiService: CustomEmojiService, - //private antennaService: AntennaService, - //private roleService: RoleService, ) { } @@ -130,6 +125,7 @@ export class UserEntityService implements OnModuleInit { this.announcementService = this.moduleRef.get('AnnouncementService'); this.roleService = this.moduleRef.get('RoleService'); this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); + this.idService = this.moduleRef.get('IdService'); } //#region Validators @@ -337,8 +333,8 @@ export class UserEntityService implements OnModuleInit { host: user.host, avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user), avatarBlurhash: user.avatarBlurhash, - isBot: user.isBot ?? falsy, - isCat: user.isCat ?? falsy, + isBot: user.isBot, + isCat: user.isCat, instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? { name: instance.name, softwareName: instance.softwareName, @@ -364,14 +360,14 @@ export class UserEntityService implements OnModuleInit { ? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null))) .then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[]) : null, - createdAt: user.createdAt.toISOString(), + createdAt: this.idService.parse(user.id).date.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, bannerUrl: user.bannerUrl, bannerBlurhash: user.bannerBlurhash, isLocked: user.isLocked, isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), - isSuspended: user.isSuspended ?? falsy, + isSuspended: user.isSuspended, description: profile!.description, location: profile!.location, birthday: profile!.birthday, diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 06b6e852b..31ab7293d 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -10,6 +10,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiUserList } from '@/models/UserList.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -22,6 +23,7 @@ export class UserListEntityService { private userListMembershipsRepository: UserListMembershipsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -37,7 +39,7 @@ export class UserListEntityService { return { id: userList.id, - createdAt: userList.createdAt.toISOString(), + createdAt: this.idService.parse(userList.id).date.toISOString(), name: userList.name, userIds: users.map(x => x.userId), isPublic: userList.isPublic, @@ -50,7 +52,7 @@ export class UserListEntityService { ) { return Promise.all(memberships.map(async x => ({ id: x.id, - createdAt: x.createdAt.toISOString(), + createdAt: this.idService.parse(x.id).date.toISOString(), userId: x.userId, user: await this.userEntityService.pack(x.userId), withReplies: x.withReplies, diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index d29462874..c5ef9b2fa 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -108,6 +108,5 @@ async function net() { // FS STAT async function fs() { - const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); - return data ?? { rIO_sec: 0, wIO_sec: 0 }; + return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); } diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts index ec8aa849c..e7b59f262 100644 --- a/packages/backend/src/misc/id/aid.ts +++ b/packages/backend/src/misc/id/aid.ts @@ -24,8 +24,7 @@ function getNoise(): string { return counter.toString(36).padStart(2, '0').slice(-2); } -export function genAid(date: Date): string { - const t = date.getTime(); +export function genAid(t: number): string { if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date'); counter++; return getTime(t) + getNoise(); diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts index 5b031ea4c..bed223225 100644 --- a/packages/backend/src/misc/id/aidx.ts +++ b/packages/backend/src/misc/id/aidx.ts @@ -31,8 +31,7 @@ function getNoise(): string { return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH); } -export function genAidx(date: Date): string { - const t = date.getTime(); +export function genAidx(t: number): string { if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date'); counter++; return getTime(t) + nodeId + getNoise(); diff --git a/packages/backend/src/misc/id/meid.ts b/packages/backend/src/misc/id/meid.ts index 82cda3723..366738de0 100644 --- a/packages/backend/src/misc/id/meid.ts +++ b/packages/backend/src/misc/id/meid.ts @@ -29,8 +29,8 @@ function getRandom() { return str; } -export function genMeid(date: Date): string { - return getTime(date.getTime()) + getRandom(); +export function genMeid(t: number): string { + return getTime(t) + getRandom(); } export function parseMeid(id: string): { date: Date; } { diff --git a/packages/backend/src/misc/id/meidg.ts b/packages/backend/src/misc/id/meidg.ts index fba715671..426a46970 100644 --- a/packages/backend/src/misc/id/meidg.ts +++ b/packages/backend/src/misc/id/meidg.ts @@ -29,8 +29,8 @@ function getRandom() { return str; } -export function genMeidg(date: Date): string { - return 'g' + getTime(date.getTime()) + getRandom(); +export function genMeidg(t: number): string { + return 'g' + getTime(t) + getRandom(); } export function parseMeidg(id: string): { date: Date; } { diff --git a/packages/backend/src/misc/id/object-id.ts b/packages/backend/src/misc/id/object-id.ts index e3b6e8e43..49bd9591c 100644 --- a/packages/backend/src/misc/id/object-id.ts +++ b/packages/backend/src/misc/id/object-id.ts @@ -29,8 +29,8 @@ function getRandom() { return str; } -export function genObjectId(date: Date): string { - return getTime(date.getTime()) + getRandom(); +export function genObjectId(t: number): string { + return getTime(t) + getRandom(); } export function parseObjectId(id: string): { date: Date; } { diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index 2551af7cb..593c44f66 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -12,12 +12,6 @@ export class MiAbuseUserReport { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the AbuseUserReport.', - }) - public createdAt: Date; - @Index() @Column(id()) public targetUserId: MiUser['id']; diff --git a/packages/backend/src/models/AccessToken.ts b/packages/backend/src/models/AccessToken.ts index 5a6269a72..452711eb8 100644 --- a/packages/backend/src/models/AccessToken.ts +++ b/packages/backend/src/models/AccessToken.ts @@ -13,11 +13,6 @@ export class MiAccessToken { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the AccessToken.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { nullable: true, }) diff --git a/packages/backend/src/models/Ad.ts b/packages/backend/src/models/Ad.ts index 6dfc9cb30..b1d7d7d79 100644 --- a/packages/backend/src/models/Ad.ts +++ b/packages/backend/src/models/Ad.ts @@ -11,12 +11,6 @@ export class MiAd { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Ad.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The expired date of the Ad.', diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts index 34b092a8d..05d5a086f 100644 --- a/packages/backend/src/models/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -12,12 +12,6 @@ export class MiAnnouncement { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Announcement.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { comment: 'The updated date of the Announcement.', nullable: true, diff --git a/packages/backend/src/models/AnnouncementRead.ts b/packages/backend/src/models/AnnouncementRead.ts index 3d6ec5652..db09e65f5 100644 --- a/packages/backend/src/models/AnnouncementRead.ts +++ b/packages/backend/src/models/AnnouncementRead.ts @@ -14,11 +14,6 @@ export class MiAnnouncementRead { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the AnnouncementRead.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index dc398b6dd..b74c61b72 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -13,11 +13,6 @@ export class MiAntenna { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Antenna.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone') public lastUsedAt: Date; @@ -98,4 +93,9 @@ export class MiAntenna { default: true, }) public isActive: boolean; + + @Column('boolean', { + default: false, + }) + public localOnly: boolean; } diff --git a/packages/backend/src/models/App.ts b/packages/backend/src/models/App.ts index c599ef8be..5c56a224a 100644 --- a/packages/backend/src/models/App.ts +++ b/packages/backend/src/models/App.ts @@ -12,12 +12,6 @@ export class MiApp { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the App.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/AuthSession.ts b/packages/backend/src/models/AuthSession.ts index d9de6b697..81bed2121 100644 --- a/packages/backend/src/models/AuthSession.ts +++ b/packages/backend/src/models/AuthSession.ts @@ -13,11 +13,6 @@ export class MiAuthSession { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the AuthSession.', - }) - public createdAt: Date; - @Index() @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/Blocking.ts b/packages/backend/src/models/Blocking.ts index 1e3dd3a64..9bf7a63b6 100644 --- a/packages/backend/src/models/Blocking.ts +++ b/packages/backend/src/models/Blocking.ts @@ -13,12 +13,6 @@ export class MiBlocking { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Blocking.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Channel.ts b/packages/backend/src/models/Channel.ts index ae3886a65..f90f8c03d 100644 --- a/packages/backend/src/models/Channel.ts +++ b/packages/backend/src/models/Channel.ts @@ -13,12 +13,6 @@ export class MiChannel { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Channel.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/ChannelFavorite.ts b/packages/backend/src/models/ChannelFavorite.ts index ab74aa553..fc25ffe26 100644 --- a/packages/backend/src/models/ChannelFavorite.ts +++ b/packages/backend/src/models/ChannelFavorite.ts @@ -14,12 +14,6 @@ export class MiChannelFavorite { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ChannelFavorite.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/ChannelFollowing.ts b/packages/backend/src/models/ChannelFollowing.ts index c62a95332..4dd391a08 100644 --- a/packages/backend/src/models/ChannelFollowing.ts +++ b/packages/backend/src/models/ChannelFollowing.ts @@ -14,12 +14,6 @@ export class MiChannelFollowing { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ChannelFollowing.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Clip.ts b/packages/backend/src/models/Clip.ts index c60b2964e..2483b0925 100644 --- a/packages/backend/src/models/Clip.ts +++ b/packages/backend/src/models/Clip.ts @@ -12,11 +12,6 @@ export class MiClip { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Clip.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/ClipFavorite.ts b/packages/backend/src/models/ClipFavorite.ts index 054764389..aa949b3ea 100644 --- a/packages/backend/src/models/ClipFavorite.ts +++ b/packages/backend/src/models/ClipFavorite.ts @@ -14,9 +14,6 @@ export class MiClipFavorite { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/DriveFile.ts b/packages/backend/src/models/DriveFile.ts index c12f0e0f0..24e6be9c9 100644 --- a/packages/backend/src/models/DriveFile.ts +++ b/packages/backend/src/models/DriveFile.ts @@ -14,12 +14,6 @@ export class MiDriveFile { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the DriveFile.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/DriveFolder.ts b/packages/backend/src/models/DriveFolder.ts index 3e049136b..18f6d1770 100644 --- a/packages/backend/src/models/DriveFolder.ts +++ b/packages/backend/src/models/DriveFolder.ts @@ -12,12 +12,6 @@ export class MiDriveFolder { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the DriveFolder.', - }) - public createdAt: Date; - @Column('varchar', { length: 128, comment: 'The name of the DriveFolder.', diff --git a/packages/backend/src/models/Flash.ts b/packages/backend/src/models/Flash.ts index 185063029..ac880843b 100644 --- a/packages/backend/src/models/Flash.ts +++ b/packages/backend/src/models/Flash.ts @@ -12,12 +12,6 @@ export class MiFlash { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Flash.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The updated date of the Flash.', diff --git a/packages/backend/src/models/FlashLike.ts b/packages/backend/src/models/FlashLike.ts index 7c66010ae..ad7f4966b 100644 --- a/packages/backend/src/models/FlashLike.ts +++ b/packages/backend/src/models/FlashLike.ts @@ -14,9 +14,6 @@ export class MiFlashLike { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/FollowRequest.ts b/packages/backend/src/models/FollowRequest.ts index 769b9a6cb..9899694dd 100644 --- a/packages/backend/src/models/FollowRequest.ts +++ b/packages/backend/src/models/FollowRequest.ts @@ -13,11 +13,6 @@ export class MiFollowRequest { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the FollowRequest.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), @@ -50,6 +45,11 @@ export class MiFollowRequest { }) public requestId: string | null; + @Column('boolean', { + default: false, + }) + public withReplies: boolean; + //#region Denormalized fields @Column('varchar', { length: 128, nullable: true, diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts index 607538b1e..e320911a1 100644 --- a/packages/backend/src/models/Following.ts +++ b/packages/backend/src/models/Following.ts @@ -14,12 +14,6 @@ export class MiFollowing { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Following.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/GalleryLike.ts b/packages/backend/src/models/GalleryLike.ts index b5f71764a..84d4ce9c3 100644 --- a/packages/backend/src/models/GalleryLike.ts +++ b/packages/backend/src/models/GalleryLike.ts @@ -14,9 +14,6 @@ export class MiGalleryLike { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/GalleryPost.ts b/packages/backend/src/models/GalleryPost.ts index 4c6063f32..b72220caf 100644 --- a/packages/backend/src/models/GalleryPost.ts +++ b/packages/backend/src/models/GalleryPost.ts @@ -13,12 +13,6 @@ export class MiGalleryPost { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the GalleryPost.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The updated date of the GalleryPost.', diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index d2bd0c26e..23ae513ed 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -76,6 +76,11 @@ export class MiMeta { }) public sensitiveWords: string[]; + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public silencedHosts: string[]; + @Column('varchar', { length: 1024, nullable: true, diff --git a/packages/backend/src/models/ModerationLog.ts b/packages/backend/src/models/ModerationLog.ts index a12b6ab61..71b33c3e4 100644 --- a/packages/backend/src/models/ModerationLog.ts +++ b/packages/backend/src/models/ModerationLog.ts @@ -12,11 +12,6 @@ export class MiModerationLog { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the ModerationLog.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/Muting.ts b/packages/backend/src/models/Muting.ts index 2f06ca8e5..a528e1e7d 100644 --- a/packages/backend/src/models/Muting.ts +++ b/packages/backend/src/models/Muting.ts @@ -13,12 +13,6 @@ export class MiMuting { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Muting.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 3e2adf4d8..a4358b9ba 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -18,11 +18,6 @@ export class MiNote { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Note.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), @@ -169,6 +164,11 @@ export class MiNote { }) public mentionedRemoteUsers: string; + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public reactionAndUserPairCache: string[]; + @Column('varchar', { length: 128, array: true, default: '{}', }) diff --git a/packages/backend/src/models/NoteFavorite.ts b/packages/backend/src/models/NoteFavorite.ts index 1171684bc..364eaabd9 100644 --- a/packages/backend/src/models/NoteFavorite.ts +++ b/packages/backend/src/models/NoteFavorite.ts @@ -14,11 +14,6 @@ export class MiNoteFavorite { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the NoteFavorite.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/NoteReaction.ts b/packages/backend/src/models/NoteReaction.ts index 43323f8a4..ee3a44746 100644 --- a/packages/backend/src/models/NoteReaction.ts +++ b/packages/backend/src/models/NoteReaction.ts @@ -14,11 +14,6 @@ export class MiNoteReaction { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the NoteReaction.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/NoteThreadMuting.ts b/packages/backend/src/models/NoteThreadMuting.ts index 2d120e4c2..00311aa57 100644 --- a/packages/backend/src/models/NoteThreadMuting.ts +++ b/packages/backend/src/models/NoteThreadMuting.ts @@ -13,10 +13,6 @@ export class MiNoteThreadMuting { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 3cb986f4e..9cab87549 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -14,12 +14,6 @@ export class MiPage { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Page.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The updated date of the Page.', diff --git a/packages/backend/src/models/PageLike.ts b/packages/backend/src/models/PageLike.ts index 92adf9bcc..b845f58b7 100644 --- a/packages/backend/src/models/PageLike.ts +++ b/packages/backend/src/models/PageLike.ts @@ -14,9 +14,6 @@ export class MiPageLike { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/PasswordResetRequest.ts b/packages/backend/src/models/PasswordResetRequest.ts index 79f2e984b..5be439511 100644 --- a/packages/backend/src/models/PasswordResetRequest.ts +++ b/packages/backend/src/models/PasswordResetRequest.ts @@ -12,9 +12,6 @@ export class MiPasswordResetRequest { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index({ unique: true }) @Column('varchar', { length: 256, diff --git a/packages/backend/src/models/PollVote.ts b/packages/backend/src/models/PollVote.ts index 37cd55fc1..751be8a32 100644 --- a/packages/backend/src/models/PollVote.ts +++ b/packages/backend/src/models/PollVote.ts @@ -14,12 +14,6 @@ export class MiPollVote { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the PollVote.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/PromoRead.ts b/packages/backend/src/models/PromoRead.ts index 09ebfc834..d9f307541 100644 --- a/packages/backend/src/models/PromoRead.ts +++ b/packages/backend/src/models/PromoRead.ts @@ -14,11 +14,6 @@ export class MiPromoRead { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the PromoRead.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/RegistrationTicket.ts b/packages/backend/src/models/RegistrationTicket.ts index d94f46591..730cedffb 100644 --- a/packages/backend/src/models/RegistrationTicket.ts +++ b/packages/backend/src/models/RegistrationTicket.ts @@ -23,9 +23,6 @@ export class MiRegistrationTicket { }) public expiresAt: Date | null; - @Column('timestamp with time zone') - public createdAt: Date; - @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) diff --git a/packages/backend/src/models/RegistryItem.ts b/packages/backend/src/models/RegistryItem.ts index fdce57c46..60bdced95 100644 --- a/packages/backend/src/models/RegistryItem.ts +++ b/packages/backend/src/models/RegistryItem.ts @@ -13,11 +13,6 @@ export class MiRegistryItem { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the RegistryItem.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { comment: 'The updated date of the RegistryItem.', }) diff --git a/packages/backend/src/models/RenoteMuting.ts b/packages/backend/src/models/RenoteMuting.ts index d2a36249d..17df43ea3 100644 --- a/packages/backend/src/models/RenoteMuting.ts +++ b/packages/backend/src/models/RenoteMuting.ts @@ -13,12 +13,6 @@ export class MiRenoteMuting { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Muting.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Role.ts b/packages/backend/src/models/Role.ts index df7541db3..6976956e1 100644 --- a/packages/backend/src/models/Role.ts +++ b/packages/backend/src/models/Role.ts @@ -89,11 +89,6 @@ export class MiRole { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Role.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { comment: 'The updated date of the Role.', }) diff --git a/packages/backend/src/models/RoleAssignment.ts b/packages/backend/src/models/RoleAssignment.ts index 4e5322c60..30c7e19f2 100644 --- a/packages/backend/src/models/RoleAssignment.ts +++ b/packages/backend/src/models/RoleAssignment.ts @@ -14,11 +14,6 @@ export class MiRoleAssignment { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the RoleAssignment.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Signin.ts b/packages/backend/src/models/Signin.ts index a8b1a45c5..656b44dfe 100644 --- a/packages/backend/src/models/Signin.ts +++ b/packages/backend/src/models/Signin.ts @@ -12,11 +12,6 @@ export class MiSignin { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Signin.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/SwSubscription.ts b/packages/backend/src/models/SwSubscription.ts index be1e4e368..f685a8ff3 100644 --- a/packages/backend/src/models/SwSubscription.ts +++ b/packages/backend/src/models/SwSubscription.ts @@ -12,9 +12,6 @@ export class MiSwSubscription { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 4d961c429..796d7c835 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -13,12 +13,6 @@ export class MiUser { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the User.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/UserList.ts b/packages/backend/src/models/UserList.ts index 9af85af97..7ad15419d 100644 --- a/packages/backend/src/models/UserList.ts +++ b/packages/backend/src/models/UserList.ts @@ -12,11 +12,6 @@ export class MiUserList { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the UserList.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/UserListFavorite.ts b/packages/backend/src/models/UserListFavorite.ts index d0b054b93..a18ed9253 100644 --- a/packages/backend/src/models/UserListFavorite.ts +++ b/packages/backend/src/models/UserListFavorite.ts @@ -14,9 +14,6 @@ export class MiUserListFavorite { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListMembership.ts index f57f9ac33..fa8287f17 100644 --- a/packages/backend/src/models/UserListMembership.ts +++ b/packages/backend/src/models/UserListMembership.ts @@ -14,11 +14,6 @@ export class MiUserListMembership { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the UserListMembership.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/UserNotePining.ts b/packages/backend/src/models/UserNotePining.ts index 1d50a5068..ae5977aa5 100644 --- a/packages/backend/src/models/UserNotePining.ts +++ b/packages/backend/src/models/UserNotePining.ts @@ -14,11 +14,6 @@ export class MiUserNotePining { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the UserNotePinings.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/UserPending.ts b/packages/backend/src/models/UserPending.ts index b15ededa1..8b1f8f617 100644 --- a/packages/backend/src/models/UserPending.ts +++ b/packages/backend/src/models/UserPending.ts @@ -11,9 +11,6 @@ export class MiUserPending { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index({ unique: true }) @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts index 5b009c18a..ec4e13cc7 100644 --- a/packages/backend/src/models/Webhook.ts +++ b/packages/backend/src/models/Webhook.ts @@ -14,11 +14,6 @@ export class MiWebhook { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Antenna.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts index 7b6475919..4a9f0ed35 100644 --- a/packages/backend/src/models/json-schema/antenna.ts +++ b/packages/backend/src/models/json-schema/antenna.ts @@ -67,6 +67,11 @@ export const packedAntennaSchema = { optional: false, nullable: false, default: false, }, + localOnly: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, notify: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index ac07519f1..4ad84d02f 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -93,6 +93,11 @@ export const packedFederationInstanceSchema = { type: 'string', optional: false, nullable: true, }, + isSilenced: { + type: "boolean", + optional: false, + nullable: false, + }, infoUpdatedAt: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 2caf0d0c3..38c0054b5 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -174,6 +174,14 @@ export const packedNoteSchema = { type: 'string', optional: true, nullable: false, }, + reactionAndUserPairCache: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, myReaction: { type: 'object', diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts index 5aac3f19e..9f49d85c7 100644 --- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts +++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts @@ -47,13 +47,13 @@ export class AggregateRetentionProcessorService { // 今日登録したユーザーを全て取得 const targetUsers = await this.usersRepository.findBy({ host: IsNull(), - createdAt: MoreThan(new Date(Date.now() - (1000 * 60 * 60 * 24))), + id: MoreThan(this.idService.gen(Date.now() - (1000 * 60 * 60 * 24))), }); const targetUserIds = targetUsers.map(u => u.id); try { await this.retentionAggregationsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), createdAt: now, updatedAt: now, dateKey, diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index a0afbee3b..d0968d292 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -80,6 +80,7 @@ export class ExportAntennasProcessorService { return this.utilityService.getFullApAccount(u.username, u.host); // acct }) : null, caseSensitive: antenna.caseSensitive, + localOnly: antenna.localOnly, withReplies: antenna.withReplies, withFile: antenna.withFile, notify: antenna.notify, diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index 7248c7a64..af2a3434a 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -15,6 +15,7 @@ import { createTemp } from '@/misc/create-temp.js'; import type { MiPoll } from '@/models/Poll.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -35,6 +36,7 @@ export class ExportFavoritesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private idService: IdService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites'); } @@ -99,7 +101,7 @@ export class ExportFavoritesProcessorService { if (favorite.note.hasPoll) { poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id }); } - const content = JSON.stringify(serialize(favorite, poll)); + const content = JSON.stringify(this.serialize(favorite, poll)); const isFirst = exportedFavoritesCount === 0; await write(isFirst ? content : ',\n' + content); exportedFavoritesCount++; @@ -125,34 +127,34 @@ export class ExportFavoritesProcessorService { cleanup(); } } -} -function serialize(favorite: MiNoteFavorite & { note: MiNote & { user: MiUser } }, poll: MiPoll | null = null): Record { - return { - id: favorite.id, - createdAt: favorite.createdAt, - note: { - id: favorite.note.id, - text: favorite.note.text, - createdAt: favorite.note.createdAt, - fileIds: favorite.note.fileIds, - replyId: favorite.note.replyId, - renoteId: favorite.note.renoteId, - poll: poll, - cw: favorite.note.cw, - visibility: favorite.note.visibility, - visibleUserIds: favorite.note.visibleUserIds, - localOnly: favorite.note.localOnly, - reactionAcceptance: favorite.note.reactionAcceptance, - uri: favorite.note.uri, - url: favorite.note.url, - user: { - id: favorite.note.user.id, - name: favorite.note.user.name, - username: favorite.note.user.username, - host: favorite.note.user.host, - uri: favorite.note.user.uri, + private serialize(favorite: MiNoteFavorite & { note: MiNote & { user: MiUser } }, poll: MiPoll | null = null): Record { + return { + id: favorite.id, + createdAt: this.idService.parse(favorite.id).date.toISOString(), + note: { + id: favorite.note.id, + text: favorite.note.text, + createdAt: this.idService.parse(favorite.note.id).date.toISOString(), + fileIds: favorite.note.fileIds, + replyId: favorite.note.replyId, + renoteId: favorite.note.renoteId, + poll: poll, + cw: favorite.note.cw, + visibility: favorite.note.visibility, + visibleUserIds: favorite.note.visibleUserIds, + localOnly: favorite.note.localOnly, + reactionAcceptance: favorite.note.reactionAcceptance, + uri: favorite.note.uri, + url: favorite.note.url, + user: { + id: favorite.note.user.id, + name: favorite.note.user.name, + username: favorite.note.user.username, + host: favorite.note.user.host, + uri: favorite.note.user.uri, + }, }, - }, - }; + }; + } } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index e0bc80e19..cd4ccb0b0 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -17,6 +17,7 @@ import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; +import { IdService } from '@/core/IdService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -37,8 +38,8 @@ export class ExportNotesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, - private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } @@ -103,7 +104,7 @@ export class ExportNotesProcessorService { poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); } const files = await this.driveFileEntityService.packManyByIds(note.fileIds); - const content = JSON.stringify(serialize(note, poll, files)); + const content = JSON.stringify(this.serialize(note, poll, files)); const isFirst = exportedNotesCount === 0; await write(isFirst ? content : ',\n' + content); exportedNotesCount++; @@ -129,22 +130,22 @@ export class ExportNotesProcessorService { cleanup(); } } -} -function serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record { - return { - id: note.id, - text: note.text, - createdAt: note.createdAt, - fileIds: note.fileIds, - files: files, - replyId: note.replyId, - renoteId: note.renoteId, - poll: poll, - cw: note.cw, - visibility: note.visibility, - visibleUserIds: note.visibleUserIds, - localOnly: note.localOnly, - reactionAcceptance: note.reactionAcceptance, - }; + private serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record { + return { + id: note.id, + text: note.text, + createdAt: this.idService.parse(note.id).date.toISOString(), + fileIds: note.fileIds, + files: files, + replyId: note.replyId, + renoteId: note.renoteId, + poll: poll, + cw: note.cw, + visibility: note.visibility, + visibleUserIds: note.visibleUserIds, + localOnly: note.localOnly, + reactionAcceptance: note.reactionAcceptance, + }; + } } diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 7c95bccaf..291ea14b6 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -43,6 +43,7 @@ const validate = new Ajv().compile({ type: 'string', } }, caseSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -76,8 +77,7 @@ export class ImportAntennasProcessorService { continue; } const result = await this.antennasRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, userId: job.data.user.id, name: antenna.name, @@ -87,6 +87,7 @@ export class ImportAntennasProcessorService { excludeKeywords: antenna.excludeKeywords, users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean), caseSensitive: antenna.caseSensitive, + localOnly: antenna.localOnly, withReplies: antenna.withReplies, withFile: antenna.withFile, notify: antenna.notify, diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 2b5e41a12..e75499a56 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -56,7 +56,7 @@ export class ImportFollowingProcessorService { const csv = await this.downloadService.downloadTextFile(file.url); const targets = csv.trim().split('\n'); - this.queueService.createImportFollowingToDbJob({ id: user.id }, targets); + this.queueService.createImportFollowingToDbJob({ id: user.id }, targets, job.data.withReplies); this.logger.succ('Import jobs created'); } @@ -93,9 +93,9 @@ export class ImportFollowingProcessorService { // skip myself if (target.id === job.data.user.id) return; - this.logger.info(`Follow ${target.id} ...`); + this.logger.info(`Follow ${target.id} ${job.data.withReplies ? 'with replies' : 'without replies'} ...`); - this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true }]); + this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: job.data.withReplies }]); } catch (e) { this.logger.warn(`Error: ${e}`); } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 9be36a9d0..5dd3fbe88 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -80,8 +80,7 @@ export class ImportUserListsProcessorService { if (list == null) { list = await this.userListsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, name: listName, }).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 99e823f9f..89d4ea503 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -88,7 +88,7 @@ export class InboxProcessorService { if (err.isClientError) { throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`); } - throw new Error(`Error in actor ${activity.actor} - ${err.statusCode ?? err}`); + throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`); } } } diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index 5b2d2ef31..b2d8e3631 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -34,8 +34,12 @@ export class RelationshipProcessorService { @bindThis public async processFollow(job: Bull.Job): Promise { - this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id}`); - await this.userFollowingService.follow(job.data.from, job.data.to, job.data.requestId, job.data.silent); + this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`); + await this.userFollowingService.follow(job.data.from, job.data.to, { + requestId: job.data.requestId, + silent: job.data.silent, + withReplies: job.data.withReplies, + }); return 'ok'; } diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index c9122f5ca..9330c0152 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -32,6 +32,7 @@ export type RelationshipJobData = { to: ThinUser; silent?: boolean; requestId?: string; + withReplies?: boolean; } export type DbJobData = DbJobMap[T]; @@ -79,6 +80,7 @@ export type DbUserDeleteJobData = { export type DbUserImportJobData = { user: ThinUser; fileId: MiDriveFile['id']; + withReplies?: boolean; }; export type DBAntennaImportJobData = { @@ -89,6 +91,7 @@ export type DBAntennaImportJobData = { export type DbUserImportToDbJobData = { user: ThinUser; target: string; + withReplies?: boolean; }; export type ObjectStorageJobData = ObjectStorageFileJobData | Record; diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index e598b91e5..757cf2161 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -73,7 +73,7 @@ export class ServerService implements OnApplicationShutdown { public async launch(): Promise { const fastify = Fastify({ trustProxy: true, - logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''), + logger: false, }); this.#fastify = fastify; diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 150f3f24d..f3115690f 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -128,8 +128,7 @@ export class SigninApiService { const fail = async (status?: number, failure?: { id: string }) => { // Append signin history await this.signinsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, ip: request.ip, headers: request.headers as any, diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index cebba8c8e..98e902700 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -30,8 +30,7 @@ export class SigninService { setImmediate(async () => { // Append signin history const record = await this.signinsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, ip: request.ip, headers: request.headers as any, diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 431df581b..d2c444011 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -164,8 +164,7 @@ export class SignupApiService { const hash = await bcrypt.hash(password, salt); const pendingUser = await this.userPendingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), code, email: emailAddress!, username: username, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index e48dffecf..17f792639 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -44,8 +44,7 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const ad = await this.adsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), dayOfWeek: ps.dayOfWeek, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 262b36b9a..253a29cf5 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -71,7 +71,6 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const { raw, packed } = await this.announcementService.create({ - createdAt: new Date(), updatedAt: null, title: ps.title, text: ps.text, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index c82e702ee..fefc379c0 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -9,6 +9,7 @@ import type { MiAnnouncement } from '@/models/Announcement.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -81,6 +82,7 @@ export default class extends Endpoint { // eslint- private announcementReadsRepository: AnnouncementReadsRepository, private queryService: QueryService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); @@ -102,7 +104,7 @@ export default class extends Endpoint { // eslint- return announcements.map(announcement => ({ id: announcement.id, - createdAt: announcement.createdAt.toISOString(), + createdAt: this.idService.parse(announcement.id).date.toISOString(), updatedAt: announcement.updatedAt?.toISOString() ?? null, title: announcement.title, text: announcement.text, diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index 7fb5342f8..4e5320007 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -8,6 +8,7 @@ import type { DriveFilesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -163,6 +164,7 @@ export default class extends Endpoint { // eslint- private usersRepository: UsersRepository, private roleService: RoleService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({ @@ -212,7 +214,7 @@ export default class extends Endpoint { // eslint- type: file.type, name: file.name, md5: file.md5, - createdAt: file.createdAt.toISOString(), + createdAt: this.idService.parse(file.id).date.toISOString(), requestIp: iAmModerator ? file.requestIp : null, requestHeaders: iAmModerator && !ownerIsModerator ? file.requestHeaders : null, }; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index c5f986ff0..a65e4e762 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- } const copied = await this.emojisRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), updatedAt: new Date(), name: emoji.name, host: null, diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index cf94c998f..6afa82470 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserIpsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -28,11 +29,13 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.userIpsRepository) private userIpsRepository: UserIpsRepository, + + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const ips = await this.userIpsRepository.find({ where: { userId: ps.userId }, - order: { createdAt: 'DESC' }, + order: { id: 'DESC' }, take: 30, }); diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 2cc5ab6e3..4a22fd482 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -72,8 +72,7 @@ export default class extends Endpoint { // eslint- for (let i = 0; i < ps.count; i++) { ticketsPromises.push(this.registrationTicketsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, code: generateInviteCode(), }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0]))); diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts index a20a51121..f25d3fcb3 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts @@ -56,8 +56,8 @@ export default class extends Endpoint { // eslint- } switch (ps.sort) { - case '+createdAt': query.orderBy('ticket.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('ticket.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('ticket.id', 'DESC'); break; + case '-createdAt': query.orderBy('ticket.id', 'ASC'); break; case '+usedAt': query.orderBy('ticket.usedAt', 'DESC', 'NULLS LAST'); break; case '-usedAt': query.orderBy('ticket.usedAt', 'ASC', 'NULLS FIRST'); break; default: query.orderBy('ticket.id', 'DESC'); break; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5a74456ab..f29493434 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -105,6 +105,16 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + silencedHosts: { + type: "array", + optional: true, + nullable: false, + items: { + type: "string", + optional: false, + nullable: false, + }, + }, pinnedUsers: { type: 'array', optional: false, nullable: false, @@ -367,6 +377,7 @@ export default class extends Endpoint { // eslint- pinnedUsers: instance.pinnedUsers, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, + silencedHosts: instance.silencedHosts, sensitiveWords: instance.sensitiveWords, preservedUsernames: instance.preservedUsernames, hcaptchaSecretKey: instance.hcaptchaSecretKey, 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 ef5627bc9..b7f9aa049 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -10,6 +10,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -49,6 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private userEntityService: UserEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ @@ -74,7 +76,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(assigns.map(async assign => ({ id: assign.id, - createdAt: assign.createdAt, + createdAt: this.idService.parse(assign.id).date.toISOString(), user: await this.userEntityService.pack(assign.user!, me, { detail: true }), expiresAt: assign.expiresAt, }))); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 0731413d0..f550c4fd2 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -44,6 +45,7 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, private roleEntityService: RoleEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const [user, profile] = await Promise.all([ @@ -92,7 +94,7 @@ export default class extends Endpoint { // eslint- policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), roleAssigns: roleAssigns.map(a => ({ - createdAt: a.createdAt.toISOString(), + createdAt: this.idService.parse(a.id).date.toISOString(), expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null, roleId: a.roleId, })), 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 e89e1a149..fc810987d 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -99,8 +99,8 @@ export default class extends Endpoint { // eslint- switch (ps.sort) { case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break; - case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('user.id', 'DESC'); break; + case '-createdAt': query.orderBy('user.id', 'ASC'); break; case '+updatedAt': query.orderBy('user.updatedAt', 'DESC', 'NULLS LAST'); break; case '-updatedAt': query.orderBy('user.updatedAt', 'ASC', 'NULLS FIRST'); break; case '+lastActiveDate': query.orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST'); break; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 7db25e659..f05819b18 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -20,18 +20,26 @@ export const paramDef = { type: 'object', properties: { disableRegistration: { type: 'boolean', nullable: true }, - pinnedUsers: { type: 'array', nullable: true, items: { - type: 'string', - } }, - hiddenTags: { type: 'array', nullable: true, items: { - type: 'string', - } }, - blockedHosts: { type: 'array', nullable: true, items: { - type: 'string', - } }, - sensitiveWords: { type: 'array', nullable: true, items: { - type: 'string', - } }, + pinnedUsers: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + hiddenTags: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + blockedHosts: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + sensitiveWords: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, @@ -67,9 +75,11 @@ export const paramDef = { proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true }, maintainerName: { type: 'string', nullable: true }, maintainerEmail: { type: 'string', nullable: true }, - langs: { type: 'array', items: { - type: 'string', - } }, + langs: { + type: 'array', items: { + type: 'string', + }, + }, summalyProxy: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true }, deeplIsPro: { type: 'boolean' }, @@ -86,8 +96,8 @@ export const paramDef = { tosUrl: { type: 'string', nullable: true }, repositoryUrl: { type: 'string' }, feedbackUrl: { type: 'string' }, - impressumUrl: { type: 'string' }, - privacyPolicyUrl: { type: 'string' }, + impressumUrl: { type: 'string', nullable: true }, + privacyPolicyUrl: { type: 'string', nullable: true }, useObjectStorage: { type: 'boolean' }, objectStorageBaseUrl: { type: 'string', nullable: true }, objectStorageBucket: { type: 'string', nullable: true }, @@ -115,6 +125,13 @@ export const paramDef = { perUserHomeTimelineCacheMax: { type: 'integer' }, perUserListTimelineCacheMax: { type: 'integer' }, notesPerOneAd: { type: 'integer' }, + silencedHosts: { + type: 'array', + nullable: true, + items: { + type: 'string', + }, + }, }, required: [], } as const; @@ -147,7 +164,14 @@ export default class extends Endpoint { // eslint- if (Array.isArray(ps.sensitiveWords)) { set.sensitiveWords = ps.sensitiveWords.filter(Boolean); } - + if (Array.isArray(ps.silencedHosts)) { + let lastValue = ''; + set.silencedHosts = ps.silencedHosts.sort().filter((h) => { + const lv = lastValue; + lastValue = h; + return h !== '' && h !== lv && !set.blockedHosts?.includes(h); + }); + } if (ps.themeColor !== undefined) { set.themeColor = ps.themeColor; } diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 15fca4904..b029493d3 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -63,6 +63,7 @@ export const paramDef = { type: 'string', } }, caseSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -112,8 +113,7 @@ export default class extends Endpoint { // eslint- const now = new Date(); const antenna = await this.antennasRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, userId: me.id, name: ps.name, @@ -123,6 +123,7 @@ export default class extends Endpoint { // eslint- excludeKeywords: ps.excludeKeywords, users: ps.users, caseSensitive: ps.caseSensitive, + localOnly: ps.localOnly, withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 6d69971e3..9b5911800 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -12,7 +12,7 @@ import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -70,11 +70,11 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private queryService: QueryService, private noteReadService: NoteReadService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const antenna = await this.antennasRepository.findOneBy({ id: ps.antennaId, @@ -90,7 +90,7 @@ export default class extends Endpoint { // eslint- lastUsedAt: new Date(), }); - let noteIds = await this.redisTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { return []; diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 0e9874688..3457bb6f6 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -62,6 +62,7 @@ export const paramDef = { type: 'string', } }, caseSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -116,6 +117,7 @@ export default class extends Endpoint { // eslint- excludeKeywords: ps.excludeKeywords, users: ps.users, caseSensitive: ps.caseSensitive, + localOnly: ps.localOnly, withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index cb0022150..f89d9823b 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -55,8 +55,7 @@ export default class extends Endpoint { // eslint- // Create account const app = await this.appsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me ? me.id : null, name: ps.name, description: ps.description, diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 1b1893fd9..e0baeb356 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -80,8 +80,7 @@ export default class extends Endpoint { // eslint- const now = new Date(); await this.accessTokensRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, appId: session.appId, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 8b6a2c213..6e474c59e 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -79,8 +79,7 @@ export default class extends Endpoint { // eslint- // Create session token document const doc = await this.authSessionsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), appId: app.id, token: token, }).then(x => this.authSessionsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index e72120e15..3ba411d28 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -80,8 +80,7 @@ export default class extends Endpoint { // eslint- } const channel = await this.channelsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, description: ps.description ?? null, diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts index 1f78a86dd..c17571891 100644 --- a/packages/backend/src/server/api/endpoints/channels/favorite.ts +++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts @@ -57,8 +57,7 @@ export default class extends Endpoint { // eslint- } await this.channelFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, channelId: channel.id, }); diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 5a43e8be1..76ec6be80 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -57,8 +57,7 @@ export default class extends Endpoint { // eslint- } await this.channelFollowingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), followerId: me.id, followeeId: channel.id, }); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 2dfcf659d..fae4249c8 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -12,7 +12,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { CacheService } from '@/core/CacheService.js'; import { ApiError } from '../../error.js'; @@ -69,13 +69,13 @@ export default class extends Endpoint { // eslint- private idService: IdService, private noteEntityService: NoteEntityService, private queryService: QueryService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, private cacheService: CacheService, private activeUsersChart: ActiveUsersChart, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const isRangeSpecified = untilId != null && sinceId != null; const channel = await this.channelsRepository.findOneBy({ @@ -95,7 +95,7 @@ export default class extends Endpoint { // eslint- this.cacheService.userMutingsCache.fetch(me.id), ]) : [new Set()]; - let noteIds = await this.redisTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length > 0) { diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index 6cd34f0a5..015b2cfa8 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -74,8 +74,7 @@ export default class extends Endpoint { // eslint- } await this.clipFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), clipId: clip.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 6f3a62977..b7e9d12e9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -69,8 +69,8 @@ export default class extends Endpoint { // eslint- } switch (ps.sort) { - case '+createdAt': query.orderBy('file.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('file.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('file.id', 'DESC'); break; + case '-createdAt': query.orderBy('file.id', 'ASC'); break; case '+name': query.orderBy('file.name', 'DESC'); break; case '-name': query.orderBy('file.name', 'ASC'); break; case '+size': query.orderBy('file.size', 'DESC'); break; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index bc3a9bbe2..d18199f19 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -76,8 +76,7 @@ export default class extends Endpoint { // eslint- // Create folder const folder = await this.driveFoldersRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), name: ps.name, parentId: parent !== null ? parent.id : null, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index be73e5dbb..c8beefa9c 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -36,6 +36,7 @@ export const paramDef = { blocked: { type: 'boolean', nullable: true }, notResponding: { type: 'boolean', nullable: true }, suspended: { type: 'boolean', nullable: true }, + silenced: { type: "boolean", nullable: true }, federating: { type: 'boolean', nullable: true }, subscribing: { type: 'boolean', nullable: true }, publishing: { type: 'boolean', nullable: true }, @@ -102,6 +103,23 @@ export default class extends Endpoint { // eslint- } } + if (typeof ps.silenced === "boolean") { + const meta = await this.metaService.fetch(true); + + if (ps.silenced) { + if (meta.silencedHosts.length === 0) { + return []; + } + query.andWhere("instance.host IN (:...silences)", { + silences: meta.silencedHosts, + }); + } else if (meta.silencedHosts.length > 0) { + query.andWhere("instance.host NOT IN (:...silences)", { + silences: meta.silencedHosts, + }); + } + } + if (typeof ps.federating === 'boolean') { if (ps.federating) { query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index b46660d21..4fa65ac9a 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -53,9 +53,8 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const flash = await this.flashsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), userId: me.id, - createdAt: new Date(), updatedAt: new Date(), title: ps.title, summary: ps.summary, diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index a90e5f653..1003249c0 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -83,8 +83,7 @@ export default class extends Endpoint { // eslint- // Create like await this.flashLikesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), flashId: flash.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index e0e7fed87..9037944ef 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -71,6 +71,7 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id' }, + withReplies: { type: 'boolean' } }, required: ['userId'], } as const; @@ -112,7 +113,7 @@ export default class extends Endpoint { // eslint- } try { - await this.userFollowingService.follow(follower, followee); + await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies }); } catch (e) { if (e instanceof IdentifiableError) { if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 94701712d..71e0ad414 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -76,8 +76,7 @@ export default class extends Endpoint { // eslint- } const post = await this.galleryPostsRepository.insert(new MiGalleryPost({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: new Date(), title: ps.title, description: ps.description, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index c55705406..561b2492a 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -83,8 +83,7 @@ export default class extends Endpoint { // eslint- // Create like await this.galleryLikesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), postId: post.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 1cef76d3d..50aea7994 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -66,8 +66,8 @@ export default class extends Endpoint { // eslint- switch (ps.sort) { case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break; - case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('user.id', 'DESC'); break; + case '-createdAt': query.orderBy('user.id', 'ASC'); break; case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; } diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index daa3e536a..09f6540a7 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { requireCredential: true, @@ -27,6 +28,8 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, + + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const query = this.accessTokensRepository.createQueryBuilder('token') @@ -34,8 +37,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('token.app', 'app'); switch (ps.sort) { - case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('token.id', 'DESC'); break; + case '-createdAt': query.orderBy('token.id', 'ASC'); break; case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break; case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break; default: query.orderBy('token.id', 'ASC'); break; @@ -46,7 +49,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(tokens.map(token => ({ id: token.id, name: token.name ?? token.app?.name, - createdAt: token.createdAt, + createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt, permission: token.permission, }))); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 38c928304..e5fa2ac96 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -52,6 +52,7 @@ export const paramDef = { type: 'object', properties: { fileId: { type: 'string', format: 'misskey:id' }, + withReplies: { type: 'boolean' }, }, required: ['fileId'], } as const; @@ -79,7 +80,7 @@ export default class extends Endpoint { // eslint- ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); - this.queueService.createImportFollowingJob(me, file.id); + this.queueService.createImportFollowingJob(me, file.id, ps.withReplies); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index c074b152d..6203e7aa8 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -53,8 +53,7 @@ export default class extends Endpoint { // eslint- }); } else { await this.registryItemsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: new Date(), userId: me.id, domain: null, diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 48eaeff40..f00dba4a8 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -63,8 +63,7 @@ export default class extends Endpoint { // eslint- } const webhook = await this.webhooksRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, url: ps.url, diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts index 7361ab616..94836283f 100644 --- a/packages/backend/src/server/api/endpoints/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/invite/create.ts @@ -62,7 +62,7 @@ export default class extends Endpoint { // eslint- if (policies.inviteLimit) { const count = await this.registrationTicketsRepository.countBy({ - createdAt: MoreThan(new Date(Date.now() - (policies.inviteLimitCycle * 1000 * 60))), + id: MoreThan(this.idService.gen(Date.now() - (policies.inviteLimitCycle * 1000 * 60))), createdById: me.id, }); @@ -72,8 +72,7 @@ export default class extends Endpoint { // eslint- } const ticket = await this.registrationTicketsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), createdBy: me, createdById: me.id, expiresAt: policies.inviteExpirationTime ? new Date(Date.now() + (policies.inviteExpirationTime * 1000 * 60)) : null, diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts index 43b94e4f0..1f4190c94 100644 --- a/packages/backend/src/server/api/endpoints/invite/limit.ts +++ b/packages/backend/src/server/api/endpoints/invite/limit.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RegistrationTicketsRepository } from '@/models/_.js'; import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['meta'], @@ -41,12 +42,13 @@ export default class extends Endpoint { // eslint- private registrationTicketsRepository: RegistrationTicketsRepository, private roleService: RoleService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const policies = await this.roleService.getUserPolicies(me.id); const count = policies.inviteLimit ? await this.registrationTicketsRepository.countBy({ - createdAt: MoreThan(new Date(Date.now() - (policies.inviteExpirationTime * 60 * 1000))), + id: MoreThan(this.idService.gen(Date.now() - (policies.inviteExpirationTime * 60 * 1000))), createdById: me.id, }) : null; diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index e40656cb6..cac8f41f8 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -59,8 +59,7 @@ export default class extends Endpoint { // eslint- // Insert access token doc await this.accessTokensRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, session: ps.session, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index cc648e22a..ed3dce7f3 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -80,8 +80,7 @@ export default class extends Endpoint { // eslint- // Create favorite await this.noteFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 8ac5f1b03..9986c6e43 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -5,7 +5,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -15,7 +14,9 @@ import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { CacheService } from '@/core/CacheService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -63,9 +64,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -74,11 +72,13 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, + private queryService: QueryService, + private userFollowingService: UserFollowingService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const policies = await this.roleService.getUserPolicies(me.id); if (!policies.ltlAvailable) { @@ -96,71 +96,148 @@ export default class extends Endpoint { // eslint- ]); let noteIds: string[]; + let shouldFallbackToDb = false; if (ps.withFiles) { - const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([ + const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([ `homeTimelineWithFiles:${me.id}`, 'localTimelineWithFiles', ], untilId, sinceId); noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds])); } else if (ps.withReplies) { - const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.redisTimelineService.getMulti([ + const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.funoutTimelineService.getMulti([ `homeTimeline:${me.id}`, 'localTimeline', 'localTimelineWithReplies', ], untilId, sinceId); noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds])); } else { - const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([ + const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([ `homeTimeline:${me.id}`, 'localTimeline', ], untilId, sinceId); noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds])); + shouldFallbackToDb = htlNoteIds.length === 0; } noteIds.sort((a, b) => a > b ? -1 : 1); noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (!shouldFallbackToDb) { + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - let timeline = await query.getMany(); - - timeline = timeline.filter(note => { - if (note.userId === me.id) { - return true; - } - if (isUserRelated(note, userIdsWhoBlockingMe)) return false; - if (isUserRelated(note, userIdsWhoMeMuting)) return false; - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; - if (ps.withRenotes === false) return false; + timeline = timeline.filter(note => { + if (note.userId === me.id) { + return true; } + if (isUserRelated(note, userIdsWhoBlockingMe)) return false; + if (isUserRelated(note, userIdsWhoMeMuting)) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; + if (ps.withRenotes === false) return false; + } + } + + return true; + }); + + // TODO: フィルタした結果件数が足りなかった場合の対応 + + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } else { // fallback to db + const followees = await this.userFollowingService.getFollowees(me.id); + + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { + if (followees.length > 0) { + const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; + qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); + qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + } else { + qb.where('note.userId = :meId', { meId: me.id }); + qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + } + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + query.andWhere(new Brackets(qb => { + qb + .where('note.replyId IS NULL') // 返信ではない + .orWhere(new Brackets(qb => { + qb // 返信だけど投稿者自身への返信 + .where('note.replyId IS NOT NULL') + .andWhere('note.replyUserId = note.userId'); + })); + })); + + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); } - return true; - }); + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - // TODO: フィルタした結果件数が足りなかった場合の対応 + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - timeline.sort((a, b) => a.id > b.id ? -1 : 1); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion - process.nextTick(() => { - this.activeUsersChart.read(me); - }); + const timeline = await query.limit(ps.limit).getMany(); - return await this.noteEntityService.packMany(timeline, me); + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index d10c3bedb..9d5688f96 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -5,7 +5,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; import type { MiNote, NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -15,7 +14,8 @@ import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { QueryService } from '@/core/QueryService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -59,9 +59,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -70,11 +67,12 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const policies = await this.roleService.getUserPolicies(me ? me.id : null); if (!policies.ltlAvailable) { @@ -94,9 +92,9 @@ export default class extends Endpoint { // eslint- let noteIds: string[]; if (ps.withFiles) { - noteIds = await this.redisTimelineService.get('localTimelineWithFiles', untilId, sinceId); + noteIds = await this.funoutTimelineService.get('localTimelineWithFiles', untilId, sinceId); } else { - const [nonReplyNoteIds, replyNoteIds] = await this.redisTimelineService.getMulti([ + const [nonReplyNoteIds, replyNoteIds] = await this.funoutTimelineService.getMulti([ 'localTimeline', 'localTimelineWithReplies', ], untilId, sinceId); @@ -106,49 +104,75 @@ export default class extends Endpoint { // eslint- noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (noteIds.length > 0) { + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - let timeline = await query.getMany(); - - timeline = timeline.filter(note => { - if (me && (note.userId === me.id)) { - return true; - } - if (!ps.withReplies && note.replyId && (me == null || note.replyUserId !== me.id)) return false; - if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; - if (me && isUserRelated(note, userIdsWhoMeMuting)) return false; - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (me && isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; - if (ps.withRenotes === false) return false; + timeline = timeline.filter(note => { + if (me && (note.userId === me.id)) { + return true; } + if (!ps.withReplies && note.replyId && (me == null || note.replyUserId !== me.id)) return false; + if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; + if (me && isUserRelated(note, userIdsWhoMeMuting)) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (me && isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; + if (ps.withRenotes === false) return false; + } + } + + return true; + }); + + // TODO: フィルタした結果件数が足りなかった場合の対応 + + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); + + return await this.noteEntityService.packMany(timeline, me); + } else { // fallback to db + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } - return true; - }); + const timeline = await query.limit(ps.limit).getMany(); - // TODO: フィルタした結果件数が足りなかった場合の対応 + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); - timeline.sort((a, b) => a.id > b.id ? -1 : 1); - - process.nextTick(() => { - if (me) { - this.activeUsersChart.read(me); - } - }); - - return await this.noteEntityService.packMany(timeline, me); + return await this.noteEntityService.packMany(timeline, me); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 986201e95..af7ff8bdc 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -98,7 +98,7 @@ export default class extends Endpoint { // eslint- id: In(polls.map(poll => poll.noteId)), }, order: { - createdAt: 'DESC', + id: 'DESC', }, }); diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index a58bf09b8..734c3f0e6 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -145,8 +145,7 @@ export default class extends Endpoint { // eslint- // Create vote const vote = await this.pollVotesRepository.insert({ - id: this.idService.genId(), - createdAt, + id: this.idService.gen(createdAt.getTime()), noteId: note.id, userId: me.id, choice: ps.choice, diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index 449a83860..b2cdaa00a 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -72,8 +72,7 @@ export default class extends Endpoint { // eslint- await this.noteReadService.read(me.id, mutedNotes); await this.noteThreadMutingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), threadId: note.threadId ?? note.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 760d52c9d..3b597107a 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -5,8 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; -import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -15,7 +14,8 @@ import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; export const meta = { tags: ['notes'], @@ -53,9 +53,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -63,11 +60,13 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, + private userFollowingService: UserFollowingService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const [ followings, @@ -81,52 +80,127 @@ export default class extends Endpoint { // eslint- this.cacheService.userBlockedCache.fetch(me.id), ]); - let noteIds = await this.redisTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (noteIds.length > 0) { + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - let timeline = await query.getMany(); - - timeline = timeline.filter(note => { - if (note.userId === me.id) { - return true; - } - if (isUserRelated(note, userIdsWhoBlockingMe)) return false; - if (isUserRelated(note, userIdsWhoMeMuting)) return false; - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; - if (ps.withRenotes === false) return false; + timeline = timeline.filter(note => { + if (note.userId === me.id) { + return true; } - } - if (note.reply && note.reply.visibility === 'followers') { - if (!Object.hasOwn(followings, note.reply.userId)) return false; + if (isUserRelated(note, userIdsWhoBlockingMe)) return false; + if (isUserRelated(note, userIdsWhoMeMuting)) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; + if (ps.withRenotes === false) return false; + } + } + if (note.reply && note.reply.visibility === 'followers') { + if (!Object.hasOwn(followings, note.reply.userId)) return false; + } + + return true; + }); + + // TODO: フィルタした結果件数が足りなかった場合の対応 + + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } else { // fallback to db + const followees = await this.userFollowingService.getFollowees(me.id); + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.channelId IS NULL') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + if (followees.length > 0) { + const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; + + query.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); + } else { + query.andWhere('note.userId = :meId', { meId: me.id }); } - return true; - }); + query.andWhere(new Brackets(qb => { + qb + .where('note.replyId IS NULL') // 返信ではない + .orWhere(new Brackets(qb => { + qb // 返信だけど投稿者自身への返信 + .where('note.replyId IS NOT NULL') + .andWhere('note.replyUserId = note.userId'); + })); + })); - // TODO: フィルタした結果件数が足りなかった場合の対応 + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserRenotesQueryForNotes(query, me); - timeline.sort((a, b) => a.id > b.id ? -1 : 1); + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - process.nextTick(() => { - this.activeUsersChart.read(me); - }); + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - return await this.noteEntityService.packMany(timeline, me); + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.limit(ps.limit).getMany(); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index f7ee58264..2b31e6169 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -15,7 +15,7 @@ import { DI } from '@/di-symbols.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -80,11 +80,11 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private cacheService: CacheService, private idService: IdService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const list = await this.userListsRepository.findOneBy({ id: ps.listId, @@ -105,7 +105,7 @@ export default class extends Endpoint { // eslint- this.cacheService.userBlockedCache.fetch(me.id), ]); - let noteIds = await this.redisTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index c0e8fab16..4c2ef516e 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -103,8 +103,7 @@ export default class extends Endpoint { // eslint- }); const page = await this.pagesRepository.insert(new MiPage({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: new Date(), title: ps.title, name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 6c69cad9d..8c18982b5 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -83,8 +83,7 @@ export default class extends Endpoint { // eslint- // Create like await this.pageLikesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), pageId: page.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index b197756ac..7d07c9217 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -60,8 +60,7 @@ export default class extends Endpoint { // eslint- } await this.promoReadsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 3c9d266e2..7ff7b5de3 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -90,8 +90,7 @@ export default class extends Endpoint { // eslint- // Create mute await this.renoteMutingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), muterId: muter.id, muteeId: mutee.id, } as MiRenoteMuting); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index adb160c58..f13710e1d 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -84,8 +84,7 @@ export default class extends Endpoint { // eslint- const token = secureRndstr(64, { chars: L_CHARS }); await this.passwordResetRequestsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: profile.userId, token, }); diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 1858c922a..3a6dc14bc 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['reset password'], @@ -38,6 +39,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const req = await this.passwordResetRequestsRepository.findOneByOrFail({ @@ -45,7 +48,7 @@ export default class extends Endpoint { // eslint- }); // 発行してから30分以上経過していたら無効 - if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { + if (Date.now() - this.idService.parse(req.id).date.getTime() > 1000 * 60 * 30) { throw new Error(); // TODO } diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 0db51abc5..daa9affc2 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -11,7 +11,7 @@ import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -66,11 +66,11 @@ export default class extends Endpoint { // eslint- private idService: IdService, private noteEntityService: NoteEntityService, private queryService: QueryService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const role = await this.rolesRepository.findOneBy({ id: ps.roleId, @@ -84,7 +84,7 @@ export default class extends Endpoint { // eslint- return []; } - let noteIds = await this.redisTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 5cfbeab73..9ab062326 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -88,8 +88,7 @@ export default class extends Endpoint { // eslint- } await this.swSubscriptionsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, endpoint: ps.endpoint, auth: ps.auth, diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 21c585f1a..8dc584131 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -74,8 +74,8 @@ export default class extends Endpoint { // eslint- switch (ps.sort) { case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break; - case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('user.id', 'DESC'); break; + case '-createdAt': query.orderBy('user.id', 'ASC'); break; case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break; case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break; default: query.orderBy('user.id', 'ASC'); break; diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index f2f6c4303..4eb37c3e4 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -104,8 +104,7 @@ export default class extends Endpoint { // eslint- } const userList = await this.userListsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 60b2b3f17..e86e4c0de 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -66,8 +66,7 @@ export default class extends Endpoint { // eslint- } const userList = await this.userListsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts index 1707afee6..2ecf0a125 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -69,8 +69,7 @@ export default class extends Endpoint { } await this.userListFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, userListId: ps.listId, }); diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index df0951ce7..343d320f6 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -14,7 +14,7 @@ import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { QueryService } from '@/core/QueryService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -71,11 +71,11 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private cacheService: CacheService, private idService: IdService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const isRangeSpecified = untilId != null && sinceId != null; const isSelf = me && (me.id === ps.userId); @@ -87,9 +87,9 @@ export default class extends Endpoint { // eslint- ]) : [new Set()]; const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([ - this.redisTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId), - ps.withReplies ? this.redisTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), - ps.withChannelNotes ? this.redisTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), + this.funoutTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId), + ps.withReplies ? this.funoutTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), + ps.withChannelNotes ? this.funoutTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), ]); let noteIds = Array.from(new Set([ @@ -151,7 +151,10 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); if (ps.withChannelNotes) { - if (!isSelf) query.andWhere('channel.isSensitive = false'); + if (!isSelf) query.andWhere(new Brackets(qb => { + qb.orWhere('note.channelId IS NULL'); + qb.orWhere('channel.isSensitive = false'); + })); } else { query.andWhere('note.channelId IS NULL'); } diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 50aa6fa09..3bcf44cc4 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -82,8 +82,7 @@ export default class extends Endpoint { // eslint- } const report = await this.abuseUserReportsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), targetUserId: user.id, targetUserHost: user.host, reporterId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts index 194d48805..b3f67815e 100644 --- a/packages/backend/src/server/api/endpoints/users/update-memo.ts +++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { // eslint- if (!previousMemo) { await this.userMemosRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), userId: me.id, targetUserId: target.id, memo: ps.memo, diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index a01714e76..57034231a 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -38,19 +38,6 @@ class ChannelChannel extends Channel { private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する @@ -58,6 +45,13 @@ class ChannelChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 03f2dff62..553c44071 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -52,19 +52,6 @@ class GlobalTimelineChannel extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 関係ない返信は除外 if (note.reply && !this.following[note.userId]?.withReplies) { const reply = note.reply; @@ -84,6 +71,13 @@ class GlobalTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 3945b1a1e..f30b29cfd 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -43,13 +43,6 @@ class HashtagChannel extends Channel { const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag)))); if (!matched) return; - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する @@ -57,6 +50,13 @@ class HashtagChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 24be59050..2b235b982 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -49,29 +49,12 @@ class HomeTimelineChannel extends Channel { } // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile!.mutedInstances ?? []))) return; + if (isInstanceMuted(note, new Set(this.userProfile!.mutedInstances))) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await this.noteEntityService.pack(note.id, this.user!, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user!, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, { - detail: true, - }); - } + if (note.visibility === 'followers') { + if (!Object.hasOwn(this.following, note.userId)) return; + } else if (note.visibility === 'specified') { + if (!note.visibleUserIds!.includes(this.user!.id)) return; } // 関係ない返信は除外 @@ -90,6 +73,13 @@ class HomeTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index adedca515..4e9091208 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -62,31 +62,14 @@ class HybridTimelineChannel extends Channel { (note.channelId != null && this.followingChannels.has(note.channelId)) )) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await this.noteEntityService.pack(note.id, this.user!, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user!, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, { - detail: true, - }); - } + if (note.visibility === 'followers') { + if (!Object.hasOwn(this.following, note.userId)) return; + } else if (note.visibility === 'specified') { + if (!note.visibleUserIds!.includes(this.user!.id)) return; } // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile!.mutedInstances ?? []))) return; + if (isInstanceMuted(note, new Set(this.userProfile!.mutedInstances))) return; // 関係ない返信は除外 if (note.reply && !this.following[note.userId]?.withReplies && !this.withReplies) { @@ -104,6 +87,14 @@ class HybridTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + console.log(note.renote.reactionAndUserPairCache); + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 69aa366f0..9dd05b9b0 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -54,19 +54,6 @@ class LocalTimelineChannel extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null && !this.followingChannels.has(note.channelId)) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 関係ない返信は除外 if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) { const reply = note.reply; @@ -83,6 +70,13 @@ class LocalTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 240822d9a..6d83d4cb5 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -82,27 +82,10 @@ class UserListChannel extends Channel { if (!Object.hasOwn(this.membershipsMap, note.userId)) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await this.noteEntityService.pack(note.id, this.user, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } + if (note.visibility === 'followers') { + if (!Object.hasOwn(this.following, note.userId)) return; + } else if (note.visibility === 'specified') { + if (!note.visibleUserIds!.includes(this.user!.id)) return; } // 関係ない返信は除外 @@ -119,6 +102,15 @@ class UserListChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + + this.connection.cacheNote(note); + this.send('note', note); } diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts index c3a78561c..4fa7b800e 100644 --- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts +++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts @@ -325,8 +325,7 @@ export class OAuth2ProviderService { // NOTE: we don't have a setup for automatic token expiration await accessTokensRepository.insert({ - id: idService.genId(), - createdAt: now, + id: idService.gen(now.getTime()), lastUsedAt: now, userId: granted.userId, token: accessToken, diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 78551e800..3ba26ad34 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -13,6 +13,7 @@ import type { MiUser } from '@/models/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class FeedService { @@ -31,6 +32,7 @@ export class FeedService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -49,14 +51,14 @@ export class FeedService { renoteId: IsNull(), visibility: In(['public', 'home']), }, - order: { createdAt: -1 }, + order: { id: -1 }, take: 20, }); const feed = new Feed({ id: author.link, title: `${author.name} (@${user.username}@${this.config.host})`, - updated: notes[0].createdAt, + updated: this.idService.parse(notes[0].id).date, generator: 'Misskey', description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, link: author.link, @@ -78,10 +80,10 @@ export class FeedService { feed.addItem({ title: `New note by ${author.name}`, link: `${this.config.url}/notes/${note.id}`, - date: note.createdAt, + date: this.idService.parse(note.id).date, description: note.cw ?? undefined, content: note.text ?? undefined, - image: file ? this.driveFileEntityService.getPublicUrl(file) ?? undefined : undefined, + image: file ? this.driveFileEntityService.getPublicUrl(file) : undefined, }); } diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 7e2beab1a..c0317f143 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -174,6 +174,7 @@ describe('アンテナ', () => { users: [''], withFile: false, withReplies: false, + localOnly: false, } as Antenna; assert.deepStrictEqual(response, expected); }); diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index 3f158f9f1..b009ef124 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -188,6 +188,7 @@ describe('Account Move', () => { excludeKeywords: [], users: [], caseSensitive: false, + localOnly: false, withReplies: false, withFile: false, notify: false, @@ -431,6 +432,7 @@ describe('Account Move', () => { excludeKeywords: [], users: [eve.id], caseSensitive: false, + localOnly: false, withReplies: false, withFile: false, notify: false, diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 77de14488..5a83bbb7d 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -18,7 +18,6 @@ describe('Streaming', () => { const follow = async (follower: any, followee: any) => { await Followings.save({ id: 'a', - createdAt: new Date(), followerId: follower.id, followeeId: followee.id, followerHost: follower.host, diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index f753b54c6..28f07bf3f 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -727,7 +727,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { }, alice); + const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index 8f61d91ba..f2aa5d35e 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -35,8 +35,7 @@ describe('AnnouncementService', () => { function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(Date.now()), username: un, usernameLower: un, ...data, @@ -44,10 +43,9 @@ describe('AnnouncementService', () => { .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); } - function createAnnouncement(data: Partial = {}) { + function createAnnouncement(data: Partial = {}) { return announcementsRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(data.createdAt ?? new Date()), updatedAt: null, title: 'Title', text: 'Text', diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index c6a14702a..f644312bc 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -37,8 +37,7 @@ describe('RoleService', () => { function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(Date.now()), username: un, usernameLower: un, ...data, @@ -48,8 +47,7 @@ describe('RoleService', () => { function createRole(data: Partial = {}) { return rolesRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(Date.now()), updatedAt: new Date(), lastUsedAt: new Date(), description: '', @@ -198,10 +196,10 @@ describe('RoleService', () => { test('conditional role', async () => { const user1 = await createUser({ - createdAt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 365)), + id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), }); const user2 = await createUser({ - createdAt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 365)), + id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), followersCount: 10, }); await createRole({ diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 2e9454927..c418e8413 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -23,6 +23,7 @@ import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; import { MetaService } from '@/core/MetaService.js'; import type { MiRemoteUser } from '@/models/User.js'; +import { genAidx } from '@/misc/id/aidx.js'; import { MockResolver } from '../misc/mock-resolver.js'; const host = 'https://host1.test'; @@ -200,7 +201,7 @@ describe('ActivityPub', () => { describe('Renderer', () => { test('Render an announce with visibility: followers', () => { rendererService.renderAnnounce(null, { - createdAt: new Date(0), + id: genAidx(Date.now()), visibility: 'followers', } as MiNote); }); diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts index 57b4ea994..59783a9fa 100644 --- a/packages/backend/test/unit/misc/id.ts +++ b/packages/backend/test/unit/misc/id.ts @@ -14,44 +14,44 @@ import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js'; describe('misc:id', () => { test('aid', () => { - const date = new Date(); + const date = Date.now(); const gotAid = genAid(date); expect(gotAid).toMatch(aidRegExp); - expect(parseAid(gotAid).date.getTime()).toBe(date.getTime()); + expect(parseAid(gotAid).date.getTime()).toBe(date); }); test('aidx', () => { - const date = new Date(); + const date = Date.now(); const gotAidx = genAidx(date); expect(gotAidx).toMatch(aidxRegExp); - expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime()); + expect(parseAidx(gotAidx).date.getTime()).toBe(date); }); test('meid', () => { - const date = new Date(); + const date = Date.now(); const gotMeid = genMeid(date); expect(gotMeid).toMatch(meidRegExp); - expect(parseMeid(gotMeid).date.getTime()).toBe(date.getTime()); + expect(parseMeid(gotMeid).date.getTime()).toBe(date); }); test('meidg', () => { - const date = new Date(); + const date = Date.now(); const gotMeidg = genMeidg(date); expect(gotMeidg).toMatch(meidgRegExp); - expect(parseMeidg(gotMeidg).date.getTime()).toBe(date.getTime()); + expect(parseMeidg(gotMeidg).date.getTime()).toBe(date); }); test('objectid', () => { - const date = new Date(); + const date = Date.now(); const gotObjectId = genObjectId(date); expect(gotObjectId).toMatch(objectIdRegExp); - expect(Math.floor(parseObjectId(gotObjectId).date.getTime() / 1000)).toBe(Math.floor(date.getTime() / 1000)); + expect(Math.floor(parseObjectId(gotObjectId).date.getTime() / 1000)).toBe(Math.floor(date / 1000)); }); test('ulid', () => { - const date = new Date(); - const gotUlid = ulid(date.getTime()); + const date = Date.now(); + const gotUlid = ulid(date); expect(gotUlid).toMatch(ulidRegExp); - expect(parseUlid(gotUlid).date.getTime()).toBe(date.getTime()); + expect(parseUlid(gotUlid).date.getTime()).toBe(date); }); }); diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8b7604f31..feadbbb04 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -20,7 +20,7 @@ "@github/webauthn-json": "2.1.1", "@rollup/plugin-alias": "5.0.1", "@rollup/plugin-json": "6.0.1", - "@rollup/plugin-replace": "5.0.3", + "@rollup/plugin-replace": "5.0.4", "@rollup/pluginutils": "5.0.5", "@syuilo/aiscript": "0.16.0", "@tabler/icons-webfont": "2.37.0", @@ -29,7 +29,7 @@ "@vue/compiler-sfc": "3.3.4", "astring": "1.8.6", "autosize": "6.0.1", - "broadcast-channel": "5.3.0", + "broadcast-channel": "5.4.0", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "buraha": "0.0.1", "canvas-confetti": "1.6.1", @@ -38,7 +38,7 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "7.2.3", + "chromatic": "7.4.0", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "2.30.0", @@ -57,9 +57,9 @@ "prismjs": "1.29.0", "punycode": "2.3.0", "querystring": "0.2.1", - "rollup": "4.0.2", + "rollup": "4.1.4", "sanitize-html": "2.11.0", - "sass": "1.69.1", + "sass": "1.69.3", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.157.0", @@ -78,44 +78,44 @@ "vuedraggable": "next" }, "devDependencies": { - "@storybook/addon-actions": "7.4.6", - "@storybook/addon-essentials": "7.4.6", - "@storybook/addon-interactions": "7.4.6", - "@storybook/addon-links": "7.4.6", - "@storybook/addon-storysource": "7.4.6", - "@storybook/addons": "7.4.6", - "@storybook/blocks": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/addon-actions": "7.5.0", + "@storybook/addon-essentials": "7.5.0", + "@storybook/addon-interactions": "7.5.0", + "@storybook/addon-links": "7.5.0", + "@storybook/addon-storysource": "7.5.0", + "@storybook/addons": "7.5.0", + "@storybook/blocks": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/jest": "0.2.3", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/react": "7.4.6", - "@storybook/react-vite": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/react": "7.5.0", + "@storybook/react-vite": "7.5.0", "@storybook/testing-library": "0.2.2", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", - "@storybook/vue3": "7.4.6", - "@storybook/vue3-vite": "7.4.6", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", + "@storybook/vue3": "7.5.0", + "@storybook/vue3-vite": "7.5.0", "@testing-library/vue": "7.0.0", "@types/escape-regexp": "0.0.1", "@types/estree": "1.0.2", "@types/matter-js": "0.19.1", "@types/micromatch": "4.0.3", - "@types/node": "20.8.4", + "@types/node": "20.8.6", "@types/punycode": "2.1.0", - "@types/sanitize-html": "2.9.1", + "@types/sanitize-html": "2.9.2", "@types/throttle-debounce": "5.0.0", "@types/tinycolor2": "1.4.4", "@types/uuid": "9.0.5", "@types/websocket": "1.0.7", - "@types/ws": "8.5.6", - "@typescript-eslint/eslint-plugin": "6.7.5", - "@typescript-eslint/parser": "6.7.5", + "@types/ws": "8.5.7", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", "@vitest/coverage-v8": "0.34.6", "@vue/runtime-core": "3.3.4", "acorn": "8.10.0", "cross-env": "7.0.3", - "cypress": "13.3.0", + "cypress": "13.3.1", "eslint": "8.51.0", "eslint-plugin-import": "2.28.1", "eslint-plugin-vue": "9.17.0", @@ -123,19 +123,19 @@ "happy-dom": "10.0.3", "micromatch": "4.0.5", "msw": "1.3.2", - "msw-storybook-addon": "1.8.0", + "msw-storybook-addon": "1.9.0", "nodemon": "3.0.1", "prettier": "3.0.3", "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.1", - "storybook": "7.4.6", + "storybook": "7.5.0", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", "vue-eslint-parser": "9.3.2", - "vue-tsc": "1.8.18" + "vue-tsc": "1.8.19" } } diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 15043fcd0..b8de71e3b 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -42,6 +42,7 @@ import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { $i } from '@/account.js'; +import { defaultStore } from "@/store.js"; const props = withDefaults(defineProps<{ user: Misskey.entities.UserDetailed, @@ -52,6 +53,10 @@ const props = withDefaults(defineProps<{ large: false, }); +const emit = defineEmits<{ + (_: 'update:user', value: Misskey.entities.UserDetailed): void +}>(); + let isFollowing = $ref(props.user.isFollowing); let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); let wait = $ref(false); @@ -95,6 +100,11 @@ async function onClick() { } else { await os.api('following/create', { userId: props.user.id, + withReplies: defaultStore.state.defaultWithReplies, + }); + emit('update:user', { + ...props.user, + withReplies: defaultStore.state.defaultWithReplies }); hasPendingFollowRequestFromYou = true; diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue index de726e3aa..e384b7a0b 100644 --- a/packages/frontend/src/components/MkInstanceCardMini.vue +++ b/packages/frontend/src/components/MkInstanceCardMini.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only -->