Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	locales/index.d.ts
#	locales/ja-JP.yml
#	package.json
#	packages/backend/src/core/entities/NoteEntityService.ts
#	packages/backend/src/models/Note.ts
#	packages/backend/src/server/api/endpoints/roles/notes.ts
#	packages/backend/src/server/api/stream/channels/global-timeline.ts
#	packages/backend/src/server/api/stream/channels/home-timeline.ts
#	packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
#	packages/backend/src/server/api/stream/channels/local-timeline.ts
#	packages/frontend/src/components/MkFollowButton.vue
#	packages/frontend/src/pages/settings/general.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/frontend/src/pages/user/home.vue
#	packages/frontend/src/store.ts
This commit is contained in:
mattyatea 2023-10-18 21:22:51 +09:00
commit eacb74cb44
246 changed files with 2381 additions and 1867 deletions

View File

@ -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"

View File

@ -12,6 +12,26 @@
-->
## 2023.x.x (unreleased)
### General
- Feat: アンテナでローカルの投稿のみ収集できるようになりました
- Feat: サーバーサイレンス機能が追加されました
- Enhance: 新規にフォローした人の返信をデフォルトでTLに追加できるオプションを追加
- Enhance: HTLとLTLを2023.10.0アップデート以前まで遡れるように
- Enhance: フォロー/フォロー解除したときに過去分のHTLにも含まれる投稿が反映されるように
- Enhance: ローカリゼーションの更新
- Enhance: 依存関係の更新
### Client
- Enhance: TLの返信表示オプションを記憶するように
### Server
- Enhance: ストリーミングAPIのパフォーマンスを向上
- Fix: users/notesでDBから参照した際にチャンネル投稿のみ取得される問題を修正
- Fix: コントロールパネルの設定項目が正しく保存できない問題を修正
- Change: nyaizeはAPIレスポンス時ではなく投稿時に一度だけ非可逆的に行われるようになりました
## 2023.10.1
### General
- Enhance: ローカルタイムライン、ソーシャルタイムラインで返信を含むかどうか設定可能に
@ -27,6 +47,7 @@
### NOTE
- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
- アップデートを行うと、タイムラインが一時的にリセットされます
- アンテナ内のノートも含む
- ソフトミュート設定はクライアントではなくサーバー側に保存されるようになったため、アップデートを行うとソフトミュートの設定がリセットされます
### Changes

View File

@ -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"

View File

@ -197,6 +197,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"
@ -215,7 +216,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"
@ -535,6 +538,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"
@ -798,7 +802,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"
@ -1133,8 +1137,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"
@ -1747,7 +1751,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."
@ -1930,6 +1934,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"
@ -2083,7 +2088,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:

View File

@ -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."

View File

@ -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 lenvoi de lactivité"
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 ?"

5
locales/index.d.ts vendored
View File

@ -207,6 +207,7 @@ export interface Locale {
"perDay": string;
"stopActivityDelivery": string;
"blockThisInstance": string;
"silenceThisInstance": string;
"operations": string;
"software": string;
"version": string;
@ -226,6 +227,8 @@ export interface Locale {
"clearCachedFilesConfirm": string;
"blockedInstances": string;
"blockedInstancesDescription": string;
"silencedInstances": string;
"silencedInstancesDescription": string;
"muteAndBlock": string;
"mutedUsers": string;
"blockedUsers": string;
@ -549,6 +552,7 @@ export interface Locale {
"showFixedPostFormInChannel": string;
"FeaturedOrNote": string;
"FeaturedOrNoteInfo": string;
"withRepliesByDefaultForNewlyFollowed": string;
"newNoteRecived": string;
"sounds": string;
"sound": string;
@ -2080,6 +2084,7 @@ export interface Locale {
"userLists": string;
"excludeMutingUsers": string;
"excludeInactiveUsers": string;
"withReplies": string;
};
"_charts": {
"federation": string;

View File

@ -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."

View File

@ -204,6 +204,7 @@ perHour: "1時間ごと"
perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送を停止"
blockThisInstance: "このサーバーをブロック"
silenceThisInstance: "サーバーをサイレンス"
operations: "操作"
software: "ソフトウェア"
version: "バージョン"
@ -222,7 +223,9 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。
clearCachedFiles: "キャッシュをクリア"
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このサーバーとやり取りできなくなります。サブドメインもブロックされます。"
blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。"
silencedInstances: "サイレンスしたサーバー"
silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー"
@ -546,6 +549,7 @@ showFixedPostForm: "タイムライン上部に投稿フォームを表示する
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
FeaturedOrNote: "ユーザーのページで最新のノートを表示する"
FeaturedOrNoteInfo: "ユーザーのページに行ったときにハイライトか最新のノートを表示するかを選択することができます。 オフでハイライト オンで最新のノート です"
withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする"
newNoteRecived: "新しいノートがあります"
sounds: "サウンド"
sound: "サウンド"
@ -604,7 +608,7 @@ poll: "アンケート"
useCw: "内容を隠す"
enablePlayer: "プレイヤーを開く"
disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する"
expandTweet: "ポストを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "キャプションを付ける"
@ -813,7 +817,7 @@ active: "アクティブ"
offline: "オフライン"
notRecommended: "非推奨"
botProtection: "Botプロテクション"
instanceBlocking: "サーバーブロック"
instanceBlocking: "サーバーブロック・サイレンス"
selectAccount: "アカウントを選択"
switchAccount: "アカウントを切り替え"
enabled: "有効"
@ -1995,6 +1999,7 @@ _exportOrImport:
userLists: "リスト"
excludeMutingUsers: "ミュートしているユーザーを除外"
excludeInactiveUsers: "使われていないアカウントを除外"
withReplies: "インポートした人による返信をTLに含むようにする"
_charts:
federation: "連合"

View File

@ -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: "หน้าเพจนี้จะสามารถปรากฏได้โดยผู้ใช้ที่อัปโหลดไฟล์นี้เท่านั้น"

View File

@ -1125,8 +1125,8 @@ showRenotes: "顯示轉發貼文"
edited: "已編輯"
notificationRecieveConfig: "接受通知的設定"
mutualFollow: "互相追隨"
fileAttachedOnly: "包含附件"
showRepliesToOthersInTimeline: "在時間軸上顯示給其他人的回覆"
fileAttachedOnly: "顯示包含附件的貼文"
showRepliesToOthersInTimeline: "顯示給其他人的回覆"
hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
externalServices: "外部服務"
impressum: "營運者資訊"

View File

@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2023.10.1-prismisskey.2",
"version": "2023.10.2-beta.1-prismisskey.1",
"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",
@ -52,10 +52,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"
},

View File

@ -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"`);
}
}

View File

@ -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") `);
}
}

