mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-08 22:33:09 +02:00
merge: latest changes
This commit is contained in:
commit
85355813ad
91 changed files with 1103 additions and 494 deletions
|
@ -24,6 +24,8 @@
|
||||||
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
|
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
|
||||||
* すべてのリモートユーザーのリアクション一覧を見えないようにします
|
* すべてのリモートユーザーのリアクション一覧を見えないようにします
|
||||||
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
|
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
|
||||||
|
- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
|
||||||
|
* デフォルトは空欄なので適用前と同等の動作になります
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 新しいゲームを追加
|
- Feat: 新しいゲームを追加
|
||||||
|
@ -74,6 +76,7 @@
|
||||||
- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
|
- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
|
||||||
- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
|
- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
|
||||||
- Fix: MkCodeEditorで行がずれていってしまう問題の修正
|
- Fix: MkCodeEditorで行がずれていってしまう問題の修正
|
||||||
|
- Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
||||||
|
|
|
@ -286,18 +286,17 @@ export const argTypes = {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 4,
|
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.
|
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
|
```ts
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
export const handlers = [
|
export const handlers = [
|
||||||
rest.post('/api/notes/timeline', (req, res, ctx) => {
|
http.post('/api/notes/timeline', ({ request }) => {
|
||||||
return res(
|
return HttpResponse.json([]);
|
||||||
ctx.json([]),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
|
@ -1041,6 +1041,9 @@ resetPasswordConfirm: "Vols canviar la teva contrasenya?"
|
||||||
sensitiveWords: "Paraules sensibles"
|
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."
|
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."
|
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"
|
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."
|
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."
|
notesSearchNotAvailable: "La cerca de notes no es troba disponible."
|
||||||
|
@ -1518,12 +1521,82 @@ _achievements:
|
||||||
title: "Nocturn"
|
title: "Nocturn"
|
||||||
description: "Publica una nota a altes hores de la nit "
|
description: "Publica una nota a altes hores de la nit "
|
||||||
flavor: "És hora d'anar a dormir."
|
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:
|
_open3windows:
|
||||||
title: "Multi finestres"
|
title: "Multi finestres"
|
||||||
description: "I va obrir més de tres finestres"
|
description: "I va obrir més de tres finestres"
|
||||||
_driveFolderCircularReference:
|
_driveFolderCircularReference:
|
||||||
title: "Consulteu la secció de bucle"
|
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:
|
_role:
|
||||||
|
new: "Nou rol"
|
||||||
|
edit: "Editar el rol"
|
||||||
|
name: "Nom del rol"
|
||||||
|
description: "Descripció del rol"
|
||||||
permission: "Permisos de rol"
|
permission: "Permisos de rol"
|
||||||
descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bàsiques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor."
|
descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bàsiques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor."
|
||||||
assignTarget: "Assignar "
|
assignTarget: "Assignar "
|
||||||
|
@ -1545,35 +1618,259 @@ _role:
|
||||||
asBadge: "Mostrar com a insígnia "
|
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."
|
descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol."
|
||||||
isExplorable: "Fer el rol explorable"
|
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: "Prioritat"
|
||||||
_priority:
|
_priority:
|
||||||
low: "Baixa"
|
low: "Baixa"
|
||||||
middle: "Mitjà"
|
middle: "Mitjà"
|
||||||
high: "Alta"
|
high: "Alta"
|
||||||
_options:
|
_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"
|
canManageCustomEmojis: "Gestiona els emojis personalitzats"
|
||||||
canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
|
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"
|
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:
|
_ffVisibility:
|
||||||
public: "Publicar"
|
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:
|
_ad:
|
||||||
back: "Tornar"
|
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:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "t'ha seguit"
|
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:
|
_instanceMute:
|
||||||
instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
|
instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
|
||||||
_theme:
|
_theme:
|
||||||
description: "Descripció"
|
description: "Descripció"
|
||||||
keys:
|
keys:
|
||||||
|
navHoverFg: "Text barra lateral (en passar per sobre)"
|
||||||
|
navActive: "Text barra lateral (actiu)"
|
||||||
|
navIndicator: "Indicador barra lateral"
|
||||||
|
link: "Enllaç"
|
||||||
|
hashtag: "Etiqueta"
|
||||||
mention: "Menció"
|
mention: "Menció"
|
||||||
|
mentionMe: "Mencions (jo)"
|
||||||
renote: "Renotar"
|
renote: "Renotar"
|
||||||
|
modalBg: "Fons del modal"
|
||||||
divider: "Divisor"
|
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:
|
_sfx:
|
||||||
note: "Notes"
|
note: "Notes"
|
||||||
|
noteMy: "Nota (per mi)"
|
||||||
notification: "Notificacions"
|
notification: "Notificacions"
|
||||||
antenna: "Antenes"
|
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:
|
_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"
|
renewTOTPCancel: "No, gràcies"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "Totes les publicacions"
|
all: "Totes les publicacions"
|
||||||
|
@ -1592,6 +1889,8 @@ _widgets:
|
||||||
chooseList: "Tria una llista"
|
chooseList: "Tria una llista"
|
||||||
_cw:
|
_cw:
|
||||||
show: "Carregar més"
|
show: "Carregar més"
|
||||||
|
_poll:
|
||||||
|
deadlineTime: "Hor(a)(es)"
|
||||||
_visibility:
|
_visibility:
|
||||||
home: "Inici"
|
home: "Inici"
|
||||||
followers: "Seguidors"
|
followers: "Seguidors"
|
||||||
|
|
|
@ -1005,6 +1005,7 @@ resetPasswordConfirm: "Opravdu chcete resetovat heslo?"
|
||||||
sensitiveWords: "Citlivá slova"
|
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ů."
|
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."
|
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é."
|
notesSearchNotAvailable: "Vyhledávání poznámek je nedostupné."
|
||||||
license: "Licence"
|
license: "Licence"
|
||||||
unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?"
|
unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?"
|
||||||
|
|
|
@ -1037,6 +1037,7 @@ resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
|
||||||
sensitiveWords: "Sensible Wörter"
|
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."
|
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."
|
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"
|
hiddenTags: "Ausgeblendete Hashtags"
|
||||||
hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden."
|
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."
|
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
|
||||||
|
|
|
@ -1078,6 +1078,7 @@ resetPasswordConfirm: "Really reset your password?"
|
||||||
sensitiveWords: "Sensitive words"
|
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."
|
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."
|
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"
|
hiddenTags: "Hidden hashtags"
|
||||||
hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines."
|
hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines."
|
||||||
notesSearchNotAvailable: "Note search is unavailable."
|
notesSearchNotAvailable: "Note search is unavailable."
|
||||||
|
@ -2023,54 +2024,30 @@ _permissions:
|
||||||
"read:flash-likes": "View list of liked Plays"
|
"read:flash-likes": "View list of liked Plays"
|
||||||
"write:flash-likes": "Edit list of liked Plays"
|
"write:flash-likes": "Edit list of liked Plays"
|
||||||
"read:admin:abuse-user-reports": "View user reports"
|
"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"
|
"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"
|
"read:admin:meta": "View instance metadata"
|
||||||
"write:admin:reset-password": "Reset user passwords"
|
"write:admin:reset-password": "Reset user password"
|
||||||
"write:admin:resolve-abuse-user-report": "Resolve user reports"
|
"write:admin:send-email": "Send email"
|
||||||
"write:admin:send-email": "Send Email"
|
|
||||||
"read:admin:server-info": "View server info"
|
"read:admin:server-info": "View server info"
|
||||||
"read:admin:show-moderation-log": "View moderation log"
|
"read:admin:show-moderation-log": "View moderation log"
|
||||||
"read:admin:show-user": "View user information"
|
"read:admin:show-user": "View private user info"
|
||||||
"read:admin:show-users": "View users"
|
"read:admin:show-users": "View private user info"
|
||||||
"write:admin:suspend-user": "Suspend user"
|
"write:admin:suspend-user": "Suspend user"
|
||||||
"write:admin:unset-user-avatar": "Remove avatar from user"
|
"write:admin:unset-user-avatar": "Remove user avatar"
|
||||||
"write:admin:unset-user-banner": "Remove banner from user"
|
"write:admin:unset-user-banner": "Remove user banner"
|
||||||
"write:admin:unsuspend-user": "Unsuspend user"
|
"write:admin:unsuspend-user": "Unsuspend user"
|
||||||
"write:admin:meta": "Edit instance metadata"
|
"write:admin:meta": "Manage instance metadata"
|
||||||
"write:admin:user-note": "Edit user note"
|
"write:admin:user-note": "Manage moderation note"
|
||||||
"write:admin:roles": "Edit roles"
|
"write:admin:roles": "Manage roles"
|
||||||
"read:admin:roles": "View roles"
|
"read:admin:roles": "View roles"
|
||||||
"write:admin:relays": "Edit relays"
|
"write:admin:relays": "Manage relays"
|
||||||
"read:admin:relays": "View 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"
|
"read:admin:invite-codes": "View invite codes"
|
||||||
"write:admin:announcements": "Edit announcements"
|
"write:admin:announcements": "Manage announcements"
|
||||||
"read:admin:announcements": "View announcements"
|
"read:admin:announcements": "View announcements"
|
||||||
"write:admin:avatar-decorations": "Edit avatar decorations"
|
"write:admin:avatar-decorations": "Manage 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"
|
|
||||||
_auth:
|
_auth:
|
||||||
shareAccessTitle: "Granting application permissions"
|
shareAccessTitle: "Granting application permissions"
|
||||||
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
||||||
|
|
|
@ -1041,6 +1041,7 @@ resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
|
||||||
sensitiveWords: "Palabras sensibles"
|
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"
|
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."
|
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"
|
hiddenTags: "Hashtags ocultos"
|
||||||
hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
|
hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
|
||||||
notesSearchNotAvailable: "No se puede buscar una nota"
|
notesSearchNotAvailable: "No se puede buscar una nota"
|
||||||
|
|
|
@ -1038,6 +1038,7 @@ resetPasswordConfirm: "Yakin untuk mereset kata sandimu?"
|
||||||
sensitiveWords: "Kata sensitif"
|
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."
|
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."
|
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"
|
hiddenTags: "Tagar tersembunyi"
|
||||||
hiddenTagsDescription: "Pilih tanda yang mana akan tidak diperlihatkan dalam daftar tren.\nTanda lebih dari satu dapat didaftarkan dengan tiap baris."
|
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."
|
notesSearchNotAvailable: "Pencarian catatan tidak tersedia."
|
||||||
|
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
|
@ -4329,6 +4329,18 @@ export interface Locale extends ILocale {
|
||||||
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
|
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
|
||||||
*/
|
*/
|
||||||
"sensitiveWordsDescription2": string;
|
"sensitiveWordsDescription2": string;
|
||||||
|
/**
|
||||||
|
* 禁止ワード
|
||||||
|
*/
|
||||||
|
"prohibitedWords": string;
|
||||||
|
/**
|
||||||
|
* 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
|
||||||
|
*/
|
||||||
|
"prohibitedWordsDescription": string;
|
||||||
|
/**
|
||||||
|
* スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
|
||||||
|
*/
|
||||||
|
"prohibitedWordsDescription2": string;
|
||||||
/**
|
/**
|
||||||
* 非表示ハッシュタグ
|
* 非表示ハッシュタグ
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1045,6 +1045,7 @@ resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
|
||||||
sensitiveWords: "Parole esplicite"
|
sensitiveWords: "Parole esplicite"
|
||||||
sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
|
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."
|
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"
|
hiddenTags: "Hashtag nascosti"
|
||||||
hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga."
|
hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga."
|
||||||
notesSearchNotAvailable: "Non è possibile cercare tra le Note."
|
notesSearchNotAvailable: "Non è possibile cercare tra le Note."
|
||||||
|
|
|
@ -1078,6 +1078,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
|
||||||
sensitiveWords: "センシティブワード"
|
sensitiveWords: "センシティブワード"
|
||||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
||||||
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||||
|
prohibitedWords: "禁止ワード"
|
||||||
|
prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
|
||||||
|
prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||||
hiddenTags: "非表示ハッシュタグ"
|
hiddenTags: "非表示ハッシュタグ"
|
||||||
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
|
hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
|
||||||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||||
|
|
|
@ -1043,6 +1043,7 @@ resetPasswordConfirm: "パスワード作り直すんでええな?"
|
||||||
sensitiveWords: "けったいな単語"
|
sensitiveWords: "けったいな単語"
|
||||||
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
|
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
|
||||||
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
|
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
|
||||||
|
prohibitedWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
|
||||||
hiddenTags: "見えてへんハッシュタグ"
|
hiddenTags: "見えてへんハッシュタグ"
|
||||||
hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。"
|
hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。"
|
||||||
notesSearchNotAvailable: "なんかノート探せへん。"
|
notesSearchNotAvailable: "なんかノート探せへん。"
|
||||||
|
|
|
@ -1041,6 +1041,7 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
|
||||||
sensitiveWords: "민감한 단어"
|
sensitiveWords: "민감한 단어"
|
||||||
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
|
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
|
||||||
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
||||||
|
prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
||||||
hiddenTags: "숨긴 해시태그"
|
hiddenTags: "숨긴 해시태그"
|
||||||
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
|
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
|
||||||
notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
|
notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
|
||||||
|
|
|
@ -1015,6 +1015,7 @@ resetPasswordConfirm: "Сбросить пароль?"
|
||||||
sensitiveWords: "Чувствительные слова"
|
sensitiveWords: "Чувствительные слова"
|
||||||
sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
|
sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
|
||||||
sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
|
sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
|
||||||
|
prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
|
||||||
notesSearchNotAvailable: "Поиск заметок недоступен"
|
notesSearchNotAvailable: "Поиск заметок недоступен"
|
||||||
license: "Лицензия"
|
license: "Лицензия"
|
||||||
unfavoriteConfirm: "Удалить избранное?"
|
unfavoriteConfirm: "Удалить избранное?"
|
||||||
|
|
|
@ -1041,6 +1041,7 @@ resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุ
|
||||||
sensitiveWords: "คำที่มีเนื้อหาละเอียดอ่อน"
|
sensitiveWords: "คำที่มีเนื้อหาละเอียดอ่อน"
|
||||||
sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
|
sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
|
||||||
sensitiveWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
|
sensitiveWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
|
||||||
|
prohibitedWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
|
||||||
hiddenTags: "แฮชแท็กที่ซ่อนอยู่"
|
hiddenTags: "แฮชแท็กที่ซ่อนอยู่"
|
||||||
hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่"
|
hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่"
|
||||||
notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน"
|
notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน"
|
||||||
|
|
|
@ -1041,6 +1041,7 @@ resetPasswordConfirm: "确定重置密码?"
|
||||||
sensitiveWords: "敏感词"
|
sensitiveWords: "敏感词"
|
||||||
sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
|
sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
|
||||||
sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
|
sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
|
||||||
|
prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
|
||||||
hiddenTags: "隐藏标签"
|
hiddenTags: "隐藏标签"
|
||||||
hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。"
|
hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。"
|
||||||
notesSearchNotAvailable: "帖子检索不可用"
|
notesSearchNotAvailable: "帖子检索不可用"
|
||||||
|
|
|
@ -1041,6 +1041,7 @@ resetPasswordConfirm: "重設密碼?"
|
||||||
sensitiveWords: "敏感詞"
|
sensitiveWords: "敏感詞"
|
||||||
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
|
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
|
||||||
sensitiveWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
|
sensitiveWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
|
||||||
|
prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
|
||||||
hiddenTags: "隱藏標籤"
|
hiddenTags: "隱藏標籤"
|
||||||
hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。"
|
hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。"
|
||||||
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
|
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "sharkey",
|
"name": "sharkey",
|
||||||
"version": "2024.2.0-beta.10",
|
"version": "2024.2.0-beta.11",
|
||||||
"codename": "shonk",
|
"codename": "shonk",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.joinsharkey.org/Sharkey/Sharkey.git"
|
"url": "https://git.joinsharkey.org/Sharkey/Sharkey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.12.1",
|
"packageManager": "pnpm@8.15.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
|
|
16
packages/backend/migration/1707429690000-prohibited-words.js
Normal file
16
packages/backend/migration/1707429690000-prohibited-words.js
Normal file
|
@ -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"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,7 +84,7 @@
|
||||||
"@nestjs/testing": "10.2.10",
|
"@nestjs/testing": "10.2.10",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@transfem-org/sfm-js": "0.24.4",
|
"@transfem-org/sfm-js": "0.24.4",
|
||||||
"@simplewebauthn/server": "9.0.1",
|
"@simplewebauthn/server": "9.0.2",
|
||||||
"@sinonjs/fake-timers": "11.2.2",
|
"@sinonjs/fake-timers": "11.2.2",
|
||||||
"@smithy/node-http-handler": "2.1.10",
|
"@smithy/node-http-handler": "2.1.10",
|
||||||
"@swc/cli": "0.1.63",
|
"@swc/cli": "0.1.63",
|
||||||
|
@ -98,12 +98,12 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"bullmq": "5.1.5",
|
"bullmq": "5.1.9",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.1",
|
"cbor": "9.0.2",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
"chalk-template": "1.1.0",
|
"chalk-template": "1.1.0",
|
||||||
"chokidar": "3.5.3",
|
"chokidar": "3.6.0",
|
||||||
"cli-highlight": "2.1.11",
|
"cli-highlight": "2.1.11",
|
||||||
"color-convert": "2.0.1",
|
"color-convert": "2.0.1",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
|
@ -205,7 +205,7 @@
|
||||||
"@types/jsrsasign": "10.5.12",
|
"@types/jsrsasign": "10.5.12",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "20.11.10",
|
"@types/node": "20.11.17",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.14",
|
"@types/nodemailer": "6.4.14",
|
||||||
"@types/oauth": "0.9.4",
|
"@types/oauth": "0.9.4",
|
||||||
|
|
|
@ -408,7 +408,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public checkDuplicate(name: string): Promise<boolean> {
|
public checkDuplicate(name: string): Promise<boolean> {
|
||||||
return this.emojisRepository.exist({ where: { name, host: IsNull() } });
|
return this.emojisRepository.exists({ where: { name, host: IsNull() } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -163,7 +163,7 @@ export class HashtagService {
|
||||||
const instance = await this.metaService.fetch();
|
const instance = await this.metaService.fetch();
|
||||||
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
||||||
if (hiddenTags.includes(hashtag)) return;
|
if (hiddenTags.includes(hashtag)) return;
|
||||||
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
|
if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
|
||||||
|
|
||||||
// YYYYMMDDHHmm (10分間隔)
|
// YYYYMMDDHHmm (10分間隔)
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
|
@ -153,6 +153,8 @@ type Option = {
|
||||||
export class NoteCreateService implements OnApplicationShutdown {
|
export class NoteCreateService implements OnApplicationShutdown {
|
||||||
#shutdownController = new AbortController();
|
#shutdownController = new AbortController();
|
||||||
|
|
||||||
|
public static ContainsProhibitedWordsError = class extends Error {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
@ -429,13 +431,19 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (data.visibility === 'public' && data.channel == null) {
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
const sensitiveWords = meta.sensitiveWords;
|
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';
|
data.visibility = 'home';
|
||||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||||
data.visibility = 'home';
|
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);
|
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
|
||||||
|
|
||||||
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
|
||||||
|
@ -795,7 +803,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
// 通知
|
// 通知
|
||||||
if (data.reply.userHost === null) {
|
if (data.reply.userHost === null) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: data.reply.userId,
|
userId: data.reply.userId,
|
||||||
threadId: data.reply.threadId ?? data.reply.id,
|
threadId: data.reply.threadId ?? data.reply.id,
|
||||||
|
@ -830,7 +838,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// Notify
|
// Notify
|
||||||
if (data.renote.userHost === null) {
|
if (data.renote.userHost === null) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: data.renote.userId,
|
userId: data.renote.userId,
|
||||||
threadId: data.renote.threadId ?? data.renote.id,
|
threadId: data.renote.threadId ?? data.renote.id,
|
||||||
|
@ -1057,7 +1065,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
@bindThis
|
@bindThis
|
||||||
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
|
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
|
||||||
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: u.id,
|
userId: u.id,
|
||||||
threadId: note.threadId ?? note.id,
|
threadId: note.threadId ?? note.id,
|
||||||
|
|
|
@ -612,7 +612,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
// 通知
|
// 通知
|
||||||
if (data.reply.userHost === null) {
|
if (data.reply.userHost === null) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: data.reply.userId,
|
userId: data.reply.userId,
|
||||||
threadId: data.reply.threadId ?? data.reply.id,
|
threadId: data.reply.threadId ?? data.reply.id,
|
||||||
|
@ -647,7 +647,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// Notify
|
// Notify
|
||||||
if (data.renote.userHost === null) {
|
if (data.renote.userHost === null) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: data.renote.userId,
|
userId: data.renote.userId,
|
||||||
threadId: data.renote.threadId ?? data.renote.id,
|
threadId: data.renote.threadId ?? data.renote.id,
|
||||||
|
@ -751,7 +751,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
@bindThis
|
@bindThis
|
||||||
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
|
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
|
||||||
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: u.id,
|
userId: u.id,
|
||||||
threadId: note.threadId ?? note.id,
|
threadId: note.threadId ?? note.id,
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// スレッドミュート
|
// スレッドミュート
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
threadId: note.threadId ?? note.id,
|
threadId: note.threadId ?? note.id,
|
||||||
|
@ -70,7 +70,7 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
|
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
|
||||||
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
|
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;
|
if (!exist) return;
|
||||||
|
|
||||||
|
|
|
@ -248,7 +248,7 @@ export class ReactionService {
|
||||||
|
|
||||||
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
||||||
if (note.userHost === null) {
|
if (note.userHost === null) {
|
||||||
const isThreadMuted = await this.noteThreadMutingsRepository.exist({
|
const isThreadMuted = await this.noteThreadMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
threadId: note.threadId ?? note.id,
|
threadId: note.threadId ?? note.id,
|
||||||
|
|
|
@ -77,12 +77,12 @@ export class SignupService {
|
||||||
const secret = generateUserToken();
|
const secret = generateUserToken();
|
||||||
|
|
||||||
// Check username duplication
|
// 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');
|
throw new Error('DUPLICATED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check deleted username duplication
|
// 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');
|
throw new Error('USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
let autoAccept = false;
|
let autoAccept = false;
|
||||||
|
|
||||||
// 鍵アカウントであっても、既にフォローされていた場合はスルー
|
// 鍵アカウントであっても、既にフォローされていた場合はスルー
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
@ -156,7 +156,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
|
|
||||||
// フォローしているユーザーは自動承認オプション
|
// フォローしているユーザーは自動承認オプション
|
||||||
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
|
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
|
||||||
const isFollowed = await this.followingsRepository.exist({
|
const isFollowed = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: followee.id,
|
followerId: followee.id,
|
||||||
followeeId: follower.id,
|
followeeId: follower.id,
|
||||||
|
@ -170,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
if (followee.isLocked && !autoAccept) {
|
if (followee.isLocked && !autoAccept) {
|
||||||
autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
|
autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
|
||||||
follower,
|
follower,
|
||||||
(oldSrc, newSrc) => this.followingsRepository.exist({
|
(oldSrc, newSrc) => this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: newSrc.id,
|
followerId: newSrc.id,
|
||||||
|
@ -233,7 +233,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
|
|
||||||
this.cacheService.userFollowingsCache.refresh(follower.id);
|
this.cacheService.userFollowingsCache.refresh(follower.id);
|
||||||
|
|
||||||
const requestExist = await this.followRequestsRepository.exist({
|
const requestExist = await this.followRequestsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: follower.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: {
|
where: {
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
|
|
|
@ -43,13 +43,13 @@ export class UtilityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
|
public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
|
||||||
if (sensitiveWords.length === 0) return false;
|
if (keyWords.length === 0) return false;
|
||||||
if (text === '') return false;
|
if (text === '') return false;
|
||||||
|
|
||||||
const regexpregexp = /^\/(.+)\/(.*)$/;
|
const regexpregexp = /^\/(.+)\/(.*)$/;
|
||||||
|
|
||||||
const matched = sensitiveWords.some(filter => {
|
const matched = keyWords.some(filter => {
|
||||||
// represents RegExp
|
// represents RegExp
|
||||||
const regexp = filter.match(regexpregexp);
|
const regexp = filter.match(regexpregexp);
|
||||||
// This should never happen due to input sanitisation.
|
// This should never happen due to input sanitisation.
|
||||||
|
|
|
@ -629,7 +629,7 @@ export class ApInboxService {
|
||||||
return 'skip: follower not found';
|
return 'skip: follower not found';
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: actor.id,
|
followeeId: actor.id,
|
||||||
|
@ -686,14 +686,14 @@ export class ApInboxService {
|
||||||
return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
|
return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestExist = await this.followRequestsRepository.exist({
|
const requestExist = await this.followRequestsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: actor.id,
|
followerId: actor.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: actor.id,
|
followerId: actor.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
|
|
@ -345,7 +345,7 @@ export class ApRendererService {
|
||||||
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
||||||
|
|
||||||
if (inReplyToNote != null) {
|
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 (inReplyToUserExist) {
|
||||||
if (inReplyToNote.uri) {
|
if (inReplyToNote.uri) {
|
||||||
|
@ -636,7 +636,7 @@ export class ApRendererService {
|
||||||
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
||||||
|
|
||||||
if (inReplyToNote != null) {
|
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 (inReplyToUserExist) {
|
||||||
if (inReplyToNote.uri) {
|
if (inReplyToNote.uri) {
|
||||||
|
|
|
@ -51,14 +51,14 @@ export class ChannelEntityService {
|
||||||
|
|
||||||
const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
|
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: {
|
where: {
|
||||||
followerId: meId,
|
followerId: meId,
|
||||||
followeeId: channel.id,
|
followeeId: channel.id,
|
||||||
},
|
},
|
||||||
}) : false;
|
}) : false;
|
||||||
|
|
||||||
const isFavorited = meId ? await this.channelFavoritesRepository.exist({
|
const isFavorited = meId ? await this.channelFavoritesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: meId,
|
userId: meId,
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
|
|
|
@ -46,7 +46,7 @@ export class ClipEntityService {
|
||||||
description: clip.description,
|
description: clip.description,
|
||||||
isPublic: clip.isPublic,
|
isPublic: clip.isPublic,
|
||||||
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
|
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ export class FlashEntityService {
|
||||||
summary: flash.summary,
|
summary: flash.summary,
|
||||||
script: flash.script,
|
script: flash.script,
|
||||||
likedCount: flash.likedCount,
|
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ export class GalleryPostEntityService {
|
||||||
tags: post.tags.length > 0 ? post.tags : undefined,
|
tags: post.tags.length > 0 ? post.tags : undefined,
|
||||||
isSensitive: post.isSensitive,
|
isSensitive: post.isSensitive,
|
||||||
likedCount: post.likedCount,
|
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
hide = false;
|
hide = false;
|
||||||
} else {
|
} else {
|
||||||
if (packedNote.renote) {
|
if (packedNote.renote) {
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followeeId: packedNote.renote.userId,
|
followeeId: packedNote.renote.userId,
|
||||||
followerId: meId,
|
followerId: meId,
|
||||||
|
@ -124,7 +124,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
hide = !isFollowing;
|
hide = !isFollowing;
|
||||||
} else {
|
} else {
|
||||||
// フォロワーかどうか
|
// フォロワーかどうか
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followeeId: packedNote.userId,
|
followeeId: packedNote.userId,
|
||||||
followerId: meId,
|
followerId: meId,
|
||||||
|
|
|
@ -104,7 +104,7 @@ export class PageEntityService {
|
||||||
eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
|
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)),
|
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)),
|
||||||
likedCount: page.likedCount,
|
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,43 +153,43 @@ export class UserEntityService implements OnModuleInit {
|
||||||
followerId: me,
|
followerId: me,
|
||||||
followeeId: target,
|
followeeId: target,
|
||||||
}),
|
}),
|
||||||
this.followingsRepository.exist({
|
this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: target,
|
followerId: target,
|
||||||
followeeId: me,
|
followeeId: me,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.followRequestsRepository.exist({
|
this.followRequestsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: me,
|
followerId: me,
|
||||||
followeeId: target,
|
followeeId: target,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.followRequestsRepository.exist({
|
this.followRequestsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: target,
|
followerId: target,
|
||||||
followeeId: me,
|
followeeId: me,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.blockingsRepository.exist({
|
this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: me,
|
blockerId: me,
|
||||||
blockeeId: target,
|
blockeeId: target,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.blockingsRepository.exist({
|
this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: target,
|
blockerId: target,
|
||||||
blockeeId: me,
|
blockeeId: me,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.mutingsRepository.exist({
|
this.mutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
muterId: me,
|
muterId: me,
|
||||||
muteeId: target,
|
muteeId: target,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.renoteMutingsRepository.exist({
|
this.renoteMutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
muterId: me,
|
muterId: me,
|
||||||
muteeId: target,
|
muteeId: target,
|
||||||
|
@ -216,7 +216,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
/*
|
/*
|
||||||
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
|
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: {
|
where: {
|
||||||
antennaId: In(myAntennas.map(x => x.id)),
|
antennaId: In(myAntennas.map(x => x.id)),
|
||||||
read: false,
|
read: false,
|
||||||
|
|
|
@ -76,6 +76,11 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public sensitiveWords: string[];
|
public sensitiveWords: string[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, array: true, default: '{}',
|
||||||
|
})
|
||||||
|
public prohibitedWords: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024, array: true, default: '{}',
|
length: 1024, array: true, default: '{}',
|
||||||
})
|
})
|
||||||
|
|
|
@ -176,12 +176,12 @@ export class SignupApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance.emailRequiredForSignup) {
|
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');
|
throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check deleted username duplication
|
// 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');
|
throw new FastifyReplyError(400, 'USED_USERNAME');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,13 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
prohibitedWords: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
bannedEmailDomains: {
|
bannedEmailDomains: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
|
@ -549,6 +556,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
blockedHosts: instance.blockedHosts,
|
blockedHosts: instance.blockedHosts,
|
||||||
silencedHosts: instance.silencedHosts,
|
silencedHosts: instance.silencedHosts,
|
||||||
sensitiveWords: instance.sensitiveWords,
|
sensitiveWords: instance.sensitiveWords,
|
||||||
|
prohibitedWords: instance.prohibitedWords,
|
||||||
preservedUsernames: instance.preservedUsernames,
|
preservedUsernames: instance.preservedUsernames,
|
||||||
bubbleInstances: instance.bubbleInstances,
|
bubbleInstances: instance.bubbleInstances,
|
||||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||||
|
|
|
@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
||||||
const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } });
|
const exist = await this.promoNotesRepository.exists({ where: { noteId: note.id } });
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyPromoted);
|
throw new ApiError(meta.errors.alreadyPromoted);
|
||||||
|
|
|
@ -41,6 +41,11 @@ export const paramDef = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
prohibitedWords: {
|
||||||
|
type: 'array', nullable: true, items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
|
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
|
||||||
mascotImageUrl: { type: 'string', nullable: true },
|
mascotImageUrl: { type: 'string', nullable: true },
|
||||||
bannerUrl: { type: 'string', nullable: true },
|
bannerUrl: { type: 'string', nullable: true },
|
||||||
|
@ -185,6 +190,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (Array.isArray(ps.sensitiveWords)) {
|
if (Array.isArray(ps.sensitiveWords)) {
|
||||||
set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
|
set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(ps.prohibitedWords)) {
|
||||||
|
set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
|
||||||
|
}
|
||||||
if (Array.isArray(ps.silencedHosts)) {
|
if (Array.isArray(ps.silencedHosts)) {
|
||||||
let lastValue = '';
|
let lastValue = '';
|
||||||
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
|
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const accessToken = secureRndstr(32);
|
const accessToken = secureRndstr(32);
|
||||||
|
|
||||||
// Fetch exist access token
|
// Fetch exist access token
|
||||||
const exist = await this.accessTokensRepository.exist({
|
const exist = await this.accessTokensRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
appId: session.appId,
|
appId: session.appId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if already blocking
|
// Check if already blocking
|
||||||
const exist = await this.blockingsRepository.exist({
|
const exist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: blocker.id,
|
blockerId: blocker.id,
|
||||||
blockeeId: blockee.id,
|
blockeeId: blockee.id,
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check not blocking
|
// Check not blocking
|
||||||
const exist = await this.blockingsRepository.exist({
|
const exist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: blocker.id,
|
blockerId: blocker.id,
|
||||||
blockeeId: blockee.id,
|
blockeeId: blockee.id,
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchClip);
|
throw new ApiError(meta.errors.noSuchClip);
|
||||||
}
|
}
|
||||||
|
|
||||||
const exist = await this.clipFavoritesRepository.exist({
|
const exist = await this.clipFavoritesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
clipId: clip.id,
|
clipId: clip.id,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const exist = await this.driveFilesRepository.exist({
|
const exist = await this.driveFilesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
md5: ps.md5,
|
md5: ps.md5,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// if already liked
|
// if already liked
|
||||||
const exist = await this.flashLikesRepository.exist({
|
const exist = await this.flashLikesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
flashId: flash.id,
|
flashId: flash.id,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if already following
|
// Check if already following
|
||||||
const exist = await this.followingsRepository.exist({
|
const exist = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check not following
|
// Check not following
|
||||||
const exist = await this.followingsRepository.exist({
|
const exist = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
|
|
@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// if already liked
|
// if already liked
|
||||||
const exist = await this.galleryLikesRepository.exist({
|
const exist = await this.galleryLikesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
postId: post.id,
|
postId: post.id,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private downloadService: DownloadService,
|
private downloadService: DownloadService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
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);
|
if (!userExist) throw new ApiError(meta.errors.noSuchUser);
|
||||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||||
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
if (ps.tokenId) {
|
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) {
|
if (tokenExist) {
|
||||||
await this.accessTokensRepository.delete({
|
await this.accessTokensRepository.delete({
|
||||||
|
@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (ps.token) {
|
} 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) {
|
if (tokenExist) {
|
||||||
await this.accessTokensRepository.delete({
|
await this.accessTokensRepository.delete({
|
||||||
|
|
|
@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if already muting
|
// Check if already muting
|
||||||
const exist = await this.mutingsRepository.exist({
|
const exist = await this.mutingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
muterId: muter.id,
|
muterId: muter.id,
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
|
|
|
@ -17,6 +17,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { isPureRenote } from '@/misc/is-pure-renote.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';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -117,6 +119,12 @@ export const meta = {
|
||||||
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
|
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
|
||||||
id: '33510210-8452-094c-6227-4a6c05d99f00',
|
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;
|
} as const;
|
||||||
|
|
||||||
|
@ -271,7 +279,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (renote.userId !== me.id) {
|
if (renote.userId !== me.id) {
|
||||||
const blockExist = await this.blockingsRepository.exist({
|
const blockExist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: renote.userId,
|
blockerId: renote.userId,
|
||||||
blockeeId: me.id,
|
blockeeId: me.id,
|
||||||
|
@ -319,7 +327,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (reply.userId !== me.id) {
|
if (reply.userId !== me.id) {
|
||||||
const blockExist = await this.blockingsRepository.exist({
|
const blockExist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: reply.userId,
|
blockerId: reply.userId,
|
||||||
blockeeId: me.id,
|
blockeeId: me.id,
|
||||||
|
@ -351,6 +359,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投稿を作成
|
// 投稿を作成
|
||||||
|
try {
|
||||||
const note = await this.noteCreateService.create(me, {
|
const note = await this.noteCreateService.create(me, {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
files: files,
|
files: files,
|
||||||
|
@ -376,6 +385,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return {
|
return {
|
||||||
createdNote: await this.noteEntityService.pack(note, me),
|
createdNote: await this.noteEntityService.pack(note, me),
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
|
||||||
|
if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
|
||||||
|
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,7 +311,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (renote.userId !== me.id) {
|
if (renote.userId !== me.id) {
|
||||||
const blockExist = await this.blockingsRepository.exist({
|
const blockExist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: renote.userId,
|
blockerId: renote.userId,
|
||||||
blockeeId: me.id,
|
blockeeId: me.id,
|
||||||
|
@ -349,7 +349,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (reply.userId !== me.id) {
|
if (reply.userId !== me.id) {
|
||||||
const blockExist = await this.blockingsRepository.exist({
|
const blockExist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: reply.userId,
|
blockerId: reply.userId,
|
||||||
blockeeId: me.id,
|
blockeeId: me.id,
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// if already favorited
|
// if already favorited
|
||||||
const exist = await this.noteFavoritesRepository.exist({
|
const exist = await this.noteFavoritesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// if already liked
|
// if already liked
|
||||||
const exist = await this.pageLikesRepository.exist({
|
const exist = await this.pageLikesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
pageId: page.id,
|
pageId: page.id,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
const exist = await this.promoReadsRepository.exist({
|
const exist = await this.promoReadsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (me == null) {
|
if (me == null) {
|
||||||
throw new ApiError(meta.errors.forbidden);
|
throw new ApiError(meta.errors.forbidden);
|
||||||
} else if (me.id !== user.id) {
|
} else if (me.id !== user.id) {
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followeeId: user.id,
|
followeeId: user.id,
|
||||||
followerId: me.id,
|
followerId: me.id,
|
||||||
|
|
|
@ -109,7 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (me == null) {
|
if (me == null) {
|
||||||
throw new ApiError(meta.errors.forbidden);
|
throw new ApiError(meta.errors.forbidden);
|
||||||
} else if (me.id !== user.id) {
|
} else if (me.id !== user.id) {
|
||||||
const isFollowing = await this.followingsRepository.exist({
|
const isFollowing = await this.followingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
followeeId: user.id,
|
followeeId: user.id,
|
||||||
followerId: me.id,
|
followerId: me.id,
|
||||||
|
|
|
@ -90,7 +90,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const listExist = await this.userListsRepository.exist({
|
const listExist = await this.userListsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
|
@ -121,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentUser.id !== me.id) {
|
if (currentUser.id !== me.id) {
|
||||||
const blockExist = await this.blockingsRepository.exist({
|
const blockExist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: currentUser.id,
|
blockerId: currentUser.id,
|
||||||
blockeeId: me.id,
|
blockeeId: me.id,
|
||||||
|
@ -132,7 +132,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const exist = await this.userListMembershipsRepository.exist({
|
const exist = await this.userListMembershipsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userListId: userList.id,
|
userListId: userList.id,
|
||||||
userId: currentUser.id,
|
userId: currentUser.id,
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const userListExist = await this.userListsRepository.exist({
|
const userListExist = await this.userListsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
|
@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
throw new ApiError(meta.errors.noSuchList);
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
}
|
}
|
||||||
|
|
||||||
const exist = await this.userListFavoritesRepository.exist({
|
const exist = await this.userListFavoritesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
userListId: ps.listId,
|
userListId: ps.listId,
|
||||||
|
|
|
@ -104,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
// Check blocking
|
// Check blocking
|
||||||
if (user.id !== me.id) {
|
if (user.id !== me.id) {
|
||||||
const blockExist = await this.blockingsRepository.exist({
|
const blockExist = await this.blockingsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
blockerId: user.id,
|
blockerId: user.id,
|
||||||
blockeeId: me.id,
|
blockeeId: me.id,
|
||||||
|
@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const exist = await this.userListMembershipsRepository.exist({
|
const exist = await this.userListMembershipsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userListId: userList.id,
|
userListId: userList.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|
|
@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
userListId: ps.listId,
|
userListId: ps.listId,
|
||||||
});
|
});
|
||||||
if (me !== null) {
|
if (me !== null) {
|
||||||
additionalProperties.isLiked = await this.userListFavoritesRepository.exist({
|
additionalProperties.isLiked = await this.userListFavoritesRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
userListId: ps.listId,
|
userListId: ps.listId,
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private userListFavoritesRepository: UserListFavoritesRepository,
|
private userListFavoritesRepository: UserListFavoritesRepository,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const userListExist = await this.userListsRepository.exist({
|
const userListExist = await this.userListsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
|
|
|
@ -43,7 +43,7 @@ class UserListChannel extends Channel {
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = params.withRenotes ?? true;
|
||||||
|
|
||||||
// Check existence and owner
|
// Check existence and owner
|
||||||
const listExist = await this.userListsRepository.exist({
|
const listExist = await this.userListsRepository.exists({
|
||||||
where: {
|
where: {
|
||||||
id: this.listId,
|
id: this.listId,
|
||||||
userId: this.user!.id,
|
userId: this.user!.id,
|
||||||
|
|
|
@ -16,12 +16,14 @@ describe('Note', () => {
|
||||||
|
|
||||||
let alice: misskey.entities.SignupResponse;
|
let alice: misskey.entities.SignupResponse;
|
||||||
let bob: misskey.entities.SignupResponse;
|
let bob: misskey.entities.SignupResponse;
|
||||||
|
let tom: misskey.entities.SignupResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const connection = await initTestDb(true);
|
const connection = await initTestDb(true);
|
||||||
Notes = connection.getRepository(MiNote);
|
Notes = connection.getRepository(MiNote);
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
bob = await signup({ username: 'bob' });
|
bob = await signup({ username: 'bob' });
|
||||||
|
tom = await signup({ username: 'tom', host: 'example.com' });
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
test('投稿できる', async () => {
|
test('投稿できる', async () => {
|
||||||
|
@ -607,6 +609,77 @@ describe('Note', () => {
|
||||||
assert.strictEqual(note2.status, 200);
|
assert.strictEqual(note2.status, 200);
|
||||||
assert.strictEqual(note2.body.createdNote.visibility, 'home');
|
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', () => {
|
describe('notes/delete', () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* 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) => {
|
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)) {
|
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'];
|
}) satisfies SharedOptions['onUnhandledRequest'];
|
||||||
|
|
||||||
export const commonHandlers = [
|
export const commonHandlers = [
|
||||||
rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => {
|
http.get('/fluent-emoji/:codepoints.png', async ({ params }) => {
|
||||||
const { codepoints } = req.params;
|
const { codepoints } = params;
|
||||||
const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
|
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) => {
|
http.get('/fluent-emojis/:codepoints.png', async ({ params }) => {
|
||||||
const { codepoints } = req.params;
|
const { codepoints } = params;
|
||||||
const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
|
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) => {
|
http.get('/twemoji/:codepoints.svg', async ({ params }) => {
|
||||||
const { codepoints } = req.params;
|
const { codepoints } = params;
|
||||||
const value = await fetch(`https://unpkg.com/@discordapp/twemoji@15.0.2/dist/svg/${codepoints}.svg`).then((response) => response.blob());
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
"@phosphor-icons/web": "^2.0.3",
|
"@phosphor-icons/web": "^2.0.3",
|
||||||
"@twemoji/parser": "15.0.0",
|
"@twemoji/parser": "15.0.0",
|
||||||
"@vitejs/plugin-vue": "5.0.3",
|
"@vitejs/plugin-vue": "5.0.3",
|
||||||
"@vue/compiler-sfc": "3.4.15",
|
"@vue/compiler-sfc": "3.4.18",
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
|
||||||
"astring": "1.8.6",
|
"astring": "1.8.6",
|
||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.0.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
|
@ -72,8 +72,8 @@
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.3.3",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"v-code-diff": "1.7.2",
|
"v-code-diff": "1.7.2",
|
||||||
"vite": "5.0.12",
|
"vite": "5.1.0",
|
||||||
"vue": "3.4.15",
|
"vue": "3.4.18",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -97,12 +97,12 @@
|
||||||
"@storybook/types": "7.6.10",
|
"@storybook/types": "7.6.10",
|
||||||
"@storybook/vue3": "7.6.10",
|
"@storybook/vue3": "7.6.10",
|
||||||
"@storybook/vue3-vite": "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/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
"@types/matter-js": "0.19.6",
|
"@types/matter-js": "0.19.6",
|
||||||
"@types/micromatch": "4.0.6",
|
"@types/micromatch": "4.0.6",
|
||||||
"@types/node": "20.11.10",
|
"@types/node": "20.11.17",
|
||||||
"@types/punycode": "2.1.3",
|
"@types/punycode": "2.1.3",
|
||||||
"@types/sanitize-html": "2.9.5",
|
"@types/sanitize-html": "2.9.5",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
|
@ -112,10 +112,10 @@
|
||||||
"@typescript-eslint/eslint-plugin": "6.18.1",
|
"@typescript-eslint/eslint-plugin": "6.18.1",
|
||||||
"@typescript-eslint/parser": "6.18.1",
|
"@typescript-eslint/parser": "6.18.1",
|
||||||
"@vitest/coverage-v8": "0.34.6",
|
"@vitest/coverage-v8": "0.34.6",
|
||||||
"@vue/runtime-core": "3.4.15",
|
"@vue/runtime-core": "3.4.18",
|
||||||
"acorn": "8.11.3",
|
"acorn": "8.11.3",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.6.3",
|
"cypress": "13.6.4",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-vue": "9.20.1",
|
"eslint-plugin-vue": "9.20.1",
|
||||||
|
@ -123,10 +123,10 @@
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.5",
|
"micromatch": "4.0.5",
|
||||||
"msw": "2.1.2",
|
"msw": "2.1.7",
|
||||||
"msw-storybook-addon": "1.10.0",
|
"msw-storybook-addon": "2.0.0-beta.1",
|
||||||
"nodemon": "3.0.3",
|
"nodemon": "3.0.3",
|
||||||
"prettier": "3.2.4",
|
"prettier": "3.2.5",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"start-server-and-test": "2.0.3",
|
"start-server-and-test": "2.0.3",
|
||||||
|
|
|
@ -60,12 +60,6 @@ export async function common(createVue: () => App<Element>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const splash = document.getElementById('splash');
|
|
||||||
// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
|
|
||||||
if (splash) splash.addEventListener('transitionend', () => {
|
|
||||||
splash.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
let isClientUpdated = false;
|
let isClientUpdated = false;
|
||||||
|
|
||||||
//#region クライアントが更新されたかチェック
|
//#region クライアントが更新されたかチェック
|
||||||
|
@ -293,5 +287,10 @@ function removeSplash() {
|
||||||
if (splash) {
|
if (splash) {
|
||||||
splash.style.opacity = '0';
|
splash.style.opacity = '0';
|
||||||
splash.style.pointerEvents = 'none';
|
splash.style.pointerEvents = 'none';
|
||||||
|
|
||||||
|
// transitionendイベントが発火しない場合があるため
|
||||||
|
window.setTimeout(() => {
|
||||||
|
splash.remove();
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { abuseUserReport } from '../../.storybook/fakes.js';
|
import { abuseUserReport } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkAbuseReport from './MkAbuseReport.vue';
|
import MkAbuseReport from './MkAbuseReport.vue';
|
||||||
|
@ -44,9 +44,9 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => {
|
http.post('/api/admin/resolve-abuse-user-report', async ({ request }) => {
|
||||||
action('POST /api/admin/resolve-abuse-user-report')(await req.json());
|
action('POST /api/admin/resolve-abuse-user-report')(await request.json());
|
||||||
return res(ctx.json({}));
|
return HttpResponse.json({});
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
|
import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
|
||||||
|
@ -44,9 +44,9 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/report-abuse', async (req, res, ctx) => {
|
http.post('/api/users/report-abuse', async ({ request }) => {
|
||||||
action('POST /api/users/report-abuse')(await req.json());
|
action('POST /api/users/report-abuse')(await request.json());
|
||||||
return res(ctx.json({}));
|
return HttpResponse.json({});
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkAchievements from './MkAchievements.vue';
|
import MkAchievements from './MkAchievements.vue';
|
||||||
|
@ -39,8 +39,8 @@ export const Empty = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/achievements', (req, res, ctx) => {
|
http.post('/api/users/achievements', () => {
|
||||||
return res(ctx.json([]));
|
return HttpResponse.json([]);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -52,8 +52,8 @@ export const All = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/achievements', (req, res, ctx) => {
|
http.post('/api/users/achievements', () => {
|
||||||
return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))));
|
return HttpResponse.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })));
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
import { expect } from '@storybook/jest';
|
import { expect } from '@storybook/jest';
|
||||||
import { userEvent, waitFor, within } from '@storybook/testing-library';
|
import { userEvent, waitFor, within } from '@storybook/testing-library';
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkAutocomplete from './MkAutocomplete.vue';
|
import MkAutocomplete from './MkAutocomplete.vue';
|
||||||
|
@ -99,11 +99,11 @@ export const User = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => {
|
http.post('/api/users/search-by-username-and-host', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
|
userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
|
||||||
userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
|
userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -132,12 +132,12 @@ export const Hashtag = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/hashtags/search', (req, res, ctx) => {
|
http.post('/api/hashtags/search', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
'気象警報注意報',
|
'気象警報注意報',
|
||||||
'気象警報',
|
'気象警報',
|
||||||
'気象情報',
|
'気象情報',
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkAvatars from './MkAvatars.vue';
|
import MkAvatars from './MkAvatars.vue';
|
||||||
|
@ -38,12 +38,12 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/show', (req, res, ctx) => {
|
http.post('/api/users/show', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
userDetailed('17'),
|
userDetailed('17'),
|
||||||
userDetailed('20'),
|
userDetailed('20'),
|
||||||
userDetailed('18'),
|
userDetailed('18'),
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
|
import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkInviteCode from './MkInviteCode.vue';
|
import MkInviteCode from './MkInviteCode.vue';
|
||||||
|
@ -39,8 +39,8 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/show', (req, res, ctx) => {
|
http.post('/api/users/show', ({ params }) => {
|
||||||
return res(ctx.json(userDetailed(req.params.userId as string)));
|
return HttpResponse.json(userDetailed(params.userId as string));
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
|
import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { ChannelConnection as Connection } from 'misskey-js';
|
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
|
@ -90,8 +89,8 @@ function prepend(note) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection: Connection;
|
let connection: Misskey.ChannelConnection | null = null;
|
||||||
let connection2: Connection;
|
let connection2: Misskey.ChannelConnection | null = null;
|
||||||
let paginationQuery: Paging | null = null;
|
let paginationQuery: Paging | null = null;
|
||||||
|
|
||||||
const stream = useStream();
|
const stream = useStream();
|
||||||
|
@ -163,7 +162,7 @@ function connectChannel() {
|
||||||
roleId: props.role,
|
roleId: props.role,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.src !== 'directs' && props.src !== 'mentions') connection.on('note', prepend);
|
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disconnectChannel() {
|
function disconnectChannel() {
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
|
v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
|
||||||
sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
|
sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
|
||||||
scrolling="no"
|
scrolling="no"
|
||||||
:allow="player.allow.join(';')"
|
:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
|
||||||
:class="$style.playerIframe"
|
:class="$style.playerIframe"
|
||||||
:src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')"
|
:src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')"
|
||||||
:style="{ border: 0 }"
|
:style="{ border: 0 }"
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
|
import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
|
||||||
|
@ -38,17 +38,17 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users', (req, res, ctx) => {
|
http.post('/api/users', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
userDetailed('44'),
|
userDetailed('44'),
|
||||||
userDetailed('49'),
|
userDetailed('49'),
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
rest.post('/api/pinned-users', (req, res, ctx) => {
|
http.post('/api/pinned-users', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
userDetailed('44'),
|
userDetailed('44'),
|
||||||
userDetailed('49'),
|
userDetailed('49'),
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import XUser from '@/components/MkUserSetupDialog.User.vue';
|
import XUser from '@/components/MkUserSetupDialog.User.vue';
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import MkUserSetupDialog from './MkUserSetupDialog.vue';
|
import MkUserSetupDialog from './MkUserSetupDialog.vue';
|
||||||
|
@ -38,17 +38,17 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users', (req, res, ctx) => {
|
http.post('/api/users', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
userDetailed('44'),
|
userDetailed('44'),
|
||||||
userDetailed('49'),
|
userDetailed('49'),
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
rest.post('/api/pinned-users', (req, res, ctx) => {
|
http.post('/api/pinned-users', () => {
|
||||||
return res(ctx.json([
|
return HttpResponse.json([
|
||||||
userDetailed('44'),
|
userDetailed('44'),
|
||||||
userDetailed('49'),
|
userDetailed('49'),
|
||||||
]));
|
]);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { expect } from '@storybook/jest';
|
import { expect } from '@storybook/jest';
|
||||||
import { userEvent, waitFor, within } from '@storybook/testing-library';
|
import { userEvent, waitFor, within } from '@storybook/testing-library';
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../../.storybook/mocks.js';
|
import { commonHandlers } from '../../../.storybook/mocks.js';
|
||||||
import MkUrl from './MkUrl.vue';
|
import MkUrl from './MkUrl.vue';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
|
@ -59,8 +59,8 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.get('/url', (req, res, ctx) => {
|
http.get('/url', () => {
|
||||||
return res(ctx.json({
|
return HttpResponse.json({
|
||||||
title: 'Misskey Hub',
|
title: 'Misskey Hub',
|
||||||
icon: 'https://misskey-hub.net/favicon.ico',
|
icon: 'https://misskey-hub.net/favicon.ico',
|
||||||
description: 'Misskeyはオープンソースの分散型ソーシャルネットワーキングプラットフォームです。',
|
description: 'Misskeyはオープンソースの分散型ソーシャルネットワーキングプラットフォームです。',
|
||||||
|
@ -74,7 +74,7 @@ export const Default = {
|
||||||
sitename: 'misskey-hub.net',
|
sitename: 'misskey-hub.net',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
url: 'https://misskey-hub.net/',
|
url: 'https://misskey-hub.net/',
|
||||||
}));
|
});
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -49,6 +49,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
|
<MkTextarea v-model="prohibitedWords">
|
||||||
|
<template #label>{{ i18n.ts.prohibitedWords }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
|
||||||
|
</MkTextarea>
|
||||||
|
|
||||||
<MkTextarea v-model="hiddenTags">
|
<MkTextarea v-model="hiddenTags">
|
||||||
<template #label>{{ i18n.ts.hiddenTags }}</template>
|
<template #label>{{ i18n.ts.hiddenTags }}</template>
|
||||||
<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
|
<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
|
||||||
|
@ -87,6 +92,7 @@ const emailRequiredForSignup = ref<boolean>(false);
|
||||||
const approvalRequiredForSignup = ref<boolean>(false);
|
const approvalRequiredForSignup = ref<boolean>(false);
|
||||||
const bubbleTimelineEnabled = ref<boolean>(false);
|
const bubbleTimelineEnabled = ref<boolean>(false);
|
||||||
const sensitiveWords = ref<string>('');
|
const sensitiveWords = ref<string>('');
|
||||||
|
const prohibitedWords = ref<string>('');
|
||||||
const hiddenTags = ref<string>('');
|
const hiddenTags = ref<string>('');
|
||||||
const preservedUsernames = ref<string>('');
|
const preservedUsernames = ref<string>('');
|
||||||
const bubbleTimeline = ref<string>('');
|
const bubbleTimeline = ref<string>('');
|
||||||
|
@ -99,6 +105,7 @@ async function init() {
|
||||||
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
||||||
approvalRequiredForSignup.value = meta.approvalRequiredForSignup;
|
approvalRequiredForSignup.value = meta.approvalRequiredForSignup;
|
||||||
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
||||||
|
prohibitedWords.value = meta.prohibitedWords.join('\n');
|
||||||
hiddenTags.value = meta.hiddenTags.join('\n');
|
hiddenTags.value = meta.hiddenTags.join('\n');
|
||||||
preservedUsernames.value = meta.preservedUsernames.join('\n');
|
preservedUsernames.value = meta.preservedUsernames.join('\n');
|
||||||
tosUrl.value = meta.tosUrl;
|
tosUrl.value = meta.tosUrl;
|
||||||
|
@ -115,6 +122,7 @@ function save() {
|
||||||
tosUrl: tosUrl.value,
|
tosUrl: tosUrl.value,
|
||||||
privacyPolicyUrl: privacyPolicyUrl.value,
|
privacyPolicyUrl: privacyPolicyUrl.value,
|
||||||
sensitiveWords: sensitiveWords.value.split('\n'),
|
sensitiveWords: sensitiveWords.value.split('\n'),
|
||||||
|
prohibitedWords: prohibitedWords.value.split('\n'),
|
||||||
hiddenTags: hiddenTags.value.split('\n'),
|
hiddenTags: hiddenTags.value.split('\n'),
|
||||||
preservedUsernames: preservedUsernames.value.split('\n'),
|
preservedUsernames: preservedUsernames.value.split('\n'),
|
||||||
bubbleInstances: bubbleTimeline.value.split('\n'),
|
bubbleInstances: bubbleTimeline.value.split('\n'),
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { rest } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed } from '../../../.storybook/fakes.js';
|
import { userDetailed } from '../../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../../.storybook/mocks.js';
|
import { commonHandlers } from '../../../.storybook/mocks.js';
|
||||||
import home_ from './home.vue';
|
import home_ from './home.vue';
|
||||||
|
@ -39,12 +39,13 @@ export const Default = {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...commonHandlers,
|
...commonHandlers,
|
||||||
rest.post('/api/users/notes', (req, res, ctx) => {
|
http.post('/api/users/notes', () => {
|
||||||
return res(ctx.json([]));
|
return HttpResponse.json([]);
|
||||||
}),
|
}),
|
||||||
rest.get('/api/charts/user/notes', (req, res, ctx) => {
|
http.get('/api/charts/user/notes', ({ request }) => {
|
||||||
const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
|
const url = new URL(request.url);
|
||||||
return res(ctx.json({
|
const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300);
|
||||||
|
return HttpResponse.json({
|
||||||
total: Array.from({ length }, () => 0),
|
total: Array.from({ length }, () => 0),
|
||||||
inc: Array.from({ length }, () => 0),
|
inc: Array.from({ length }, () => 0),
|
||||||
dec: Array.from({ length }, () => 0),
|
dec: Array.from({ length }, () => 0),
|
||||||
|
@ -54,11 +55,12 @@ export const Default = {
|
||||||
renote: Array.from({ length }, () => 0),
|
renote: Array.from({ length }, () => 0),
|
||||||
withFile: Array.from({ length }, () => 0),
|
withFile: Array.from({ length }, () => 0),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
}),
|
}),
|
||||||
rest.get('/api/charts/user/pv', (req, res, ctx) => {
|
http.get('/api/charts/user/pv', ({ request }) => {
|
||||||
const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
|
const url = new URL(request.url);
|
||||||
return res(ctx.json({
|
const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300);
|
||||||
|
return HttpResponse.json({
|
||||||
upv: {
|
upv: {
|
||||||
user: Array.from({ length }, () => 0),
|
user: Array.from({ length }, () => 0),
|
||||||
visitor: Array.from({ length }, () => 0),
|
visitor: Array.from({ length }, () => 0),
|
||||||
|
@ -67,7 +69,7 @@ export const Default = {
|
||||||
user: Array.from({ length }, () => 0),
|
user: Array.from({ length }, () => 0),
|
||||||
visitor: Array.from({ length }, () => 0),
|
visitor: Array.from({ length }, () => 0),
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -68,10 +68,7 @@ export async function initHighlighter() {
|
||||||
themes,
|
themes,
|
||||||
langs: [
|
langs: [
|
||||||
import('shiki/langs/javascript.mjs'),
|
import('shiki/langs/javascript.mjs'),
|
||||||
{
|
aiScriptGrammar.default as unknown as LanguageRegistration,
|
||||||
aliases: ['is', 'ais'],
|
|
||||||
...aiScriptGrammar.default,
|
|
||||||
} as unknown as LanguageRegistration,
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,34 @@ describe('MkUrlPreview', () => {
|
||||||
assert.strictEqual(iframe?.allow, 'fullscreen;web-share');
|
assert.strictEqual(iframe?.allow, 'fullscreen;web-share');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('A Summaly proxy response without allow falls back to the default', async () => {
|
||||||
|
const iframe = await renderAndOpenPreview({
|
||||||
|
url: 'https://example.local',
|
||||||
|
player: {
|
||||||
|
url: 'https://example.local/player',
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
allow: undefined as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.exists(iframe, 'iframe should exist');
|
||||||
|
assert.strictEqual(iframe?.allow, 'autoplay;encrypted-media;fullscreen');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Filtering the allow list from the Summaly proxy', async () => {
|
||||||
|
const iframe = await renderAndOpenPreview({
|
||||||
|
url: 'https://example.local',
|
||||||
|
player: {
|
||||||
|
url: 'https://example.local/player',
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
allow: ['autoplay', 'camera', 'fullscreen'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.exists(iframe, 'iframe should exist');
|
||||||
|
assert.strictEqual(iframe?.allow, 'autoplay;fullscreen');
|
||||||
|
});
|
||||||
|
|
||||||
test('Having a player width should keep the fixed aspect ratio', async () => {
|
test('Having a player width should keep the fixed aspect ratio', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreview({
|
||||||
url: 'https://example.local',
|
url: 'https://example.local',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2024.2.0-beta.10",
|
"version": "2024.2.0-beta.11",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"types": "./built/dts/index.d.ts",
|
"types": "./built/dts/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@swc/jest": "0.2.31",
|
"@swc/jest": "0.2.31",
|
||||||
"@types/jest": "29.5.11",
|
"@types/jest": "29.5.11",
|
||||||
"@types/node": "20.11.10",
|
"@types/node": "20.11.17",
|
||||||
"@typescript-eslint/eslint-plugin": "6.18.1",
|
"@typescript-eslint/eslint-plugin": "6.18.1",
|
||||||
"@typescript-eslint/parser": "6.18.1",
|
"@typescript-eslint/parser": "6.18.1",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
|
|
|
@ -4793,6 +4793,7 @@ export type operations = {
|
||||||
hiddenTags: string[];
|
hiddenTags: string[];
|
||||||
blockedHosts: string[];
|
blockedHosts: string[];
|
||||||
sensitiveWords: string[];
|
sensitiveWords: string[];
|
||||||
|
prohibitedWords: string[];
|
||||||
bannedEmailDomains?: string[];
|
bannedEmailDomains?: string[];
|
||||||
preservedUsernames: string[];
|
preservedUsernames: string[];
|
||||||
bubbleInstances: string[];
|
bubbleInstances: string[];
|
||||||
|
@ -8814,6 +8815,7 @@ export type operations = {
|
||||||
hiddenTags?: string[] | null;
|
hiddenTags?: string[] | null;
|
||||||
blockedHosts?: string[] | null;
|
blockedHosts?: string[] | null;
|
||||||
sensitiveWords?: string[] | null;
|
sensitiveWords?: string[] | null;
|
||||||
|
prohibitedWords?: string[] | null;
|
||||||
themeColor?: string | null;
|
themeColor?: string | null;
|
||||||
mascotImageUrl?: string | null;
|
mascotImageUrl?: string | null;
|
||||||
bannerUrl?: string | null;
|
bannerUrl?: string | null;
|
||||||
|
|
613
pnpm-lock.yaml
613
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue