diff --git a/CHANGELOG.md b/CHANGELOG.md
index 652c7ea7c5..1d788e1522 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,8 @@
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
* すべてのリモートユーザーのリアクション一覧を見えないようにします
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
+- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
+ * デフォルトは空欄なので適用前と同等の動作になります
### Client
- Feat: 新しいゲームを追加
@@ -74,6 +76,7 @@
- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
- Fix: MkCodeEditorで行がずれていってしまう問題の修正
+- Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196
### Server
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7f6c1f4f82..ac0a1ba3c1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -286,18 +286,17 @@ export const argTypes = {
min: 1,
max: 4,
},
+ },
};
```
Also, you can use msw to mock API requests in the storybook. Creating a `MyComponent.stories.msw.ts` file to define the mock handlers.
```ts
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
export const handlers = [
- rest.post('/api/notes/timeline', (req, res, ctx) => {
- return res(
- ctx.json([]),
- );
+ http.post('/api/notes/timeline', ({ request }) => {
+ return HttpResponse.json([]);
}),
];
```
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 02e1d3e362..13bbb43f76 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1041,6 +1041,9 @@ resetPasswordConfirm: "Vols canviar la teva contrasenya?"
sensitiveWords: "Paraules sensibles"
sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
+prohibitedWords: "Paraules prohibides"
+prohibitedWordsDescription: "Quan intenteu publicar una Nota que conté una paraula prohibida, feu que es converteixi en un error. Es poden dividir i establir múltiples línies."
+prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
hiddenTags: "Etiquetes ocultes"
hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
notesSearchNotAvailable: "La cerca de notes no es troba disponible."
@@ -1518,12 +1521,82 @@ _achievements:
title: "Nocturn"
description: "Publica una nota a altes hores de la nit "
flavor: "És hora d'anar a dormir."
+ _postedAt0min0sec:
+ title: "Rellotge xerraire"
+ description: "Publica una nota a les 0:00"
+ flavor: "Tic tac, tic tac, tic tac, DING!"
+ _selfQuote:
+ title: "Autoreferència "
+ description: "Cita una nota teva"
+ _htl20npm:
+ title: "Línia de temps fluida"
+ description: "La teva línia de temps va a més de 20npm (notes per minut)"
+ _viewInstanceChart:
+ title: "Analista "
+ description: "Mira els gràfics de la teva instància "
+ _outputHelloWorldOnScratchpad:
+ title: "Hola, món!"
+ description: "Escriu \"hola, món\" al bloc de notes"
_open3windows:
title: "Multi finestres"
description: "I va obrir més de tres finestres"
_driveFolderCircularReference:
title: "Consulteu la secció de bucle"
+ description: "Intenta crear carpetes recursives al Disc"
+ _reactWithoutRead:
+ title: "De veritat has llegit això?"
+ description: "Reaccions a una nota de més de 100 caràcters publicada fa menys de 3 segons "
+ _clickedClickHere:
+ title: "Fer clic"
+ description: "Has fet clic aquí "
+ _justPlainLucky:
+ title: "Ha sigut sort"
+ description: "Oportunitat de guanyar-lo amb una probabilitat d'un 0.005% cada 10 segons"
+ _setNameToSyuilo:
+ title: "soc millor"
+ description: "Posat \"siuylo\" com a nom"
+ _passedSinceAccountCreated1:
+ title: "Primer aniversari"
+ description: "Ja ha passat un any d'ençà que vas crear el teu compte"
+ _passedSinceAccountCreated2:
+ title: "Segon aniversari"
+ description: "Ja han passat dos anys d'ençà que vas crear el teu compte"
+ _passedSinceAccountCreated3:
+ title: "Tres anys"
+ description: "Ja han passat tres anys d'ençà que vas crear el teu compte"
+ _loggedInOnBirthday:
+ title: "Felicitats!"
+ description: "T'has identificat el dia del teu aniversari"
+ _loggedInOnNewYearsDay:
+ title: "Bon any nou!"
+ description: "T'has identificat el primer dia de l'any "
+ flavor: "A per un altre any memorable a la teva instància "
+ _cookieClicked:
+ title: "Un joc en què fas clic a les galetes"
+ description: "Pica galetes"
+ flavor: "Espera, ets al lloc web correcte?"
+ _brainDiver:
+ title: "Busseja Ments"
+ description: "Publica un enllaç al Busseja Ments"
+ flavor: "Misskey-Misskey La-Tu-Ma"
+ _smashTestNotificationButton:
+ title: "Sobrecàrrega de proves"
+ description: "Envia moltes notificacions de prova en un període de temps molt curt"
+ _tutorialCompleted:
+ title: "Diploma del Curs Elemental de Misskey"
+ description: "Has completat el tutorial"
+ _bubbleGameExplodingHead:
+ title: "🤯"
+ description: "L'objecte més gran del joc de la bombolla "
+ _bubbleGameDoubleExplodingHead:
+ title: "Doble 🤯"
+ description: "Dos dels objectes més grans del joc de la bombolla al mateix temps"
+ flavor: "Pots emplenar una carmanyola com aquesta 🤯🤯 una mica"
_role:
+ new: "Nou rol"
+ edit: "Editar el rol"
+ name: "Nom del rol"
+ description: "Descripció del rol"
permission: "Permisos de rol"
descriptionOfPermission: "Els Moderadors poden fer operacions bàsiques de moderació.\nEls Administradors poden canviar tots els ajustos del servidor."
assignTarget: "Assignar "
@@ -1545,35 +1618,259 @@ _role:
asBadge: "Mostrar com a insígnia "
descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol."
isExplorable: "Fer el rol explorable"
+ descriptionOfIsExplorable: "La línia de temps d'aquest rol i la llista d'usuaris seran públics si s'activa."
+ displayOrder: "Posició "
+ descriptionOfDisplayOrder: "Com més gran és el número, més dalt la seva posició a la interfície."
+ canEditMembersByModerator: "Permetre que els moderadors editin la llista d'usuaris en aquest rol"
+ descriptionOfCanEditMembersByModerator: "Quan s'activa, els moderadors, així com els administradors, podran afegir i treure usuaris d'aquest rol. Si es troba desactivat, només els administradors poden assignar usuaris."
priority: "Prioritat"
_priority:
low: "Baixa"
middle: "Mitjà"
high: "Alta"
_options:
+ gtlAvailable: "Pot veure la línia de temps global"
+ ltlAvailable: "Pot veure la línia de temps local"
+ canPublicNote: "Pot enviar notes públiques"
+ canInvite: "Pot crear invitacions a la instància "
+ inviteLimit: "Límit d'invitacions "
+ inviteLimitCycle: "Temps de refresc de les invitacions"
+ inviteExpirationTime: "Interval de caducitat de les invitacions"
canManageCustomEmojis: "Gestiona els emojis personalitzats"
canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
+ driveCapacity: "Capacitat del disc"
+ alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
+ pinMax: "Nombre màxim de notes fixades"
antennaMax: "Nombre màxim d'antenes"
+ wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades"
+ webhookMax: "Nombre màxim de Webhooks"
+ clipMax: "Nombre màxim de clips"
+ noteEachClipsMax: "Nombre màxim de notes dintre d'un clip"
+ userListMax: "Nombre màxim de llistes d'usuaris "
+ userEachUserListsMax: "Nombre màxim d'usuaris dintre d'una llista d'usuaris "
+ rateLimitFactor: "Limitador"
+ descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius."
+ canHideAds: "Pot amagar els anuncis"
+ canSearchNotes: "Pot cercar notes"
+ canUseTranslator: "Pot fer servir el traductor"
+ avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
+ _condition:
+ isLocal: "Usuari local"
+ isRemote: "Usuari remot"
+ createdLessThan: "Han passat menys de X a passat des de la creació del compte"
+ createdMoreThan: "Han passat més de X des de la creació del compte"
+ followersLessThanOrEq: "Té menys de X seguidors"
+ followersMoreThanOrEq: "Té X o més seguidors"
+ followingLessThanOrEq: "Segueix X o menys comptes"
+ followingMoreThanOrEq: "Segueix a X o més comptes"
+ notesLessThanOrEq: "Les publicacions són menys o igual a "
+ notesMoreThanOrEq: "Les publicacions són més o igual a "
+ and: "AND condicional "
+ or: "OR condicional"
+ not: "NOT condicional"
+_sensitiveMediaDetection:
+ description: "Redueix els esforços de moderació gràcies al reconeixement automàtic dels fitxers amb contingut sensible mitjançant Machine Learing. Això augmentarà la càrrega del servidor."
+ sensitivity: "Sensibilitat de la detecció "
+ sensitivityDescription: "Reduint la sensibilitat provocarà menys falsos positius. D'altra banda incrementant-ho generarà més falsos negatius."
+ setSensitiveFlagAutomatically: "Marcar com a sensible"
+ setSensitiveFlagAutomaticallyDescription: "Els resultats de la detecció interna seran desats, inclòs si aquesta opció es troba desactivada."
+ analyzeVideos: "Activar anàlisis de vídeos "
+ analyzeVideosDescription: "Analitzar els vídeos a més de les imatges. Això incrementarà lleugerament la càrrega del servidor."
+_emailUnavailable:
+ used: "Aquest correu electrònic ja s'està fent servir"
+ format: "El format del correu electrònic és invàlid "
+ disposable: "No es poden fer servir adreces de correu electrònic d'un sol ús "
+ mx: "Aquest servidor de correu electrònic no és vàlid "
+ smtp: "Aquest servidor de correu electrònic no respon"
+ banned: "No pots registrar-te amb aquesta adreça de correu electrònic "
_ffVisibility:
public: "Publicar"
+ followers: "Visible només per a seguidors "
+ private: "Privat"
+_signup:
+ almostThere: "Ja quasi estem"
+ emailAddressInfo: "Si us plau, escriu la teva adreça de correu electrònic. No es farà pública."
+ emailSent: "S'ha enviat un correu de confirmació a ({email}). Si us plau, fes clic a l'enllaç per completar el registre."
+_accountDelete:
+ accountDelete: "Eliminar el compte"
+ mayTakeTime: "Com l'eliminació d'un compte consumeix bastants recursos, pot trigar un temps perquè es completi l'esborrat, depenent si tens molt contingut i la quantitat de fitxer que hagis pujat."
+ sendEmail: "Una vegada hagi finalitzat l'esborrat del compte rebràs un correu electrònic a l'adreça que tinguis registrada en aquest compte."
+ requestAccountDelete: "Demanar l'eliminació del compte"
+ started: "Ha començat l'esborrat del compte."
+ inProgress: "L'esborrat es troba en procés "
_ad:
back: "Tornar"
+ reduceFrequencyOfThisAd: "Mostrar menys aquest anunci"
+ hide: "No mostrar mai"
+ timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor."
+ adsSettings: "Configuració d'anuncis "
+ notesPerOneAd: "Interval d'emplaçament d'anuncis en temps real (Notes per anuncis)"
+ setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real"
+ adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix."
+_forgotPassword:
+ enterEmail: "Escriu l'adreça de correu electrònic amb la que et vas registrar. S'enviarà un correu electrònic amb un enllaç perquè puguis canviar-la."
+ ifNoEmail: "Si no vas fer servir una adreça de correu electrònic per registrar-te, si us plau posa't en contacte amb l'administrador."
+ contactAdmin: "Aquesta instància no suporta registrar-se amb correu electrònic. Si us plau, contacta amb l'administrador del servidor."
+_gallery:
+ my: "La meva Galeria "
+ liked: "Publicacions que t'han agradat"
+ like: "M'agrada "
+ unlike: "Ja no m'agrada"
_email:
_follow:
title: "t'ha seguit"
+ _receiveFollowRequest:
+ title: "Has rebut una sol·licitud de seguiment"
+_plugin:
+ install: "Instal·lar un afegit "
+ installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança."
+ manage: "Gestionar els afegits"
+ viewSource: "Veure l'origen "
+_preferencesBackups:
+ list: "Llista de còpies de seguretat"
+ saveNew: "Fer una còpia de seguretat nova"
+ loadFile: "Carregar des d'un fitxer"
+ apply: "Aplicar en aquest dispositiu"
+ save: "Desar els canvis"
+ inputName: "Escriu un nom per aquesta còpia de seguretat"
+ cannotSave: "No s'ha pogut desar"
+ nameAlreadyExists: "Ja existeix una còpia de seguretat anomenada \"{name}\". Escriu un nom diferent."
+ applyConfirm: "Vols aplicar la còpia de seguretat \"{name}\" a aquest dispositiu? La configuració actual del dispositiu serà esborrada."
+ saveConfirm: "Desar còpia de seguretat com {name}?"
+ deleteConfirm: "Esborrar la còpia de seguretat {name}?"
+ renameConfirm: "Vols canvia el nom de la còpia de seguretat de \"{old}\" a \"{new}\"?"
+ noBackups: "No hi ha còpies de seguretat. Pots fer una còpia de seguretat de la configuració d'aquest dispositiu al servidor fent servir \"Crear nova còpia de seguretat\""
+ createdAt: "Creat el: {date} {time}"
+ updatedAt: "Actualitzat el: {date} {time}"
+ cannotLoad: "Hi ha hagut un error al carregar"
+ invalidFile: "Format del fitxer no vàlid "
+_registry:
+ scope: "Àmbit "
+ key: "Clau"
+ keys: "Claus"
+ domain: "Domini"
+ createKey: "Crear una clau"
+_aboutMisskey:
+ about: "Misskey és un programa de codi obert desenvolupar per syuilo des de 2014"
+ contributors: "Col·laboradors principals"
+ allContributors: "Tots els col·laboradors "
+ source: "Codi font"
+ translation: "Tradueix Misskey"
+ donate: "Fes un donatiu a Misskey"
+ morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰"
+ patrons: "Patrocinadors"
+ projectMembers: "Membres del projecte"
+_displayOfSensitiveMedia:
+ respect: "Ocultar imatges o vídeos marcats com a sensibles"
+ ignore: "Mostrar imatges o vídeos marcats com a sensibles"
+ force: "Ocultar totes les imatges o vídeos "
+_instanceTicker:
+ none: "No mostrar mai"
+ remote: "Mostrar per usuaris remots"
+ always: "Mostrar sempre"
+_serverDisconnectedBehavior:
+ reload: "Recarregar automàticament "
+ dialog: "Mostrar finestres de confirmació "
+ quiet: "Mostrar un avís que no molesti"
+_channel:
+ create: "Crear un canal"
+ edit: "Editar canal"
+ setBanner: "Estableix el bàner "
+ removeBanner: "Eliminar el.bàner"
+ featured: "Popular"
+ owned: "Propietat"
+ following: "Seguin"
+ usersCount: "{n} Participants"
+ notesCount: "{n} Notes"
+ nameAndDescription: "Nom i descripció "
+ nameOnly: "Nom només "
+ allowRenoteToExternal: "Permet la citació i l'impuls fora del canal"
_instanceMute:
instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
_theme:
description: "Descripció"
keys:
+ navHoverFg: "Text barra lateral (en passar per sobre)"
+ navActive: "Text barra lateral (actiu)"
+ navIndicator: "Indicador barra lateral"
+ link: "Enllaç"
+ hashtag: "Etiqueta"
mention: "Menció"
+ mentionMe: "Mencions (jo)"
renote: "Renotar"
+ modalBg: "Fons del modal"
divider: "Divisor"
+ scrollbarHandle: "Maneta de la barra de desplaçament"
+ scrollbarHandleHover: "Maneta de la barra de desplaçament (en passar-hi per sobre)"
+ dateLabelFg: "Text de l'etiqueta de la data"
+ infoBg: "Fons d'informació "
+ infoFg: "Text d'informació "
+ infoWarnBg: "Fons avís "
+ infoWarnFg: "Text avís "
+ toastBg: "Fons notificació "
+ toastFg: "Text notificació "
+ buttonBg: "Fons botó "
+ buttonHoverBg: "Fons botó (en passar-hi per sobre)"
+ inputBorder: "Contorn del cap d'introducció "
+ listItemHoverBg: "Fons dels elements d'una llista"
+ driveFolderBg: "Fons de la carpeta Disc"
+ wallpaperOverlay: "Superposició del fons de pantalla "
+ badge: "Insígnia "
+ messageBg: "Fons del xat"
+ accentDarken: "Accent (fosc)"
+ accentLighten: "Accent (clar)"
+ fgHighlighted: "Text ressaltat"
_sfx:
note: "Notes"
+ noteMy: "Nota (per mi)"
notification: "Notificacions"
antenna: "Antenes"
+ channel: "Notificacions dels canals"
+ reaction: "Quan se selecciona una reacció "
+_soundSettings:
+ driveFile: "Fer servir un fitxer d'àudio del disc"
+ driveFileWarn: "Seleccionar un fitxer d'àudio del disc"
+ driveFileTypeWarn: "Fitxer no suportat "
+ driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio "
+ driveFileDurationWarn: "L'àudio és massa llarg"
+ driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?"
+_ago:
+ future: "Futur "
+ justNow: "Ara mateix"
+ secondsAgo: "Fa {n} segons"
+ minutesAgo: "Fa {n} minuts"
+ hoursAgo: "Fa {n} hores"
+ daysAgo: "Fa {n} dies"
+ weeksAgo: "Fa {n} setmanes"
+ monthsAgo: "Fa {n} mesos"
+ yearsAgo: "Fa {n} anys"
+ invalid: "Res"
+_timeIn:
+ seconds: "En {n} segons"
+ minutes: "En {n} minuts"
+ hours: "En {n} hores"
+ days: "En {n} dies"
+ weeks: "En {n} setmanes"
+ months: "En {n} mesos"
+ years: "En {n} anys"
+_time:
+ second: "Segon(s)"
+ minute: "Minut(s)"
+ hour: "Hor(a)(es)"
+ day: "Di(a)(es)"
_2fa:
+ alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor."
+ registerTOTP: "Registrar una aplicació autenticadora"
+ step1: "Primer instal·la una aplicació autenticadora (com {a} o {b}) al teu dispositiu."
+ step2: "Després escaneja el codi QR que es mostra en aquesta pantalla."
+ step2Click: "Fent clic en aquest codi QR et permetrà registrar l'autenticació de doble factor a la teva clau de seguretat o en l'aplicació d'autenticació del teu dispositiu."
+ step2Uri: "Escriu la següent URI si estàs fent servir una aplicació d'escriptori "
+ step3Title: "Escriu un codi d'autenticació"
+ step3: "Escriu el codi d'autenticació (token) que es mostra a la teva aplicació per finalitzar la configuració."
+ setupCompleted: "Configuració terminada"
+ step4: "D'ara endavant quan accedeixis se't demanarà el token que has introduït."
+ securityKeyNotSupported: "El teu navegador no suporta claus de seguretat"
+ removeKeyConfirm: "Esborrar la còpia de seguretat {name}?"
renewTOTPCancel: "No, gràcies"
_antennaSources:
all: "Totes les publicacions"
@@ -1592,6 +1889,8 @@ _widgets:
chooseList: "Tria una llista"
_cw:
show: "Carregar més"
+_poll:
+ deadlineTime: "Hor(a)(es)"
_visibility:
home: "Inici"
followers: "Seguidors"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 241e4cfc7b..6f838d4880 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -1005,6 +1005,7 @@ resetPasswordConfirm: "Opravdu chcete resetovat heslo?"
sensitiveWords: "Citlivá slova"
sensitiveWordsDescription: "Viditelnost všech poznámek obsahujících některé z nakonfigurovaných slov bude automaticky nastavena na \"Domů\". Můžete jich uvést více tak, že je oddělíte pomocí řádků."
sensitiveWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
+prohibitedWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
notesSearchNotAvailable: "Vyhledávání poznámek je nedostupné."
license: "Licence"
unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 909ee5d45c..67178924e3 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1037,6 +1037,7 @@ resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
sensitiveWords: "Sensible Wörter"
sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
+prohibitedWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
hiddenTags: "Ausgeblendete Hashtags"
hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden."
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 4c859861f7..8a32d8307c 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1078,6 +1078,7 @@ resetPasswordConfirm: "Really reset your password?"
sensitiveWords: "Sensitive words"
sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
+prohibitedWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
hiddenTags: "Hidden hashtags"
hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines."
notesSearchNotAvailable: "Note search is unavailable."
@@ -2023,54 +2024,30 @@ _permissions:
"read:flash-likes": "View list of liked Plays"
"write:flash-likes": "Edit list of liked Plays"
"read:admin:abuse-user-reports": "View user reports"
- "write:admin:delete-account": "Delete account"
+ "write:admin:delete-account": "Delete user account"
"write:admin:delete-all-files-of-a-user": "Delete all files of a user"
- "read:admin:index-stats": "View information about database indexes"
- "read:admin:table-stats": "View information about database tables"
- "read:admin:user-ips": "View user IP address"
"read:admin:meta": "View instance metadata"
- "write:admin:reset-password": "Reset user passwords"
- "write:admin:resolve-abuse-user-report": "Resolve user reports"
- "write:admin:send-email": "Send Email"
+ "write:admin:reset-password": "Reset user password"
+ "write:admin:send-email": "Send email"
"read:admin:server-info": "View server info"
"read:admin:show-moderation-log": "View moderation log"
- "read:admin:show-user": "View user information"
- "read:admin:show-users": "View users"
+ "read:admin:show-user": "View private user info"
+ "read:admin:show-users": "View private user info"
"write:admin:suspend-user": "Suspend user"
- "write:admin:unset-user-avatar": "Remove avatar from user"
- "write:admin:unset-user-banner": "Remove banner from user"
+ "write:admin:unset-user-avatar": "Remove user avatar"
+ "write:admin:unset-user-banner": "Remove user banner"
"write:admin:unsuspend-user": "Unsuspend user"
- "write:admin:meta": "Edit instance metadata"
- "write:admin:user-note": "Edit user note"
- "write:admin:roles": "Edit roles"
+ "write:admin:meta": "Manage instance metadata"
+ "write:admin:user-note": "Manage moderation note"
+ "write:admin:roles": "Manage roles"
"read:admin:roles": "View roles"
- "write:admin:relays": "Edit relays"
+ "write:admin:relays": "Manage relays"
"read:admin:relays": "View relays"
- "write:admin:invite-codes": "Edit invite codes"
+ "write:admin:invite-codes": "Manage invite codes"
"read:admin:invite-codes": "View invite codes"
- "write:admin:announcements": "Edit announcements"
+ "write:admin:announcements": "Manage announcements"
"read:admin:announcements": "View announcements"
- "write:admin:avatar-decorations": "Edit avatar decorations"
- "read:admin:avatar-decorations": "View avatar decorations"
- "write:admin:federation": "Edit remote instance information"
- "write:admin:account": "Edit users"
- "read:admin:account": "View information about user"
- "write:admin:emoji": "Edit emojis"
- "read:admin:emoji": "View emojis"
- "write:admin:queue": "Edit queue"
- "read:admin:queue": "View queue"
- "write:admin:promo": "Edit promo"
- "write:admin:drive": "Edit user drive"
- "read:admin:drive": "View user drive"
- "read:admin:stream": "Using the Websocket API for Admin"
- "write:admin:ad": "Edit ads"
- "read:admin:ad": "View ads"
- "write:invite-codes": "Create Invitation Code"
- "read:invite-codes": "View Invitation Code"
- "write:clip-favorite": "Edit clips and likes"
- "read:clip-favorite": "View clips and likes"
- "read:federation": "View information about remote instance"
- "write:report-abuse": "Report abuse"
+ "write:admin:avatar-decorations": "Manage avatar decorations"
_auth:
shareAccessTitle: "Granting application permissions"
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index e8e9761d26..96926dc38d 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
sensitiveWords: "Palabras sensibles"
sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
+prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
hiddenTags: "Hashtags ocultos"
hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
notesSearchNotAvailable: "No se puede buscar una nota"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 4093dee6d3..30a5955ff3 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -1038,6 +1038,7 @@ resetPasswordConfirm: "Yakin untuk mereset kata sandimu?"
sensitiveWords: "Kata sensitif"
sensitiveWordsDescription: "Visibilitas dari semua catatan mengandung kata yang telah diatur akan dijadikan \"Beranda\" secara otomatis. Kamu dapat mendaftarkan kata tersebut lebih dari satu dengan menuliskannya di baris baru."
sensitiveWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler."
+prohibitedWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler."
hiddenTags: "Tagar tersembunyi"
hiddenTagsDescription: "Pilih tanda yang mana akan tidak diperlihatkan dalam daftar tren.\nTanda lebih dari satu dapat didaftarkan dengan tiap baris."
notesSearchNotAvailable: "Pencarian catatan tidak tersedia."
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 686fa57cd5..012cbe055a 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -4329,6 +4329,18 @@ export interface Locale extends ILocale {
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
*/
"sensitiveWordsDescription2": string;
+ /**
+ * 禁止ワード
+ */
+ "prohibitedWords": string;
+ /**
+ * 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
+ */
+ "prohibitedWordsDescription": string;
+ /**
+ * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
+ */
+ "prohibitedWordsDescription2": string;
/**
* 非表示ハッシュタグ
*/
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index db98eaaa24..69720871c9 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -1045,6 +1045,7 @@ resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
sensitiveWords: "Parole esplicite"
sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
sensitiveWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
+prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
hiddenTags: "Hashtag nascosti"
hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga."
notesSearchNotAvailable: "Non è possibile cercare tra le Note."
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index f6879acda1..9714f8f668 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1078,6 +1078,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
sensitiveWords: "センシティブワード"
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
+prohibitedWords: "禁止ワード"
+prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
hiddenTags: "非表示ハッシュタグ"
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
notesSearchNotAvailable: "ノート検索は利用できません。"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 5d5b175e0c..b4787c636f 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1043,6 +1043,7 @@ resetPasswordConfirm: "パスワード作り直すんでええな?"
sensitiveWords: "けったいな単語"
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
hiddenTags: "見えてへんハッシュタグ"
hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。"
notesSearchNotAvailable: "なんかノート探せへん。"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 2a59ab9a23..1231209b36 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
sensitiveWords: "민감한 단어"
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
+prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
hiddenTags: "숨긴 해시태그"
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 60682fe961..d014b7fc25 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1015,6 +1015,7 @@ resetPasswordConfirm: "Сбросить пароль?"
sensitiveWords: "Чувствительные слова"
sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
notesSearchNotAvailable: "Поиск заметок недоступен"
license: "Лицензия"
unfavoriteConfirm: "Удалить избранное?"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index b14d855566..cfba65685b 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุ
sensitiveWords: "คำที่มีเนื้อหาละเอียดอ่อน"
sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
sensitiveWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
+prohibitedWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
hiddenTags: "แฮชแท็กที่ซ่อนอยู่"
hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่"
notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 09c210011c..4a36e30db8 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "确定重置密码?"
sensitiveWords: "敏感词"
sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
+prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
hiddenTags: "隐藏标签"
hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。"
notesSearchNotAvailable: "帖子检索不可用"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 872a90bc6a..ed2bd1cf3a 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "重設密碼?"
sensitiveWords: "敏感詞"
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
sensitiveWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
+prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
hiddenTags: "隱藏標籤"
hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。"
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
diff --git a/package.json b/package.json
index 171adc0da3..fdd23f02c6 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
{
"name": "sharkey",
- "version": "2024.2.0-beta.10",
+ "version": "2024.2.0-beta.11",
"codename": "shonk",
"repository": {
"type": "git",
"url": "https://git.joinsharkey.org/Sharkey/Sharkey.git"
},
- "packageManager": "pnpm@8.12.1",
+ "packageManager": "pnpm@8.15.1",
"workspaces": [
"packages/frontend",
"packages/backend",
diff --git a/packages/backend/migration/1707429690000-prohibited-words.js b/packages/backend/migration/1707429690000-prohibited-words.js
new file mode 100644
index 0000000000..2dd62d8ff8
--- /dev/null
+++ b/packages/backend/migration/1707429690000-prohibited-words.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class prohibitedWords1707429690000 {
+ name = 'prohibitedWords1707429690000'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWords" character varying(1024) array NOT NULL DEFAULT '{}'`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWords"`);
+ }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 296eddfe3c..e5c70d1209 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -84,7 +84,7 @@
"@nestjs/testing": "10.2.10",
"@peertube/http-signature": "1.7.0",
"@transfem-org/sfm-js": "0.24.4",
- "@simplewebauthn/server": "9.0.1",
+ "@simplewebauthn/server": "9.0.2",
"@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.1.10",
"@swc/cli": "0.1.63",
@@ -98,12 +98,12 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
- "bullmq": "5.1.5",
+ "bullmq": "5.1.9",
"cacheable-lookup": "7.0.0",
- "cbor": "9.0.1",
+ "cbor": "9.0.2",
"chalk": "5.3.0",
"chalk-template": "1.1.0",
- "chokidar": "3.5.3",
+ "chokidar": "3.6.0",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
@@ -205,7 +205,7 @@
"@types/jsrsasign": "10.5.12",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
- "@types/node": "20.11.10",
+ "@types/node": "20.11.17",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.14",
"@types/oauth": "0.9.4",
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 5a1fe3d089..1de71212e9 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -408,7 +408,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
*/
@bindThis
public checkDuplicate(name: string): Promise {
- return this.emojisRepository.exist({ where: { name, host: IsNull() } });
+ return this.emojisRepository.exists({ where: { name, host: IsNull() } });
}
@bindThis
diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index 5a2417c9cd..712530108e 100644
--- a/packages/backend/src/core/HashtagService.ts
+++ b/packages/backend/src/core/HashtagService.ts
@@ -163,7 +163,7 @@ export class HashtagService {
const instance = await this.metaService.fetch();
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
if (hiddenTags.includes(hashtag)) return;
- if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
+ if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
// YYYYMMDDHHmm (10分間隔)
const now = new Date();
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 2848be9fe1..34f3736446 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -153,6 +153,8 @@ type Option = {
export class NoteCreateService implements OnApplicationShutdown {
#shutdownController = new AbortController();
+ public static ContainsProhibitedWordsError = class extends Error {};
+
constructor(
@Inject(DI.config)
private config: Config,
@@ -429,13 +431,19 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords;
- if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
+ if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
}
}
+ if (!user.host) {
+ if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
+ throw new NoteCreateService.ContainsProhibitedWordsError();
+ }
+ }
+
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
@@ -795,7 +803,7 @@ export class NoteCreateService implements OnApplicationShutdown {
});
// 通知
if (data.reply.userHost === null) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: data.reply.userId,
threadId: data.reply.threadId ?? data.reply.id,
@@ -830,7 +838,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// Notify
if (data.renote.userHost === null) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: data.renote.userId,
threadId: data.renote.threadId ?? data.renote.id,
@@ -1057,7 +1065,7 @@ export class NoteCreateService implements OnApplicationShutdown {
@bindThis
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: u.id,
threadId: note.threadId ?? note.id,
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index 98762f790e..a6f7edb05c 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -612,7 +612,7 @@ export class NoteEditService implements OnApplicationShutdown {
if (data.reply) {
// 通知
if (data.reply.userHost === null) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: data.reply.userId,
threadId: data.reply.threadId ?? data.reply.id,
@@ -647,7 +647,7 @@ export class NoteEditService implements OnApplicationShutdown {
// Notify
if (data.renote.userHost === null) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: data.renote.userId,
threadId: data.renote.threadId ?? data.renote.id,
@@ -751,7 +751,7 @@ export class NoteEditService implements OnApplicationShutdown {
@bindThis
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: u.id,
threadId: note.threadId ?? note.id,
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index c73cf76592..11791a4412 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -49,7 +49,7 @@ export class NoteReadService implements OnApplicationShutdown {
//#endregion
// スレッドミュート
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: userId,
threadId: note.threadId ?? note.id,
@@ -70,7 +70,7 @@ export class NoteReadService implements OnApplicationShutdown {
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
- const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } });
+ const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } });
if (!exist) return;
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 11c972982e..6cce4a61ea 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -248,7 +248,7 @@ export class ReactionService {
// リアクションされたユーザーがローカルユーザーなら通知を作成
if (note.userHost === null) {
- const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ const isThreadMuted = await this.noteThreadMutingsRepository.exists({
where: {
userId: note.userId,
threadId: note.threadId ?? note.id,
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 6b8c64488f..5941944a20 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -77,12 +77,12 @@ export class SignupService {
const secret = generateUserToken();
// Check username duplication
- if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
+ if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
throw new Error('DUPLICATED_USERNAME');
}
// Check deleted username duplication
- if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
+ if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
throw new Error('USED_USERNAME');
}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 93e9fbbd70..e82a7e06f9 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -144,7 +144,7 @@ export class UserFollowingService implements OnModuleInit {
let autoAccept = false;
// 鍵アカウントであっても、既にフォローされていた場合はスルー
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: followee.id,
@@ -156,7 +156,7 @@ export class UserFollowingService implements OnModuleInit {
// フォローしているユーザーは自動承認オプション
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
- const isFollowed = await this.followingsRepository.exist({
+ const isFollowed = await this.followingsRepository.exists({
where: {
followerId: followee.id,
followeeId: follower.id,
@@ -170,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
if (followee.isLocked && !autoAccept) {
autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
follower,
- (oldSrc, newSrc) => this.followingsRepository.exist({
+ (oldSrc, newSrc) => this.followingsRepository.exists({
where: {
followeeId: followee.id,
followerId: newSrc.id,
@@ -233,7 +233,7 @@ export class UserFollowingService implements OnModuleInit {
this.cacheService.userFollowingsCache.refresh(follower.id);
- const requestExist = await this.followRequestsRepository.exist({
+ const requestExist = await this.followRequestsRepository.exists({
where: {
followeeId: followee.id,
followerId: follower.id,
@@ -531,7 +531,7 @@ export class UserFollowingService implements OnModuleInit {
}
}
- const requestExist = await this.followRequestsRepository.exist({
+ const requestExist = await this.followRequestsRepository.exists({
where: {
followeeId: followee.id,
followerId: follower.id,
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 5dec36c89e..15b98abe63 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -43,13 +43,13 @@ export class UtilityService {
}
@bindThis
- public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
- if (sensitiveWords.length === 0) return false;
+ public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
+ if (keyWords.length === 0) return false;
if (text === '') return false;
const regexpregexp = /^\/(.+)\/(.*)$/;
- const matched = sensitiveWords.some(filter => {
+ const matched = keyWords.some(filter => {
// represents RegExp
const regexp = filter.match(regexpregexp);
// This should never happen due to input sanitisation.
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 3f01c0289a..acbe59b8bd 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -629,7 +629,7 @@ export class ApInboxService {
return 'skip: follower not found';
}
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: actor.id,
@@ -686,14 +686,14 @@ export class ApInboxService {
return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
}
- const requestExist = await this.followRequestsRepository.exist({
+ const requestExist = await this.followRequestsRepository.exists({
where: {
followerId: actor.id,
followeeId: followee.id,
},
});
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followerId: actor.id,
followeeId: followee.id,
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index be3ebbaf96..e07bb8be78 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -345,7 +345,7 @@ export class ApRendererService {
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
if (inReplyToNote != null) {
- const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
+ const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } });
if (inReplyToUserExist) {
if (inReplyToNote.uri) {
@@ -636,7 +636,7 @@ export class ApRendererService {
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
if (inReplyToNote != null) {
- const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
+ const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } });
if (inReplyToUserExist) {
if (inReplyToNote.uri) {
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 305946b8a6..f358875f72 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -51,14 +51,14 @@ export class ChannelEntityService {
const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
- const isFollowing = meId ? await this.channelFollowingsRepository.exist({
+ const isFollowing = meId ? await this.channelFollowingsRepository.exists({
where: {
followerId: meId,
followeeId: channel.id,
},
}) : false;
- const isFavorited = meId ? await this.channelFavoritesRepository.exist({
+ const isFavorited = meId ? await this.channelFavoritesRepository.exists({
where: {
userId: meId,
channelId: channel.id,
diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index 96422894fd..2133f80f1a 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -46,7 +46,7 @@ export class ClipEntityService {
description: clip.description,
isPublic: clip.isPublic,
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
- isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined,
+ isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index 70faa2b380..c1b9f9a791 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -47,7 +47,7 @@ export class FlashEntityService {
summary: flash.summary,
script: flash.script,
likedCount: flash.likedCount,
- isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined,
+ isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts
index d7b960e0d9..2a615a9216 100644
--- a/packages/backend/src/core/entities/GalleryPostEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts
@@ -53,7 +53,7 @@ export class GalleryPostEntityService {
tags: post.tags.length > 0 ? post.tags : undefined,
isSensitive: post.isSensitive,
likedCount: post.likedCount,
- isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined,
+ isLiked: meId ? await this.galleryLikesRepository.exists({ where: { postId: post.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 1cbd5cb70c..a59de4985c 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -114,7 +114,7 @@ export class NoteEntityService implements OnModuleInit {
hide = false;
} else {
if (packedNote.renote) {
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followeeId: packedNote.renote.userId,
followerId: meId,
@@ -124,7 +124,7 @@ export class NoteEntityService implements OnModuleInit {
hide = !isFollowing;
} else {
// フォロワーかどうか
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followeeId: packedNote.userId,
followerId: meId,
diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts
index bc26362aba..27c9011f6d 100644
--- a/packages/backend/src/core/entities/PageEntityService.ts
+++ b/packages/backend/src/core/entities/PageEntityService.ts
@@ -104,7 +104,7 @@ export class PageEntityService {
eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)),
likedCount: page.likedCount,
- isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined,
+ isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 6c1a02d9d8..d24d97ed17 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -153,43 +153,43 @@ export class UserEntityService implements OnModuleInit {
followerId: me,
followeeId: target,
}),
- this.followingsRepository.exist({
+ this.followingsRepository.exists({
where: {
followerId: target,
followeeId: me,
},
}),
- this.followRequestsRepository.exist({
+ this.followRequestsRepository.exists({
where: {
followerId: me,
followeeId: target,
},
}),
- this.followRequestsRepository.exist({
+ this.followRequestsRepository.exists({
where: {
followerId: target,
followeeId: me,
},
}),
- this.blockingsRepository.exist({
+ this.blockingsRepository.exists({
where: {
blockerId: me,
blockeeId: target,
},
}),
- this.blockingsRepository.exist({
+ this.blockingsRepository.exists({
where: {
blockerId: target,
blockeeId: me,
},
}),
- this.mutingsRepository.exist({
+ this.mutingsRepository.exists({
where: {
muterId: me,
muteeId: target,
},
}),
- this.renoteMutingsRepository.exist({
+ this.renoteMutingsRepository.exists({
where: {
muterId: me,
muteeId: target,
@@ -216,7 +216,7 @@ export class UserEntityService implements OnModuleInit {
/*
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
- const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({
+ const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exists({
where: {
antennaId: In(myAntennas.map(x => x.id)),
read: false,
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 9629012c74..149955aa5a 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -76,6 +76,11 @@ export class MiMeta {
})
public sensitiveWords: string[];
+ @Column('varchar', {
+ length: 1024, array: true, default: '{}',
+ })
+ public prohibitedWords: string[];
+
@Column('varchar', {
length: 1024, array: true, default: '{}',
})
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index d2e6185aa3..584853c1f3 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -176,12 +176,12 @@ export class SignupApiService {
}
if (instance.emailRequiredForSignup) {
- if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
+ if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
}
// Check deleted username duplication
- if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
+ if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
throw new FastifyReplyError(400, 'USED_USERNAME');
}
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index c6edd6c9a1..6201d1554d 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -160,6 +160,13 @@ export const meta = {
type: 'string',
},
},
+ prohibitedWords: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ },
+ },
bannedEmailDomains: {
type: 'array',
optional: true, nullable: false,
@@ -549,6 +556,7 @@ export default class extends Endpoint { // eslint-
blockedHosts: instance.blockedHosts,
silencedHosts: instance.silencedHosts,
sensitiveWords: instance.sensitiveWords,
+ prohibitedWords: instance.prohibitedWords,
preservedUsernames: instance.preservedUsernames,
bubbleInstances: instance.bubbleInstances,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index ab69dfba96..339b7a8aa4 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -55,7 +55,7 @@ export default class extends Endpoint { // eslint-
throw e;
});
- const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } });
+ const exist = await this.promoNotesRepository.exists({ where: { noteId: note.id } });
if (exist) {
throw new ApiError(meta.errors.alreadyPromoted);
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 8c0d2f8876..dce0253271 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -41,6 +41,11 @@ export const paramDef = {
type: 'string',
},
},
+ prohibitedWords: {
+ type: 'array', nullable: true, items: {
+ type: 'string',
+ },
+ },
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true },
@@ -185,6 +190,9 @@ export default class extends Endpoint { // eslint-
if (Array.isArray(ps.sensitiveWords)) {
set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
}
+ if (Array.isArray(ps.prohibitedWords)) {
+ set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
+ }
if (Array.isArray(ps.silencedHosts)) {
let lastValue = '';
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index e0baeb3565..602c34b1e6 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -62,7 +62,7 @@ export default class extends Endpoint { // eslint-
const accessToken = secureRndstr(32);
// Fetch exist access token
- const exist = await this.accessTokensRepository.exist({
+ const exist = await this.accessTokensRepository.exists({
where: {
appId: session.appId,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index 1dc4563180..ea7d2076b0 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -88,7 +88,7 @@ export default class extends Endpoint { // eslint-
});
// Check if already blocking
- const exist = await this.blockingsRepository.exist({
+ const exist = await this.blockingsRepository.exists({
where: {
blockerId: blocker.id,
blockeeId: blockee.id,
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index a6e6bcb5b3..b0d66fd05c 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -88,7 +88,7 @@ export default class extends Endpoint { // eslint-
});
// Check not blocking
- const exist = await this.blockingsRepository.exist({
+ const exist = await this.blockingsRepository.exists({
where: {
blockerId: blocker.id,
blockeeId: blockee.id,
diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts
index 015b2cfa85..b4c6a4940b 100644
--- a/packages/backend/src/server/api/endpoints/clips/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts
@@ -62,7 +62,7 @@ export default class extends Endpoint { // eslint-
throw new ApiError(meta.errors.noSuchClip);
}
- const exist = await this.clipFavoritesRepository.exist({
+ const exist = await this.clipFavoritesRepository.exists({
where: {
clipId: clip.id,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
index 85e6312b6a..8c1f491f8d 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
@@ -38,7 +38,7 @@ export default class extends Endpoint { // eslint-
private driveFilesRepository: DriveFilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
- const exist = await this.driveFilesRepository.exist({
+ const exist = await this.driveFilesRepository.exists({
where: {
md5: ps.md5,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts
index 1003249c0c..5878200828 100644
--- a/packages/backend/src/server/api/endpoints/flash/like.ts
+++ b/packages/backend/src/server/api/endpoints/flash/like.ts
@@ -70,7 +70,7 @@ export default class extends Endpoint { // eslint-
}
// if already liked
- const exist = await this.flashLikesRepository.exist({
+ const exist = await this.flashLikesRepository.exists({
where: {
flashId: flash.id,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 9037944ef9..1d0691407d 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -101,7 +101,7 @@ export default class extends Endpoint { // eslint-
});
// Check if already following
- const exist = await this.followingsRepository.exist({
+ const exist = await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: followee.id,
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index f44692ba6d..f761968c90 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -85,7 +85,7 @@ export default class extends Endpoint { // eslint-
});
// Check not following
- const exist = await this.followingsRepository.exist({
+ const exist = await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: followee.id,
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index cc424261b4..576cff4e91 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -72,7 +72,7 @@ export default class extends Endpoint { // eslint-
}
// if already liked
- const exist = await this.galleryLikesRepository.exist({
+ const exist = await this.galleryLikesRepository.exists({
where: {
postId: post.id,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index 71db8710af..771f3395ce 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -71,7 +71,7 @@ export default class extends Endpoint {
private downloadService: DownloadService,
) {
super(meta, paramDef, async (ps, me) => {
- const userExist = await this.usersRepository.exist({ where: { id: me.id } });
+ const userExist = await this.usersRepository.exists({ where: { id: me.id } });
if (!userExist) throw new ApiError(meta.errors.noSuchUser);
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (file === null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index 98d866f867..545d16082c 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -34,7 +34,7 @@ export default class extends Endpoint { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
if (ps.tokenId) {
- const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } });
+ const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } });
if (tokenExist) {
await this.accessTokensRepository.delete({
@@ -43,7 +43,7 @@ export default class extends Endpoint { // eslint-
});
}
} else if (ps.token) {
- const tokenExist = await this.accessTokensRepository.exist({ where: { token: ps.token } });
+ const tokenExist = await this.accessTokensRepository.exists({ where: { token: ps.token } });
if (tokenExist) {
await this.accessTokensRepository.delete({
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 49c2b5707d..1d931150e1 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -83,7 +83,7 @@ export default class extends Endpoint { // eslint-
});
// Check if already muting
- const exist = await this.mutingsRepository.exist({
+ const exist = await this.mutingsRepository.exists({
where: {
muterId: muter.id,
muteeId: mutee.id,
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 44ca1b9c16..dbcea2932e 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -17,6 +17,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import { DI } from '@/di-symbols.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { MetaService } from '@/core/MetaService.js';
+import { UtilityService } from '@/core/UtilityService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -117,6 +119,12 @@ export const meta = {
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
id: '33510210-8452-094c-6227-4a6c05d99f00',
},
+
+ containsProhibitedWords: {
+ message: 'Cannot post because it contains prohibited words.',
+ code: 'CONTAINS_PROHIBITED_WORDS',
+ id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
+ },
},
} as const;
@@ -271,7 +279,7 @@ export default class extends Endpoint { // eslint-
// Check blocking
if (renote.userId !== me.id) {
- const blockExist = await this.blockingsRepository.exist({
+ const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: renote.userId,
blockeeId: me.id,
@@ -319,7 +327,7 @@ export default class extends Endpoint { // eslint-
// Check blocking
if (reply.userId !== me.id) {
- const blockExist = await this.blockingsRepository.exist({
+ const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: reply.userId,
blockeeId: me.id,
@@ -351,31 +359,40 @@ export default class extends Endpoint { // eslint-
}
// 投稿を作成
- const note = await this.noteCreateService.create(me, {
- createdAt: new Date(),
- files: files,
- poll: ps.poll ? {
- choices: ps.poll.choices,
- multiple: ps.poll.multiple ?? false,
- expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
- } : undefined,
- text: ps.text ?? undefined,
- reply,
- renote,
- cw: ps.cw,
- localOnly: ps.localOnly,
- reactionAcceptance: ps.reactionAcceptance,
- visibility: ps.visibility,
- visibleUsers,
- channel,
- apMentions: ps.noExtractMentions ? [] : undefined,
- apHashtags: ps.noExtractHashtags ? [] : undefined,
- apEmojis: ps.noExtractEmojis ? [] : undefined,
- });
+ try {
+ const note = await this.noteCreateService.create(me, {
+ createdAt: new Date(),
+ files: files,
+ poll: ps.poll ? {
+ choices: ps.poll.choices,
+ multiple: ps.poll.multiple ?? false,
+ expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
+ } : undefined,
+ text: ps.text ?? undefined,
+ reply,
+ renote,
+ cw: ps.cw,
+ localOnly: ps.localOnly,
+ reactionAcceptance: ps.reactionAcceptance,
+ visibility: ps.visibility,
+ visibleUsers,
+ channel,
+ apMentions: ps.noExtractMentions ? [] : undefined,
+ apHashtags: ps.noExtractHashtags ? [] : undefined,
+ apEmojis: ps.noExtractEmojis ? [] : undefined,
+ });
- return {
- createdNote: await this.noteEntityService.pack(note, me),
- };
+ return {
+ createdNote: await this.noteEntityService.pack(note, me),
+ };
+ } catch (e) {
+ // TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
+ if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
+ throw new ApiError(meta.errors.containsProhibitedWords);
+ }
+
+ throw e;
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 0c9c0d3baf..44796d9290 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -311,7 +311,7 @@ export default class extends Endpoint { // eslint-
// Check blocking
if (renote.userId !== me.id) {
- const blockExist = await this.blockingsRepository.exist({
+ const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: renote.userId,
blockeeId: me.id,
@@ -349,7 +349,7 @@ export default class extends Endpoint { // eslint-
// Check blocking
if (reply.userId !== me.id) {
- const blockExist = await this.blockingsRepository.exist({
+ const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: reply.userId,
blockeeId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index ed3dce7f35..bfa621aa38 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -67,7 +67,7 @@ export default class extends Endpoint { // eslint-
});
// if already favorited
- const exist = await this.noteFavoritesRepository.exist({
+ const exist = await this.noteFavoritesRepository.exists({
where: {
noteId: note.id,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index 8c18982b50..bee60f080d 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -70,7 +70,7 @@ export default class extends Endpoint { // eslint-
}
// if already liked
- const exist = await this.pageLikesRepository.exist({
+ const exist = await this.pageLikesRepository.exists({
where: {
pageId: page.id,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index f427939a7a..4899408ddd 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -49,7 +49,7 @@ export default class extends Endpoint { // eslint-
throw err;
});
- const exist = await this.promoReadsRepository.exist({
+ const exist = await this.promoReadsRepository.exists({
where: {
noteId: note.id,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 5706e46b96..314a45ed61 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -101,7 +101,7 @@ export default class extends Endpoint { // eslint-
if (me == null) {
throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) {
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followeeId: user.id,
followerId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 794fb04f10..86f55c5a12 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -109,7 +109,7 @@ export default class extends Endpoint { // eslint-
if (me == null) {
throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) {
- const isFollowing = await this.followingsRepository.exist({
+ const isFollowing = await this.followingsRepository.exists({
where: {
followeeId: user.id,
followerId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
index fa2e3338b8..dd9b459a1f 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -90,7 +90,7 @@ export default class extends Endpoint { // eslint-
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
- const listExist = await this.userListsRepository.exist({
+ const listExist = await this.userListsRepository.exists({
where: {
id: ps.listId,
isPublic: true,
@@ -121,7 +121,7 @@ export default class extends Endpoint { // eslint-
});
if (currentUser.id !== me.id) {
- const blockExist = await this.blockingsRepository.exist({
+ const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: currentUser.id,
blockeeId: me.id,
@@ -132,7 +132,7 @@ export default class extends Endpoint { // eslint-
}
}
- const exist = await this.userListMembershipsRepository.exist({
+ const exist = await this.userListMembershipsRepository.exists({
where: {
userListId: userList.id,
userId: currentUser.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
index 864cdc2ee0..e5b3a73b55 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
@@ -47,7 +47,7 @@ export default class extends Endpoint {
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
- const userListExist = await this.userListsRepository.exist({
+ const userListExist = await this.userListsRepository.exists({
where: {
id: ps.listId,
isPublic: true,
@@ -58,7 +58,7 @@ export default class extends Endpoint {
throw new ApiError(meta.errors.noSuchList);
}
- const exist = await this.userListFavoritesRepository.exist({
+ const exist = await this.userListFavoritesRepository.exists({
where: {
userId: me.id,
userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index c4ceec575b..0984270943 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -104,7 +104,7 @@ export default class extends Endpoint { // eslint-
// Check blocking
if (user.id !== me.id) {
- const blockExist = await this.blockingsRepository.exist({
+ const blockExist = await this.blockingsRepository.exists({
where: {
blockerId: user.id,
blockeeId: me.id,
@@ -115,7 +115,7 @@ export default class extends Endpoint { // eslint-
}
}
- const exist = await this.userListMembershipsRepository.exist({
+ const exist = await this.userListMembershipsRepository.exists({
where: {
userListId: userList.id,
userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index df44870b04..10efbaafd8 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -74,7 +74,7 @@ export default class extends Endpoint {
userListId: ps.listId,
});
if (me !== null) {
- additionalProperties.isLiked = await this.userListFavoritesRepository.exist({
+ additionalProperties.isLiked = await this.userListFavoritesRepository.exists({
where: {
userId: me.id,
userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
index d51d57343e..0935584cbc 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
@@ -45,7 +45,7 @@ export default class extends Endpoint {
private userListFavoritesRepository: UserListFavoritesRepository,
) {
super(meta, paramDef, async (ps, me) => {
- const userListExist = await this.userListsRepository.exist({
+ const userListExist = await this.userListsRepository.exists({
where: {
id: ps.listId,
isPublic: true,
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index e0245814c4..0434833df8 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -43,7 +43,7 @@ class UserListChannel extends Channel {
this.withRenotes = params.withRenotes ?? true;
// Check existence and owner
- const listExist = await this.userListsRepository.exist({
+ const listExist = await this.userListsRepository.exists({
where: {
id: this.listId,
userId: this.user!.id,
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 0280b051f5..1bc8cb591c 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -16,12 +16,14 @@ describe('Note', () => {
let alice: misskey.entities.SignupResponse;
let bob: misskey.entities.SignupResponse;
+ let tom: misskey.entities.SignupResponse;
beforeAll(async () => {
const connection = await initTestDb(true);
Notes = connection.getRepository(MiNote);
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
+ tom = await signup({ username: 'tom', host: 'example.com' });
}, 1000 * 60 * 2);
test('投稿できる', async () => {
@@ -607,6 +609,77 @@ describe('Note', () => {
assert.strictEqual(note2.status, 200);
assert.strictEqual(note2.body.createdNote.visibility, 'home');
});
+
+ test('禁止ワードを含む投稿はエラーになる (単語指定)', async () => {
+ const prohibited = await api('admin/update-meta', {
+ prohibitedWords: [
+ 'test',
+ ],
+ }, alice);
+
+ assert.strictEqual(prohibited.status, 204);
+
+ await new Promise(x => setTimeout(x, 2));
+
+ const note1 = await api('/notes/create', {
+ text: 'hogetesthuge',
+ }, alice);
+
+ assert.strictEqual(note1.status, 400);
+ assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+ });
+
+ test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
+ const prohibited = await api('admin/update-meta', {
+ prohibitedWords: [
+ '/Test/i',
+ ],
+ }, alice);
+
+ assert.strictEqual(prohibited.status, 204);
+
+ const note2 = await api('/notes/create', {
+ text: 'hogetesthuge',
+ }, alice);
+
+ assert.strictEqual(note2.status, 400);
+ assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+ });
+
+ test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
+ const prohibited = await api('admin/update-meta', {
+ prohibitedWords: [
+ 'Test hoge',
+ ],
+ }, alice);
+
+ assert.strictEqual(prohibited.status, 204);
+
+ const note2 = await api('/notes/create', {
+ text: 'hogeTesthuge',
+ }, alice);
+
+ assert.strictEqual(note2.status, 400);
+ assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+ });
+
+ test('禁止ワードを含んでいてもリモートノートはエラーにならない', async () => {
+ const prohibited = await api('admin/update-meta', {
+ prohibitedWords: [
+ 'test',
+ ],
+ }, alice);
+
+ assert.strictEqual(prohibited.status, 204);
+
+ await new Promise(x => setTimeout(x, 2));
+
+ const note1 = await api('/notes/create', {
+ text: 'hogetesthuge',
+ }, tom);
+
+ assert.strictEqual(note1.status, 200);
+ });
});
describe('notes/delete', () => {
diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts
index 80e5157c5a..f0feff9f78 100644
--- a/packages/frontend/.storybook/mocks.ts
+++ b/packages/frontend/.storybook/mocks.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { type SharedOptions, rest } from 'msw';
+import { type SharedOptions, http, HttpResponse } from 'msw';
export const onUnhandledRequest = ((req, print) => {
if (req.url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(req.url.pathname)) {
@@ -13,19 +13,31 @@ export const onUnhandledRequest = ((req, print) => {
}) satisfies SharedOptions['onUnhandledRequest'];
export const commonHandlers = [
- rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => {
- const { codepoints } = req.params;
+ http.get('/fluent-emoji/:codepoints.png', async ({ params }) => {
+ const { codepoints } = params;
const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
- return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+ return new HttpResponse(value, {
+ headers: {
+ 'Content-Type': 'image/png',
+ },
+ });
}),
- rest.get('/fluent-emojis/:codepoints.png', async (req, res, ctx) => {
- const { codepoints } = req.params;
+ http.get('/fluent-emojis/:codepoints.png', async ({ params }) => {
+ const { codepoints } = params;
const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
- return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+ return new HttpResponse(value, {
+ headers: {
+ 'Content-Type': 'image/png',
+ },
+ });
}),
- rest.get('/twemoji/:codepoints.svg', async (req, res, ctx) => {
- const { codepoints } = req.params;
+ http.get('/twemoji/:codepoints.svg', async ({ params }) => {
+ const { codepoints } = params;
const value = await fetch(`https://unpkg.com/@discordapp/twemoji@15.0.2/dist/svg/${codepoints}.svg`).then((response) => response.blob());
- return res(ctx.set('Content-Type', 'image/svg+xml'), ctx.body(value));
+ return new HttpResponse(value, {
+ headers: {
+ 'Content-Type': 'image/svg+xml',
+ },
+ });
}),
];
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 707c15f5c7..22553dc687 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -29,8 +29,8 @@
"@phosphor-icons/web": "^2.0.3",
"@twemoji/parser": "15.0.0",
"@vitejs/plugin-vue": "5.0.3",
- "@vue/compiler-sfc": "3.4.15",
- "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
+ "@vue/compiler-sfc": "3.4.18",
+ "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
"astring": "1.8.6",
"broadcast-channel": "7.0.0",
"buraha": "0.0.1",
@@ -72,8 +72,8 @@
"typescript": "5.3.3",
"uuid": "9.0.1",
"v-code-diff": "1.7.2",
- "vite": "5.0.12",
- "vue": "3.4.15",
+ "vite": "5.1.0",
+ "vue": "3.4.18",
"vuedraggable": "next"
},
"devDependencies": {
@@ -97,12 +97,12 @@
"@storybook/types": "7.6.10",
"@storybook/vue3": "7.6.10",
"@storybook/vue3-vite": "7.6.10",
- "@testing-library/vue": "8.0.1",
+ "@testing-library/vue": "8.0.2",
"@types/escape-regexp": "0.0.3",
"@types/estree": "1.0.5",
"@types/matter-js": "0.19.6",
"@types/micromatch": "4.0.6",
- "@types/node": "20.11.10",
+ "@types/node": "20.11.17",
"@types/punycode": "2.1.3",
"@types/sanitize-html": "2.9.5",
"@types/throttle-debounce": "5.0.2",
@@ -112,10 +112,10 @@
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"@vitest/coverage-v8": "0.34.6",
- "@vue/runtime-core": "3.4.15",
+ "@vue/runtime-core": "3.4.18",
"acorn": "8.11.3",
"cross-env": "7.0.3",
- "cypress": "13.6.3",
+ "cypress": "13.6.4",
"eslint": "8.56.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-vue": "9.20.1",
@@ -123,10 +123,10 @@
"happy-dom": "10.0.3",
"intersection-observer": "0.12.2",
"micromatch": "4.0.5",
- "msw": "2.1.2",
- "msw-storybook-addon": "1.10.0",
+ "msw": "2.1.7",
+ "msw-storybook-addon": "2.0.0-beta.1",
"nodemon": "3.0.3",
- "prettier": "3.2.4",
+ "prettier": "3.2.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"start-server-and-test": "2.0.3",
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index f6a3e40305..65bd7cf0f1 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -60,12 +60,6 @@ export async function common(createVue: () => App) {
});
}
- const splash = document.getElementById('splash');
- // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
- if (splash) splash.addEventListener('transitionend', () => {
- splash.remove();
- });
-
let isClientUpdated = false;
//#region クライアントが更新されたかチェック
@@ -293,5 +287,10 @@ function removeSplash() {
if (splash) {
splash.style.opacity = '0';
splash.style.pointerEvents = 'none';
+
+ // transitionendイベントが発火しない場合があるため
+ window.setTimeout(() => {
+ splash.remove();
+ }, 1000);
}
}
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
index 77e7c84d5c..dc2697f25c 100644
--- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
import { abuseUserReport } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAbuseReport from './MkAbuseReport.vue';
@@ -44,9 +44,9 @@ export const Default = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => {
- action('POST /api/admin/resolve-abuse-user-report')(await req.json());
- return res(ctx.json({}));
+ http.post('/api/admin/resolve-abuse-user-report', async ({ request }) => {
+ action('POST /api/admin/resolve-abuse-user-report')(await request.json());
+ return HttpResponse.json({});
}),
],
},
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
index dc842b3d1b..771452cb5f 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
@@ -44,9 +44,9 @@ export const Default = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/users/report-abuse', async (req, res, ctx) => {
- action('POST /api/users/report-abuse')(await req.json());
- return res(ctx.json({}));
+ http.post('/api/users/report-abuse', async ({ request }) => {
+ action('POST /api/users/report-abuse')(await request.json());
+ return HttpResponse.json({});
}),
],
},
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
index 6d972467b1..81e9529de2 100644
--- a/packages/frontend/src/components/MkAchievements.stories.impl.ts
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAchievements from './MkAchievements.vue';
@@ -39,8 +39,8 @@ export const Empty = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/users/achievements', (req, res, ctx) => {
- return res(ctx.json([]));
+ http.post('/api/users/achievements', () => {
+ return HttpResponse.json([]);
}),
],
},
@@ -52,8 +52,8 @@ export const All = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/users/achievements', (req, res, ctx) => {
- return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))));
+ http.post('/api/users/achievements', () => {
+ return HttpResponse.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })));
}),
],
},
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
index 969519386f..3ca8c5b864 100644
--- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions';
import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAutocomplete from './MkAutocomplete.vue';
@@ -99,11 +99,11 @@ export const User = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => {
- return res(ctx.json([
+ http.post('/api/users/search-by-username-and-host', () => {
+ return HttpResponse.json([
userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
- ]));
+ ]);
}),
],
},
@@ -132,12 +132,12 @@ export const Hashtag = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/hashtags/search', (req, res, ctx) => {
- return res(ctx.json([
+ http.post('/api/hashtags/search', () => {
+ return HttpResponse.json([
'気象警報注意報',
'気象警報',
'気象情報',
- ]));
+ ]);
}),
],
},
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
index d41b64695f..a9b4540ca9 100644
--- a/packages/frontend/src/components/MkAvatars.stories.impl.ts
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAvatars from './MkAvatars.vue';
@@ -38,12 +38,12 @@ export const Default = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/users/show', (req, res, ctx) => {
- return res(ctx.json([
+ http.post('/api/users/show', () => {
+ return HttpResponse.json([
userDetailed('17'),
userDetailed('20'),
userDetailed('18'),
- ]));
+ ]);
}),
],
},
diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
index 2ea32dd3b6..2abe1a8770 100644
--- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkInviteCode from './MkInviteCode.vue';
@@ -39,8 +39,8 @@ export const Default = {
msw: {
handlers: [
...commonHandlers,
- rest.post('/api/users/show', (req, res, ctx) => {
- return res(ctx.json(userDetailed(req.params.userId as string)));
+ http.post('/api/users/show', ({ params }) => {
+ return HttpResponse.json(userDetailed(params.userId as string));
}),
],
},
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 95bbe71f96..e998b081ee 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -19,7 +19,6 @@ SPDX-License-Identifier: AGPL-3.0-only