View File

@ -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"`);
}
}

View File

@ -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"`);
}
}

View File

@ -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",

View File

@ -180,13 +180,13 @@ export class AccountMoveService {
{ muteeId: dst.id, expiresAt: IsNull() },
).then(mutings => mutings.map(muting => muting.muterId));
const newMutings: Map<string, { muterId: string; muteeId: string; createdAt: Date; expiresAt: Date | null; }> = new Map();
const newMutings: Map<string, { muterId: string; muteeId: string; expiresAt: Date | null; }> = 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<string, { createdAt: Date; userId: string; userListId: string; userListUserId: string; }> = new Map();
const newMemberships: Map<string, { userId: string; userListId: string; userListUserId: string; }> = 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,

View File

@ -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<MiAnnouncement>, 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<void> {
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,

View File

@ -16,7 +16,7 @@ import type { AntennasRepository, UserListMembershipsRepository } from '@/models
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { 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') {

View File

@ -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,
});

View File

@ -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,

View File

@ -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,

View File

@ -69,7 +69,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
}, moderator?: MiUser): Promise<MiEmoji> {
const emoji = await this.emojisRepository.insert({
id: this.idService.genId(),
id: this.idService.gen(),
updatedAt: new Date(),
name: data.name,
category: data.category,
@ -360,7 +360,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);

View File

@ -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;

View File

@ -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]));

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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');
}
}

View File

@ -24,8 +24,7 @@ export class ModerationLogService {
@bindThis
public async log<T extends typeof moderationLogTypes[number]>(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) ?? {},

View File

@ -54,7 +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';
@ -195,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,
@ -214,6 +216,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart,
private utilityService: UtilityService,
) { }
@bindThis
@ -221,9 +224,11 @@ 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<MiNote> {
let patsedText: mfm.MfmNode[] | null = null;
// チャンネル外にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
@ -247,8 +252,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) {
@ -256,6 +263,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':
@ -302,6 +315,25 @@ 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 = 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;
}
@ -312,7 +344,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// Parse MFM if needed
if (!tags || !emojis || !mentionedUsers) {
const tokens = data.text ? mfm.parse(data.text)! : [];
const tokens = patsedText ?? (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)!))
@ -327,7 +359,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 }));
@ -360,8 +392,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,
@ -460,7 +491,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();
@ -554,7 +584,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 });
this.globalEventService.publishNotesStream(noteObj);
@ -821,9 +851,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: {
@ -833,9 +863,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 {
@ -873,9 +903,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);
}
}
@ -891,36 +921,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);
}
}
}

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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');

View File

@ -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,

View File

@ -148,13 +148,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,

View File

@ -54,7 +54,7 @@ export class RelayService {
@bindThis
public async addRelay(inbox: string): Promise<MiRelay> {
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]));

View File

@ -20,7 +20,7 @@ import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js';
import { RedisTimelineService } from '@/core/RedisTimelineService.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type RolePolicies = {
@ -105,7 +105,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);
@ -127,7 +127,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),
});
@ -141,7 +140,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),
};
@ -161,7 +159,6 @@ export class RoleService implements OnApplicationShutdown {
if (cached) {
cached.push({
...body,
createdAt: new Date(body.createdAt),
expiresAt: body.expiresAt ? new Date(body.expiresAt) : null,
});
}
@ -200,10 +197,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;
@ -385,7 +382,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis
public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise<void> {
const now = new Date();
const now = Date.now();
const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
@ -395,7 +392,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,
@ -406,8 +403,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,
@ -477,7 +473,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);
}
@ -488,8 +484,7 @@ export class RoleService implements OnApplicationShutdown {
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
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,

View File

@ -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,

View File

@ -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),

View File

@ -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,

View File

@ -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<void> {
public async follow(
_follower: { id: MiUser['id'] },
_followee: { id: MiUser['id'] },
{ requestId, silent = false, withReplies }: {
requestId?: string,
silent?: boolean,
withReplies?: boolean,
} = {},
): Promise<void> {
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<void> {
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<void> {
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();
}
}

View File

@ -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,

View File

@ -26,8 +26,7 @@ export class UserMutingService {
@bindThis
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
await this.mutingsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
id: this.idService.gen(),
expiresAt: expiresAt ?? null,
muterId: user.id,
muteeId: target.id,

View File

@ -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);

View File

@ -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,
});
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,
});

View File

@ -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<typeof schema> { // 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<typeof schema> { // eslint-d
}
@bindThis
public async read(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise<void> {
public async read(user: { id: MiUser['id'], host: null }): Promise<void> {
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<void> {
public async write(user: { id: MiUser['id'], host: null }): Promise<void> {
await this.commit({
'write': [user.id],
});

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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),

View File

@ -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<Packed<'DriveFile'>>({
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<Packed<'DriveFile'>>({
id: file.id,
createdAt: file.createdAt.toISOString(),
createdAt: this.idService.parse(file.id).date.toISOString(),
name: file.name,
type: file.type,
md5: file.md5,

View File

@ -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,

View File

@ -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 } すると無限ループするので注意

View File

@ -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, {

View File

@ -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),

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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, {

View File

@ -5,11 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import * as mfm from 'mfm-js';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
import { nyaize } from '@/misc/nyaize.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
@ -18,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';
@ -30,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(
@ -68,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
@ -169,11 +170,11 @@ export class NoteEntityService implements OnModuleInit {
}
@bindThis
private async populateMyReaction(note: MiNote, meId: MiUser['id'], _hint_?: {
public async populateMyReaction(noteId: MiNote['id'], meId: MiUser['id'], _hint_?: {
myReactions: Map<MiNote['id'], MiNoteReaction | null>;
}) {
if (_hint_?.myReactions) {
const reaction = _hint_.myReactions.get(note.id);
const reaction = _hint_.myReactions.get(noteId);
if (reaction) {
return this.reactionService.convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
@ -182,14 +183,14 @@ export class NoteEntityService implements OnModuleInit {
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
// パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない
if (note.createdAt.getTime() + 1000 > Date.now()) {
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
if (this.idService.parse(noteId).date.getTime() + 2000 > Date.now()) {
return undefined;
}
const reaction = await this.noteReactionsRepository.findOneBy({
userId: meId,
noteId: note.id,
noteId: noteId,
});
if (reaction) {
@ -309,7 +310,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(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me, {
@ -318,7 +319,7 @@ 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,
@ -358,30 +359,11 @@ export class NoteEntityService implements OnModuleInit {
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
...(meId ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),
myReaction: this.populateMyReaction(note.id, meId, options?._hint_),
} : {}),
} : {}),
});
if (packed.user.isCat && packed.text) {
const tokens = packed.text ? mfm.parse(packed.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 tokens) {
nyaizeNode(node);
}
packed.text = mfm.toString(tokens);
}
if (!opts.skipHide) {
await this.hideNote(packed, meId);
}
@ -404,8 +386,9 @@ export class NoteEntityService implements OnModuleInit {
const myReactionsMap = new Map<MiNote['id'], MiNoteReaction | null>();
if (meId) {
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
// パフォーマンスのためートが作成されてから1秒以上経っていない場合はリアクションを取得しない
const targets = [...notes.filter(n => n.createdAt.getTime() + 1000 < Date.now()).map(n => n.id), ...renoteIds];
// パフォーマンスのためートが作成されてから2秒以上経っていない場合はリアクションを取得しない
const oldId = this.idService.gen(Date.now() - 2000);
const targets = [...notes.filter(n => n.id < oldId).map(n => n.id), ...renoteIds];
const myReactions = await this.noteReactionsRepository.findBy({
userId: meId,
noteId: In(targets),

View File

@ -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),
};

View File

@ -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 ? {

View File

@ -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 } すると無限ループするので注意

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 }));
}

View File

@ -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();

View File

@ -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();

View File

@ -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; } {

View File

@ -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; } {

View File

@ -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; } {

View File

@ -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'];

View File

@ -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,
})

View File

@ -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.',

View File

@ -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,

View File

@ -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'];

View File

@ -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;
}

View File

@ -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(),

View File

@ -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,

View File

@ -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(),

View File

@ -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,

View File

@ -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(),

View File

@ -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(),

View File

@ -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,

View File

@ -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'];

View File

@ -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(),

View File

@ -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.',

View File

@ -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.',

View File

@ -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'];

View File

@ -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,

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