1
0
mirror of https://github.com/misskey-dev/misskey synced 2024-11-28 06:48:42 +09:00

Merge branch 'develop'

This commit is contained in:
syuilo 2021-03-22 15:27:08 +09:00
commit 52d577c7dd
171 changed files with 3365 additions and 1776 deletions

View File

@ -1,3 +0,0 @@
.[]
.head
.label

View File

@ -1,2 +0,0 @@
.links
.next

View File

@ -1,39 +0,0 @@
(
.data |
map(
select(
.relationships
.currently_entitled_tiers
.data[]
)
) |
map(
.relationships
.user
.data
.id
)
) as $data |
.included |
map(
select(
.id as $id |
$data |
contains(
[
$id
]
)
)
) |
map(
.attributes |
[
.full_name,
.thumb_url,
.url
] |
@tsv
) |
.[] |
@text

View File

@ -1,87 +0,0 @@
#!/usr/bin/env bash
# __MISSKEY_BEARER_TOKEN=
# __MISSKEY_CAMPAIGN_ID=
# __MISSKEY_GITHUB_TOKEN=
# __MISSKEY_HEAD=syuilo:patch-autogen
# __MISSKEY_REPO=syuilo/misskey
# __MISSKEY_BRANCH=develop
test "$(curl -LSs -w '\n' -- "https://api.github.com/repos/$REPO/pulls?access_token=$__MISSKEY_GITHUB_TOKEN" | jq -r -f check_pr.jq | grep $__MISSKEY_HEAD)" && exit 1
cd "$(dirname $0)/.." && \
touch null.cache && \
rm *.cache && \
git checkout $__MISSKEY_BRANCH && \
git pull origin $__MISSKEY_BRANCH && \
git pull upstream $__MISSKEY_BRANCH && \
git stash && \
git rebase -f upstream/$__MISSKEY_BRANCH && \
git branch patch-autogen && \
git checkout patch-autogen && \
git reset --hard HEAD || \
exit 1
touch patreon.md.cache && \
rm patreon.md.cache && \
echo '<!-- PATREON_START -->' > patreon.md.cache && \
url="https://www.patreon.com/api/oauth2/v2/campaigns/$__MISSKEY_CAMPAIGN_ID/members?include=currently_entitled_tiers,user&fields%5Btier%5D=title&fields%5Buser%5D=full_name,thumb_url,url,hide_pledges"
while :
do
touch patreon.raw.cache && \
rm patreon.raw.cache && \
curl -LSs -w '\n' -H "Authorization: Bearer $__MISSKEY_BEARER_TOKEN" -- $url > patreon.raw.cache && \
touch patreon.cache && \
rm patreon.cache && \
cat patreon.raw.cache | \
jq -r -f patreon.jq >> patreon.cache && \
echo '<table><tr>' >> patreon.md.cache && \
cat patreon.cache | \
awk -F'\t' '{print $2,$1}' | \
sed -e 's/ /\\" alt=\\"/' | \
xargs -I% echo '<td><img src="%" width="100"></td>' >> patreon.md.cache && \
echo '</tr><tr>' >> patreon.md.cache && \
cat patreon.cache | \
awk -F'\t' '{print $3,$1}' | \
sed -e 's/ /\\">/' | \
xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \
echo '</tr></table>' >> patreon.md.cache || \
exit 1
new_url="$(cat patreon.raw.cache | jq -r -f next_url.jq)"
test "$new_url" = 'null' && \
break || \
URL="$url"
done
ignore= && \
echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \
touch README.md && \
touch .autogen/README.md && \
rm .autogen/README.md && \
mv README.md .autogen/README.md && \
cat .autogen/README.md | while IFS= read line;
do
if [[ -z "$ignore" ]]
then
if [[ "$line" = '<!-- PATREON_START -->' ]]
then
ignore='PATREON_INSIDE'
else
echo "$line" >> README.md
fi
else
if [[ "$LINE" = '<!-- PATREON_END -->' ]]
then
ignore=
cat patreon.md.cache >> README.md
fi
fi
done
cat patreon.md.cache
touch null.cache && \
rm *.cache && \
diff .autogen/README.md README.md > diff.cache
cat diff.cache && \
test 4 -lt $(cat diff.cache | wc -l) && \
git add README.md && \
git commit -m 'Update README.md [AUTOGEN]' && \
git push -f origin patch-autogen && \
curl -LSs -w '\n' -X POST -d '{"title":"[AUTOMATED] Update README.md","body":"*This pull request was created by a tool.*","head":"'$__MISSKEY_HEAD'","base":"'$__MISSKEY_BRANCH'"}' -- "https://api.github.com/repos/$__MISSKEY_REPO/pulls?access_token=$__MISSKEY_GITHUB_TOKEN"
git stash
git checkout $__MISSKEY_BRANCH
git branch -D patch-autogen

View File

@ -3,29 +3,11 @@
"parser": "@typescript-eslint/parser"
},
"extends": [
"eslint:recommended",
"plugin:vue/recommended"
"eslint:recommended"
],
"rules": {
"vue/require-v-for-key": 0,
"vue/max-attributes-per-line": 0,
"vue/html-indent": 0,
"vue/html-self-closing": 0,
"vue/no-unused-vars": 0,
"vue/attributes-order": 0,
"vue/require-prop-types": 0,
"vue/require-default-prop": 0,
"vue/html-closing-bracket-spacing": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/no-v-html": 0,
"no-console": 0,
"no-unused-vars": 0,
"no-empty": 0
},
"globals": {
"ENV": true,
"VERSION": true,
"API": true,
"LANGS": true
}
}

1
CHANGELOG.md Normal file
View File

@ -0,0 +1 @@
see [releases](https://github.com/syuilo/misskey/releases)

BIN
assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -19,6 +19,6 @@
</head>
<body>
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-rc.50/bundles/redoc.standalone.js" integrity="sha256-WJbngBWN9vp6vkEuzeoSj5tE5saW9Hfj6/SinkzhL2s=" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -14,7 +14,7 @@ const locales: { [x: string]: any } = require('./locales');
const meta = require('./package.json');
gulp.task('build:ts', () => {
const tsProject = ts.createProject('./tsconfig.json');
const tsProject = ts.createProject('./src/tsconfig.json');
return tsProject
.src()
@ -64,7 +64,6 @@ gulp.task('build:client:style', () => {
gulp.task('build:copy', gulp.parallel('build:copy:locales', 'build:copy:views', 'build:client:script', 'build:client:style', 'build:copy:fonts', () =>
gulp.src([
'./src/emojilist.json',
'./src/server/web/views/**/*',
'./src/**/assets/**/*',
'!./src/client/assets/**/*'
]).pipe(gulp.dest('./built/'))
@ -78,17 +77,16 @@ gulp.task('cleanall', gulp.parallel('clean', cb =>
rimraf('./node_modules', cb)
));
gulp.task('copy:docs', () =>
gulp.src([
'./src/docs/**/*',
])
.pipe(gulp.dest('./built/assets/docs/'))
);
gulp.task('build', gulp.parallel(
'build:ts',
'build:copy',
'copy:docs',
));
gulp.task('default', gulp.task('build'));
gulp.task('watch', () => {
gulp.watch([
'./src/**/*',
'!./src/client/**/*'
], { ignoreInitial: false }, gulp.task('build'));
});

View File

@ -110,7 +110,7 @@ attachCancel: "Supprimer le fichier attaché"
markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Supprimer le marquage comme sensible"
enterFileName: "Entrer le nom du fichier"
mute: "Mettre en sourdine"
mute: "Masquer"
unmute: "Ne plus masquer"
block: "Bloquer"
unblock: "Débloquer"
@ -206,7 +206,7 @@ all: "Tous"
subscribing: "Abonné"
publishing: "Publié"
notResponding: "Ne répond pas"
instanceFollowing: "Suivre une instance"
instanceFollowing: "Abonnements de l'instance"
instanceFollowers: "Abonné·e·s de linstance"
instanceUsers: "Utilisateur·rice·s de cette linstance"
changePassword: "Modifier votre mot de passe"
@ -317,12 +317,12 @@ disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur
registration: "Sinscrire"
enableRegistration: "Autoriser les nouvelles inscriptions"
invite: "Inviter"
proxyRemoteFiles: "Proxy fichiers distants"
proxyRemoteFiles: "Utiliser les fichiers distants comme proxy"
proxyRemoteFilesDescription: "Si vous activez ce paramètre, les fichiers distants non stockés ou supprimés en raison d'une capacité excédentaire seront affichés via un proxy local et généreront une miniature. Cela n'affectera pas le stockage du serveur."
driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local"
driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant"
inMb: "en mégaoctets"
iconUrl: "URL de limage de lavatar"
iconUrl: "URL de l'icône"
bannerUrl: "URL de limage de la bannière"
basicInfo: "Informations basiques"
pinnedUsers: "Utilisateur·rice épinglé·e"
@ -491,7 +491,7 @@ objectStorageUseProxyDesc: "Désactivez cette option si vous n'utilisez pas Prox
objectStorageSetPublicRead: "Régler sur « public » lors de l'envoi"
serverLogs: "Journal du serveur"
deleteAll: "Supprimer tout"
showFixedPostForm: "Afficher le formulaire en haut du fil d'actualité"
showFixedPostForm: "Afficher le formulaire de publication en haut du fil d'actualité"
newNoteRecived: "Vous avez reçu une nouvelle note"
sounds: "Sons"
listen: "Écouter"
@ -616,12 +616,14 @@ openInNewTab: "Ouvrir dans un nouvel onglet"
openInSideView: "Ouvrir en vue latérale"
defaultNavigationBehaviour: "Navigation par défaut"
editTheseSettingsMayBreakAccount: "La modification de ces paramètres peut endommager votre compte."
instanceTicker: "Nom de l'instance d'origine des notes"
waitingFor: "En attente de {x}"
random: "Aléatoire"
system: "Système"
switchUi: "Modifier l'interface utilisateur"
desktop: "Bureau"
clip: "Clip"
createNew: "Créer nouveau"
optional: "Facultatif"
createNewClip: "Créer un nouveau clip"
public: "Public"
@ -806,6 +808,7 @@ _reversi:
canPutEverywhere: "Les pions peuvent être placés partout "
_instanceTicker:
none: "Cacher "
remote: "Montrer pour les utilisateur·ice·s distant·e·s"
always: "Toujours afficher"
_serverDisconnectedBehavior:
reload: "Rechargement automatique"
@ -823,11 +826,12 @@ _channel:
notesCount: "{n} Notes"
_sidebar:
full: "Complet"
icon: "Avatar"
icon: "Icônes"
hide: "Masquer"
_wordMute:
muteWords: "Mots à filtrer"
muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez les mots-clés entre barres obliques."
softDescription: "Masquez les notes de votre fil selon les paramètres que vous définissez."
hardDescription: "Empêchez votre fil de charger les notes selon les paramètres que vous définissez. Cette action est irréversible : si vous modifiez ces paramètres plus tard, les notes précédemment filtrées ne seront pas récupérées."
soft: "Doux"
@ -902,6 +906,8 @@ _sfx:
chatBg: "Discuter (De fond)"
antenna: "Réception de lantenne"
channel: "Notifications de canal"
reversiPutBlack: "Reversi : les pions noirs ont joué"
reversiPutWhite: "Reversi : les pions blancs ont joué"
_ago:
unknown: "Inconnu"
future: "Futur"
@ -953,12 +959,12 @@ _2fa:
_permissions:
"read:account": "Afficher les informations du compte"
"write:account": "Mettre à jour les informations de votre compte"
"read:blocks": "Voir les blocs"
"write:blocks": "Écrire des blocs"
"read:blocks": "Voir les comptes bloqués"
"write:blocks": "Gérer les comptes bloqués"
"read:drive": "Parcourir le Drive"
"write:drive": "Écrire sur le Drive"
"read:favorites": "Afficher les favoris"
"write:favorites": "Écrire des favoris"
"write:favorites": "Gérer les favoris"
"read:following": "Voir les informations de vos abonnements"
"write:following": "Abonnements/Se désabonner"
"read:messaging": "Cherche à discuter"
@ -1012,7 +1018,7 @@ _widgets:
photos: "Photos"
digitalClock: "Horloge numérique"
federation: "Fédération"
postForm: "Formulaire à publier"
postForm: "Formulaire de publication"
slideshow: "Diaporama"
button: "Bouton"
onlineUsers: "Utilisateurs en ligne"
@ -1083,8 +1089,8 @@ _profile:
_exportOrImport:
allNotes: "Toutes les notes"
followingList: "Abonnements"
muteList: "Liste des comptes maqués"
blockingList: "Bloquer"
muteList: "Comptes masqués"
blockingList: "Comptes bloqués"
userLists: "Listes"
_charts:
federationInstancesIncDec: "Variation du nombre des instances fédérées"
@ -1228,17 +1234,17 @@ _pages:
if: "Si"
_if:
variable: "Variables"
post: "Formulaire à publier"
post: "Formulaire de publication"
_post:
text: "Contenu"
attachCanvasImage: "Publier avec Toile comme image"
canvasId: "Toile ID"
textInput: "Entrée de textuelle"
textInput: "Entrée textuelle"
_textInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
textareaInput: "Entrée de textuelle multiligne"
textareaInput: "Entrée textuelle multi-ligne"
_textareaInput:
name: "Nom de la variable"
text: "Titre"
@ -1253,10 +1259,12 @@ _pages:
id: "Toile ID"
width: "Largeur"
height: "Hauteur"
note: "Note intégrée"
_note:
id: "Identifiant de la note"
idDescription: "Pour configurer la note, vous pouvez aussi coller ici l'URL correspondante."
detailed: "Afficher les détails"
switch: "Basculer"
switch: "Interrupteur"
_switch:
name: "Nom de la variable"
text: "Titre"
@ -1265,16 +1273,16 @@ _pages:
_counter:
name: "Nom de la variable"
text: "Titre"
inc: "Augmenter le chiffre"
inc: "Augmenter de"
_button:
text: "Titre"
colored: "Coloré"
action: "L'opération lorsque le bouton sera pressé"
action: "Opération à effectuer lorsque le bouton est pressé"
_action:
dialog: "Afficher une fenêtre de dialogue"
_dialog:
content: "Contenu"
resetRandom: "Réinitialiser le nombre aléatoire"
resetRandom: "Réinitialiser un nombre aléatoire"
pushEvent: "Envoyer un évènement"
_pushEvent:
event: "Nom de lévènement"
@ -1288,7 +1296,7 @@ _pages:
_radioButton:
name: "Nom de la variable"
title: "Titre"
values: "Choix séparés par des sauts de ligne"
values: "Liste des choix (un par ligne)"
default: "Valeur par défaut"
script:
categories:
@ -1304,7 +1312,7 @@ _pages:
list: "Listes"
blocks:
text: "Texte"
multiLineText: "Texte (Multi-lignes)"
multiLineText: "Texte (multi-ligne)"
textList: "Liste de texte"
_textList:
info: "Veuillez séparer chaque entrée avec un saut de ligne"
@ -1347,10 +1355,10 @@ _pages:
_mod:
arg1: "A"
arg2: "B"
round: "Décimal rond"
round: "Arrondir les décimales"
_round:
arg1: "Numérique"
eq: "A et B sont équivalents"
eq: "A et B sont égaux"
_eq:
arg1: "A"
arg2: "B"
@ -1366,7 +1374,7 @@ _pages:
_or:
arg1: "A"
arg2: "B"
lt: "A est plus petit que B"
lt: "A est inférieur à B"
_lt:
arg1: "A"
arg2: "B"
@ -1374,7 +1382,7 @@ _pages:
_gt:
arg1: "A"
arg2: "B"
ltEq: "A est plus petit ou égal à B"
ltEq: "A est inférieur ou égal à B"
_ltEq:
arg1: "A"
arg2: "B"
@ -1440,7 +1448,7 @@ _pages:
numberToString: "Convertir du numérique en texte"
_numberToString:
arg1: "Numérique"
splitStrByLine: "Séparer le texte par lignes"
splitStrByLine: "Séparer le texte par des sauts de lignes"
_splitStrByLine:
arg1: "Texte"
ref: "Variables"
@ -1448,7 +1456,7 @@ _pages:
fn: "Fonction"
_fn:
slots: "Slots"
slots-info: "Veuillez délimiter chaque slot par un saut de ligne"
slots-info: "Veuillez insérer un seul slot par ligne"
arg1: "Sortie"
for: "Répéter"
_for:
@ -1482,18 +1490,19 @@ _notification:
youWereFollowed: "Vous suit"
youReceivedFollowRequest: "Vous avez reçu une demande dabonnement"
yourFollowRequestAccepted: "Votre demande dabonnement a été accepté"
youWereInvitedToGroup: "Invité au groupe"
youWereInvitedToGroup: "Invité·e au groupe"
_types:
all: "Toutes"
follow: "Abonnements"
mention: "Mentionner"
mention: "Mentions"
reply: "Réponses"
renote: "Partager"
quote: "Citer"
reaction: "Réactions"
pollVote: "Votes dans des sondages"
receiveFollowRequest: "Demande d'abonnement reçue"
followRequestAccepted: "Demande d'abonnement acceptée"
groupInvited: "Invité aux groupes"
groupInvited: "Invitation à un groupe"
app: "Notifications provenant des apps"
_deck:
alwaysShowMainColumn: "Toujours afficher la colonne principale"

View File

@ -1,6 +1,7 @@
---
_lang_: "Italiano"
headlineMisskey: "Rete collegata tramite note"
introMisskey: "Benvenut@! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \nScrivi \"note\" per condividere ciò che sta succedendo adesso o per dire a tutti qualcosa di te. 📡\nGrazie alla funzione \"reazioni\" puoi anche mandare reazioni rapide alle note delle altre persone del Fediverso. 👍\nEsplora un nuovo mondo! 🚀"
monthAndDay: "{day}/{month}"
search: "Cerca"
notifications: "Notifiche"
@ -11,7 +12,7 @@ ok: "OK"
gotIt: "Capito!"
cancel: "Annulla"
enterUsername: "Inserisci un nome utente"
renotedBy: "Condiviso da {user}"
renotedBy: "Rinotato da {user}"
noNotes: "Nessuna nota!"
noNotifications: "Nessuna notifica"
instance: "Istanza"
@ -21,6 +22,7 @@ otherSettings: "Altre impostazioni"
openInWindow: "Apri in una finestra"
profile: "Profilo"
timeline: "Timeline"
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
login: "Accedi"
loggingIn: "Accesso in corso..."
logout: "Esci"
@ -32,15 +34,16 @@ addUser: "Aggiungi utente"
favorite: "Preferiti"
favorites: "Preferiti"
unfavorite: "Rimuovi nota dai preferiti"
favorited: "Aggiunta ai preferiti."
alreadyFavorited: "Già tra i preferiti."
cantFavorite: "Impossibile aggiungere ai Preferiti."
favorited: "Aggiunta ai tuoi preferiti."
alreadyFavorited: "Già tra i tuoi preferiti."
cantFavorite: "Impossibile aggiungere la nota ai preferiti."
pin: "Fissa sul profilo"
unpin: "Non fissare sul profilo"
copyContent: "Copia il contenuto"
copyLink: "Copia link"
copyLink: "Copia il link"
delete: "Elimina"
deleteAndEdit: "Elimina & Modifica"
deleteAndEdit: "Elimina e modifica"
deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verrano eliminate anche tutte le reazioni, Rinote e risposte collegate."
addToList: "Aggiungi alla lista"
sendMessage: "Invia messaggio"
copyUsername: "Copia nome utente"
@ -49,65 +52,104 @@ reply: "Rispondi"
loadMore: "Mostra di più"
showMore: "Mostra di più"
youGotNewFollower: "Ha iniziato a seguirti"
receiveFollowRequest: "Nuova richiesta di essere seguito"
receiveFollowRequest: "Hai ricevuto una richiesta di follow."
followRequestAccepted: "Richiesta di follow accettata"
mention: "Menzioni"
mentions: "Menzioni"
directNotes: "Note dirette"
importAndExport: "Importa ed Esporta"
importAndExport: "Importa ed esporta"
import: "Importa"
export: "Esporta"
files: "Allegato"
files: "Allegati"
download: "Scarica"
driveFileDeleteConfirm: "Vuoi davvero eliminare il file「{name}? Anche gli allegati verranno eliminati."
unfollowConfirm: "Vuoi davvero smettere di seguire {name}?"
exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive."
importRequested: "Hai richiesto un'importazione. Può volerci tempo. "
lists: "Liste"
noLists: "Qui non c'è ancora niente"
noLists: "Nessuna lista"
note: "Nota"
notes: "Note"
following: "Seiguiti"
followers: "Seguaci"
following: "Follows"
followers: "Followers"
followsYou: "Ti segue"
createList: "Crea una nuova lista"
manageLists: "Modifica lista"
createList: "Aggiungi una nuova lista"
manageLists: "Gestisci liste"
error: "Errore"
somethingHappened: "Qualcosa è andato storto."
somethingHappened: "Si è verificato un problema"
retry: "Riprova"
enterListName: "Inserisci il nome della lista"
pageLoadError: "Caricamento pagina non riuscito. "
enterListName: "Nome della lista"
privacy: "Privacy"
makeFollowManuallyApprove: "Richiedi di approvare i follower manualmente"
defaultNoteVisibility: "Privacy predefinita delle note"
follow: "Segui"
followRequest: "Richiesta di seguire"
followRequests: "Richiesta di seguire"
followRequest: "Richiesta di follow"
followRequests: "Richieste di follow"
unfollow: "Smetti di seguire"
followRequestPending: "In sospeso"
followRequestPending: "La richiesta di follow deve essere approvata"
enterEmoji: "Inserisci emoji"
renote: "Rinota"
unrenote: "Annulla rinota"
renoted: "Condiviso!"
cantReRenote: "È impossibile rinota una condivisione."
renoted: "Rinotato!"
cantRenote: "È impossibile rinotare questa nota."
cantReRenote: "È impossibile rinotare una Rinota."
quote: "Cita"
pinnedNote: "Nota fissata"
pinned: "Fissa sul profilo"
you: "Tu"
clickToShow: "Clicca per visualizzare"
sensitive: "Contenuto sensibile"
add: "Aggiungi"
reaction: "Reazione"
reactionSettingDescription: "Scegli le reazioni che preferisci e fissale nel pannello di reazioni."
reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
attachCancel: "Rimuovi allegato"
markAsSensitive: "Segna come sensibile"
unmarkAsSensitive: "Segna come non sensibile"
enterFileName: "Nome del file"
mute: "Silenzia"
unmute: "Riattiva"
block: "Blocca"
unblock: "Sblocca"
suspend: "Sospendi"
unsuspend: "Annulla la sospensione dell'account"
blockConfirm: "Vuoi bloccare?"
unblockConfirm: "Vuoi sbloccare?"
blockConfirm: "Vuoi davvero bloccare l'account?"
unblockConfirm: "Vuoi davvero sbloccare l'account?"
suspendConfirm: "Vuoi davvero sospendere questo account?"
unsuspendConfirm: "Vuoi annullare la sospensione dell'account?"
selectList: "Seleziona una lista"
selectAntenna: "Scegli un'antenna"
selectWidget: "Seleziona widget"
editWidgets: "Modifica i widget"
editWidgetsExit: "Modifica fine"
customEmojis: "Emoji personalizzati"
emoji: "Emoji"
addAcount: "Aggiungi un account esistente"
emojiName: "Nome dell'emoji"
emojiUrl: "URL dell'emoji"
addEmoji: "Aggiungi un emoji"
settingGuide: "Configurazione suggerita"
cacheRemoteFiles: "Memorizzazione nella cache dei file remoti"
flagAsBot: "Io sono un robot"
flagAsBotDescription: "Se l'account esegue principalmente operazioni automatiche, attiva quest'opzione. Quando attivata, opera come un segnalatore per gli altri sviluppatori allo scopo di prevenire catene dinterazione senza fine con altri bot, e di adeguare i sistemi interni di Misskey perché trattino questo account come un bot."
flagAsCat: "Io sono un gatto"
flagAsCatDescription: "Abilita l'opzione \"Io sono un gatto\" per l'account."
autoAcceptFollowed: "Accetta automaticamente le richieste di follow da utenti che già segui"
addAcount: "Aggiungi account"
loginFailed: "Accesso non riuscito"
showOnRemote: "Sfoglia sull'istanza remota"
general: "Generali"
wallpaper: "Sfondo"
setWallpaper: "Imposta sfondo"
removeWallpaper: "Elimina lo sfondo"
searchWith: "Cerca: {q}"
youHaveNoLists: "Non hai ancora creato nessuna lista"
followConfirm: "Sei sicur@ di voler seguire {name}?"
proxyAccount: "Account proxy"
host: "Server remoto"
selectUser: "Seleziona utente"
recipient: "Destinatario"
annotation: "Descrizione"
federation: "Federazione"
instances: "Istanza"
@ -115,36 +157,62 @@ storageUsage: "Volume di dischi"
charts: "Grafici"
perHour: "All'ora"
perDay: "al giorno"
blockThisInstance: "Blocca l'istanza"
operations: "Operazioni"
software: "Software"
version: "Versione"
metadata: "Metadato"
withNFiles: "{n} file in allegato"
monitor: "Monitorare"
jobQueue: "Coda di lavoro"
cpuAndMemory: "CPU e Memoria"
network: "Rete"
disk: "Disco"
instanceInfo: "Informazioni di istanza"
instanceInfo: "Informazioni sull'istanza"
statistics: "Statistiche"
clearQueue: "Cancella coda"
clearQueueConfirmTitle: "Cancella coda?"
blockedInstances: "Istanza bloccati"
muteAndBlock: "Silenziamento e blocco"
clearQueue: "Svuota coda"
clearQueueConfirmTitle: "Vuoi davvero svuotare la coda?"
clearCachedFiles: "Svuota cache"
clearCachedFilesConfirm: "Vuoi davvero svuotare la cache da tutti i file remoti?"
blockedInstances: "Istanze bloccate"
blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza."
muteAndBlock: "Silenziati / Bloccati"
mutedUsers: "Account silenziati"
blockedUsers: "Account bloccati"
noUsers: "Nessun utente trovato"
editProfile: "Modifica profilo"
noteDeleteConfirm: "Eliminare questo Nota?"
pinLimitExceeded: "Non puoi fissare altre note "
intro: "L'installazione di Misskey è finita! Si prega di creare un account amministratore."
done: "Fine"
processing: "In elaborazione"
blocked: "Bloccati"
preview: "Anteprima"
default: "Predefinito"
noCustomEmojis: "Nessun emoji"
noJobs: "Nessun lavoro"
federating: "Federando"
blocked: "Bloccato"
suspended: "Sospes@"
all: "Tutti"
subscribing: "Iscrivendo"
publishing: "Pubblicando"
notResponding: "Nessuna risposta"
instanceFollowing: "Seguiti dall'istanza"
instanceFollowers: "Followers dell'istanza"
instanceUsers: "Utenti dell'istanza"
changePassword: "Aggiorna Password"
security: "Sicurezza"
retypedNotMatch: "Le password non corrispondono."
currentPassword: "Password attuale"
newPassword: "Nuova Password"
newPasswordRetype: "Conferma nuova password"
newPasswordRetype: "Conferma password"
attachFile: "Allega file"
more: "Altri!"
featured: "Tendenze"
usernameOrUserId: "Nome utente o ID utente"
noSuchUser: "Nessun utente trovato"
lookup: "Cercare"
announcements: "Annuncio"
announcements: "Annunci"
imageUrl: "URL dell'immagine"
remove: "Elimina"
removed: "Il tuo Tweet è stato eliminato"
@ -154,31 +222,66 @@ resetAreYouSure: "Reimposta"
saved: "Salvato"
messaging: "Messaggi"
upload: "Carica"
fromDrive: "Dal Drive"
fromUrl: "Dall'URL"
uploadFromUrl: "Incolla URL immagine"
uploadFromUrlDescription: "URL del file che vuoi caricare"
uploadFromUrlRequested: "Caricamento richiesto"
uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo."
explore: "Esplora"
games: "Misskey Giochi"
messageRead: "Visualizzato"
noMoreHistory: "Non c'è più cronologia da visualizzare"
startMessaging: "Nuovo messaggio"
nUsersRead: "Letto da {n} persone"
agreeTo: "Sono d'accordo con {0}"
tos: "Termini di servizio"
start: "Inizia!"
home: "Home"
remoteUserCaution: "Può darsi che le informazioni siano incomplete perché questo è un utente remoto."
activity: "Attività"
images: "Immagini"
birthday: "Compleanno"
yearsOld: "{age}Anni"
registeredDate: "Iscrizione a.."
location: "Posizione"
theme: "Tema"
themeForLightMode: "Tema da utilizzare per il modo chiaro"
themeForDarkMode: "Tema da utilizzare per il modo scuro"
light: "Chiaro"
dark: "Scuro"
lightThemes: "Tema Chiaro"
darkThemes: "Tema Scuro"
syncDeviceDarkMode: "Sincronizza il tema scuro con le impostazioni del dispositivo"
drive: "Drive"
fileName: "Nome dell'allegato"
selectFile: "Scelta allegato"
selectFiles: "Scelta allegato"
selectFolder: "Seleziona cartella"
selectFolders: "Seleziona cartella"
renameFile: "Rinomina file"
folderName: "Nome della cartella"
createFolder: "Nuova cartella"
renameFolder: "Rinominare cartella"
deleteFolder: "Elimina cartella"
addFile: "Allega"
emptyDrive: "Il Drive è vuoto"
emptyFolder: "La cartella è vuota"
unableToDelete: "Eliminazione impossibile"
inputNewFileName: "Inserisci nome del nuovo file"
inputNewFolderName: "Inserisci nome della nuova cartella"
circularReferenceFolder: "La cartella di destinazione è una sottocartella della cartella che vuoi spostare."
hasChildFilesOrFolders: "Impossibile eliminare la cartella perché non è vuota"
copyUrl: "Copia URL"
rename: "Modifica nome"
avatar: "Foto del profilo"
banner: "Foto d'intestazione"
banner: "Intestazione"
nsfw: "Contenuti sensibili"
whenServerDisconnected: "Quando la connessione col server è persa"
disconnectedFromServer: "Disconness@ dal server"
reload: "Ricarica"
doNothing: "Nessun'azione"
reloadConfirm: "Vuoi ricaricare?"
watch: "Osserva"
unwatch: "Smetti di Osserva"
accept: "Accetta"
@ -195,21 +298,64 @@ today: "Oggi"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Pagine"
integration: "App collegate"
connectSerice: "Connetti"
disconnectSerice: "Disconnetti"
enableLocalTimeline: "Abilita Timeline locale"
enableGlobalTimeline: "Abilita Timeline federata"
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e i moderatori potranno sempre accederci."
registration: "Iscriviti"
enableRegistration: "Permettere nuove registrazioni"
invite: "Invita"
bannerUrl: "indirizzo Foto d'intestazione"
proxyRemoteFiles: "Usare file remoti come proxy"
driveCapacityPerLocalAccount: "Volume del Drive per utente locale"
driveCapacityPerRemoteAccount: "Volume del Drive per utente remoto"
inMb: "in Megabytes"
iconUrl: "URL di icona (favicon, ecc.)"
bannerUrl: "URL dell'immagine d'intestazione"
basicInfo: "Informazioni fondamentali"
pinnedUsers: "Utenti in evidenza"
pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina \"Esplora\", un@ per riga."
pinnedPages: "Pagine in evidenza"
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
pinnedClipId: "ID della clip in evidenza"
pinnedNotes: "Nota fissata"
hcaptcha: "hCaptcha"
enableHcaptcha: "Abilita hCaptcha"
hcaptchaSiteKey: "Chiave del sito"
hcaptchaSecretKey: "Chiave segreta"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Abilita reCAPTCHA"
recaptchaSiteKey: "Chiave del sito"
recaptchaSecretKey: "Chiave segreta"
antennas: "Antenne"
manageAntennas: "Gestore delle antenne"
name: "Nome"
antennaSource: "Fonte dell'antenna"
antennaKeywords: "Parole chiavi da ricevere"
antennaExcludeKeywords: "Parole chiavi da escludere"
antennaKeywordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con un'interruzzione riga indica la condizione \"O\"."
notifyAntenna: "Invia notifiche delle nuove note"
withFileAntenna: "Solo note con file in allegato"
serviceworker: "ServiceWorker"
enableServiceworker: "Abilita ServiceWorker"
antennaUsersDescription: "Inserisci solo un nome utente per riga"
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
withReplies: "Includere le risposte"
connectedTo: "Sei conness@ agli account qui sotto:"
notesAndReplies: "Note e risposte"
exploreFediverse: "Esplora Fediverse"
withFiles: "Con file in allegato"
silence: "Silenzia"
silenceConfirm: "Vuoi davvero silenziare l'utente?"
unsilence: "Riattiva"
unsilenceConfirm: "Vuoi davvero riattivare l'utente?"
popularUsers: "Utenti popolari"
recentlyUpdatedUsers: "Utenti attivi di recente"
recentlyRegisteredUsers: "Utenti registrati di recente"
recentlyDiscoveredUsers: "Utenti scoperti di recente"
exploreUsersCount: "Ci sono {count} utenti"
exploreFediverse: "Esplora il Fediverso"
popularTags: "Tag di tendenza"
userList: "Liste"
about: "Informazioni"
@ -218,96 +364,243 @@ administrator: "Amministratore"
token: "Token"
twoStepAuthentication: "Autenticazione a due fattori"
moderator: "Moderatore"
nUsersMentioned: "{n} utenti menzionatə"
securityKey: "Chiave di sicurezza"
securityKeyName: "Nome della chiave"
registerSecurityKey: "Registra una chiave di sicurezza"
lastUsed: "Ultima attività"
unregister: "Disattiva account"
unregister: "Annulla l'iscrizione"
passwordLessLogin: "Accedi senza password"
resetPassword: "Reimposta password"
share: "Renota"
newPasswordIs: "La tua nuova password è「{password}」"
reduceUiAnimation: "Ridurre le animazioni dell'interfaccia"
share: "Condividi"
notFound: "Non trovato"
notFoundDescription: "Nessuna pagina corrisponde all'URL indicata."
uploadFolder: "Destinazione caricamento predefinita"
cacheClear: "Svuota cache"
markAsReadAllNotifications: "Segna tutte le notifiche come lette"
markAsReadAllUnreadNotes: "Segna tutte le note come lette"
markAsReadAllTalkMessages: "Segna tutte le chat come lette"
help: "Guida"
inputMessageHere: "Scrivi messaggio qui"
close: "Chiudi"
group: "Gruppo"
groups: "Gruppi"
createGroup: "Nuovo gruppo"
invites: "Invita"
ownedGroups: "I miei gruppi"
joinedGroups: "Gruppi a cui mi sono unit@"
invites: "Inviti"
groupName: "Nome del gruppo"
members: "Membri"
transfer: "Trasferisci"
messagingWithUser: "Iniziare una chat con un altr@ utente"
messagingWithGroup: "Chattare in gruppo"
title: "Titolo"
text: "Testo"
enable: "Abilita"
next: "Avanti"
retype: "Conferma"
noteOf: "Note di {user}"
inviteToGroup: "Invitare al gruppo"
maxNoteTextLength: "Lunghezza massima delle note"
quoteAttached: "Citazione allegata"
quoteQuestion: "Vuoi aggiungere una citazione?"
noMessagesYet: "Ancora nessuna chat"
newMessageExists: "Hai ricevuto un nuovo messaggio"
onlyOneFileCanBeAttached: "È possibile allegare al messaggio soltanto uno file"
signinRequired: "Devi essere registrat@ nel tuo account"
invitations: "Invita"
invitationCode: "Codice di invito"
checking: "Confermando"
available: "Consigliati"
unavailable: "Il nome utente è già in uso"
usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'"
tooShort: "Troppo breve"
tooLong: "Troppo lungo"
weakPassword: "Password debole"
normalPassword: "Password buona"
strongPassword: "Password forte"
passwordMatched: "Corretta"
passwordNotMatched: "Le password non corrispondono."
signinWith: "Accedi con {x}"
signinFailed: "Autenticazione non riuscita. Controlla la tua password e nome utente."
tapSecurityKey: "Premi la chiave di sicurezza"
or: "oppure"
language: "Lingua"
uiLanguage: "Lingua di visualizzazione dell'interfaccia"
groupInvited: "Invitat@ al gruppo"
aboutX: "Informazioni su {x}"
useOsNativeEmojis: "Usare le emoji native del sistema operativo"
youHaveNoGroups: "Nessun gruppo"
joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono."
noHistory: "Nessuna cronologia"
signinHistory: "Cronologia di accesso all'account"
category: "Categoria"
tags: "Tag"
docSource: "Sorgente della scheda"
createAccount: "Crea il tuo account"
existingAcount: "Account esistente"
regenerate: "Generare di nuovo"
fontSize: "Dimensione carattere"
noFollowRequests: "Non hai alcuna richiesta di follow"
openImageInNewTab: "Aprire immagini in una nuova scheda"
dashboard: "Pannello di controllo"
local: "Locale"
remote: "Remoto"
accountSettings: "Impostazioni Account"
total: "Totale"
weekOverWeekChanges: "Settimanale"
dayOverDayChanges: "Giornaliero"
appearance: "Aspetto"
clientSettings: "Impostazioni client"
accountSettings: "Impostazioni account"
promotion: "Promossa"
promote: "Pubblicizza"
numberOfDays: "Numero di giorni"
hideThisNote: "Nasconda la nota"
showFeaturedNotesInTimeline: "Mostrare le note di tendenza nella tua timeline"
objectStorage: "Stoccaggio oggetti"
useObjectStorage: "Utilizza stoccaggio oggetti"
objectStorageBaseUrl: "Base URL"
objectStorageBucket: "Bucket"
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "I file saranno conservati sotto la directory di questo prefisso."
objectStorageEndpoint: "Endpoint"
objectStorageRegion: "Region"
objectStorageUseSSL: "Usare SSL"
serverLogs: "Log del server"
deleteAll: "Cancella cronologia"
sounds: "Effetti sonori"
showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline"
newNoteRecived: "Nuova nota ricevuta"
sounds: "Impostazioni suoni"
listen: "Ascolta"
none: "Niente"
showInPage: "Visualizza in pagina"
popout: "Finestra pop-out"
volume: "Volume"
masterVolume: "Volume principale"
details: "Dettagli"
chooseEmoji: "Scegli emoji"
unableToProcess: "Impossibile compiere l'operazione"
recentUsed: "Usato di recente"
install: "Installa"
uninstall: "Disinstalla"
installedApps: "Applicazioni installate"
nothing: "Niente da visualizzare"
installedDate: "Data installazione"
lastUsedDate: "Data di ultimo uso"
state: "Stato"
sort: "Ordina per"
visibility: "Privacy dei post"
ascendingOrder: "Ascendente"
descendingOrder: "Discendente"
scratchpad: "ScratchPad"
output: "Uscita"
script: "Script"
disablePagesScript: "Disabilita AiScript nelle pagine"
updateRemoteUser: "Aggiornare le informazioni di utente remoto"
deleteAllFiles: "Elimina tutti i file"
deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?"
removeAllFollowing: "Cancella tutti i follows"
removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più."
userSuspended: "L'utente è sospes@."
userSilenced: "L'utente è silenziat@."
sidebar: "Barra laterale"
divider: "Linea di separazione"
addItem: "Aggiungi elemento"
rooms: "Camera"
serviceworkerInfo: "Deve essere abilitato per le notifiche push. "
deletedNote: "Nota eliminata"
invisibleNote: "Nota invisibile"
enableInfiniteScroll: "Abilita scorrimento infinito"
visibility: "Visibilità"
poll: "Sondaggio"
useCw: "Nascondere media"
expandTweet: "Espandi tweet"
themeEditor: "Editor di temi"
description: "Descrizione"
author: "Autore"
leaveConfirm: "Ci sono delle modifiche ancora non salvate. Vuoi cancellarle?"
manage: "Gestione"
useFullReactionPicker: "Usa la totalità del pannello di reazioni"
width: "Larghezza"
height: "Altezza"
large: "Grande"
medium: "Predefinito"
small: "Piccolo"
enableAll: "Abilita tutto"
disableAll: "Disabilita tutto"
tokenRequested: "Autorizza accesso all'account"
notificationType: "Tipo di notifiche"
edit: "Modifica"
useStarForReactionFallback: "Se è sconosciuto l'emoji di reazione, usare la ★ come alternativa."
emailConfig: "Impostazioni server email"
email: "Email"
smtpHost: "Server remoto"
smtpUser: "Nome utente"
smtpPass: "Password"
wordMute: "Parole silenziate"
wordMute: "Filtri parole"
userSaysSomething: "{name} ha detto qualcosa"
display: "Visualizza"
copy: "Copia"
logs: "Log"
database: "Base di dati"
channel: "Canale"
notificationSetting: "impostazioni delle notifiche"
create: "Crea"
notificationSetting: "Impostazioni notifiche"
notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare."
other: "Avanzate"
fileIdOrUrl: "ID o URL del file"
abuseReports: "Segnala"
reportAbuse: "Segnala"
reportAbuseOf: "Segnala {name}"
send: "Inviare"
openInNewTab: "Apri in una nuova scheda"
editTheseSettingsMayBreakAccount: "Modificare queste impostazioni può danneggiare l'account."
waitingFor: "Aspettando {x}"
random: "Casuale"
system: "Sistema"
switchUi: "Cambiare interfaccia utente"
desktop: "Desktop"
clip: "Clip"
createNew: "Crea nuov@"
optional: "Opzionale"
public: "Pubblico"
createNewClip: "Nuova clip"
public: "Pubblica"
i18nInfo: "Misskey è tradotto in diverse lingue da volontari. Anche tu puoi contribuire su {link}."
notesCount: "Conteggio note"
repliesCount: "Numero di risposte inviate"
renotesCount: "Numero di note che hai ricondiviso"
repliedCount: "Numero di risposte ricevute"
renotedCount: "Numero delle tue note ricondivise"
followingCount: "Numero di account seguiti"
followersCount: "Numero di account che ti seguono"
sentReactionsCount: "Numero di reazioni inviate"
receivedReactionsCount: "Numero di reazioni ricevute"
pollVotesCount: "Numero di voti inviati"
pollVotedCount: "Numero di voti ricevuti"
yes: "Sì"
no: "No"
driveFilesCount: "Numero di file nel Drive"
noteFavoritesCount: "Conteggio note tra i preferiti"
pageLikesCount: "Numero di pagine che ti piacciono"
pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\""
reversiCount: "Numero di partite a Reversi"
contact: "Contatti"
clips: "Clip"
experimentalFeatures: "Funzioni sperimentali"
developer: "Sviluppatore"
showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline"
duplicate: "Duplica"
left: "Sinistra"
center: "Centro"
wide: "Largo"
clearCache: "Svuota cache"
onlineUsersCount: "{n} utenti online"
nUsers: "{n} utenti"
nNotes: "{n}Note"
myTheme: "I miei temi"
backgroundColor: "Sfondo"
textColor: "Testo"
value: "Valore"
saveConfirm: "Vuoi salvare le modifiche?"
deleteConfirm: "Rimuovere?"
@ -317,45 +610,103 @@ currentVersion: "Versione attuale"
latestVersion: "Ultima versione"
editCode: "Modifica codice"
apply: "Applica"
emailNotification: "Eventi per notifiche via mail"
inChannelSearch: "Cerca in canale"
useReactionPickerForContextMenu: "Cliccare sul tasto destro per aprire il pannello di reazioni"
typingUsers: "{users} sta(nno) scrivendo"
showingPastTimeline: "Stai visualizzando una vecchia timeline"
_email:
_follow:
title: "Ha iniziato a seguirti"
_receiveFollowRequest:
title: "Hai ricevuto una richiesta di follow"
_registry:
key: "Dati"
keys: "Dati"
createKey: "Crea chiave"
_aboutMisskey:
source: "Codice sorgente"
morePatrons: "Ci sono molti altri che ci sostengono. Grazie 🥰"
_mfm:
mention: "Menzioni"
mentionDescription: "Si può menzionare un utente specifico digitando il suo nome utente subito dopo il segno @."
hashtag: "Hashtag"
url: "URL"
link: "Link"
bold: "Grassetto"
blockCode: "Codice(blocco)"
blockCode: "Codice (blocco)"
inlineMath: "Espressione matematica(Immersione)"
blockMath: "Espressione matematica(blocco)"
blockMath: "Formula matematica (blocco)"
quote: "Cita il nota"
emoji: "Emoji personalizzati"
search: "Cerca"
blur: "Sfocatura"
font: "Tipo di carattere"
_reversi:
gameSettings: "Impostazioni di gioco"
botSettings: "Opzioni del bot"
black: "Nero"
white: "Bianco"
total: "Totale"
ended: "Esci"
_instanceTicker:
none: "Nascondi"
remote: "Mostra solo per gli/le utenti remotə"
always: "Mostra sempre"
_channel:
create: "Nuovo canale"
edit: "Gerisci canale"
setBanner: "Scegli intestazione"
removeBanner: "Rimuovi intestazione"
featured: "Tendenze"
owned: "I miei canali"
following: "Seguiti"
usersCount: "{n} partecipanti"
notesCount: "{n} note"
_sidebar:
icon: "Foto del profilo"
icon: "Icone"
hide: "Nascondere"
_wordMute:
muteWords: "Parole da silenziare"
muteWordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con un'interruzzione riga indica la condizione \"O\"."
muteWordsDescription2: "Metti le parole chiavi tra slash per usare espressioni regolari (regexp)."
mutedNotes: "Note silenziate"
_theme:
explore: "Esplora temi"
install: "Installa un tema"
manage: "Gerisci temi"
code: "Codice tema"
installed: "{name} è installato"
installedThemes: "Temi installati"
builtinThemes: "Temi integrati"
alreadyInstalled: "Questo tema è già installato"
invalid: "Il formato tema non è valido"
make: "Crea un tema"
base: "Base"
addConstant: "Aggiungi costante"
constant: "Costante"
defaultValue: "Valore predefinito"
color: "Colore"
key: "Chiave"
func: "Funzione"
argument: "Argomento"
darken: "Scuro"
lighten: "Chiaro"
keys:
bg: "Sfondo"
fg: "Testo"
focus: "Focalizzazione"
indicator: "Indicatore"
panel: "Pannello"
shadow: "Ombra"
header: "Intestazione"
navBg: "Sfondo della barra laterale"
navFg: "Testo della barra laterale"
navHoverFg: "Testo della barra laterale (al passaggio del mouse)"
navActive: "Testo della barra laterale (attivo)"
navIndicator: "Indicatore di barra laterale"
link: "Link"
hashtag: "Hashtag"
mention: "Menzioni"
renote: "Rinota"
divider: "Interruzione di linea"
@ -363,6 +714,8 @@ _sfx:
note: "Nota"
notification: "Notifiche"
chat: "Messaggi"
antenna: "Ricezione dell'antenna"
channel: "Notifiche di canale"
_ago:
unknown: "Sconosciuto"
future: "Futuro"
@ -381,14 +734,51 @@ _time:
day: "giorni"
_tutorial:
title: "Come usare Misskey"
step1_1: "Benvenuto"
step1_1: "Benvenuto/a!"
step1_2: "Questa pagina si chiama una \" Timeline \". Mostra in ordine cronologico le \" note \" delle persone che segui."
step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun account e non hai pubblicato alcuna nota ancora."
step2_1: "Prima di scrivere una nota o di seguire un account, imposta il tuo profilo!"
step2_2: "Aggiungere qualche informazione su di te aumenterà le tue possibilità di essere seguit@ da altre persone. "
step3_1: "Hai finito di impostare il tuo profilo?"
step3_2: "Ora, puoi pubblicare una nota. Facciamo una prova! Premi il pulsante a forma di penna in cima allo schermo per aprire una finestra di dialogo. "
step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella parte superiore destra della finestra di dialogo."
step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena cominciato a usare Misskey\"?"
step4_1: "Hai pubblicato qualcosa?"
step4_2: "Se puoi visualizzare la tua nota sulla timeline, ce l'hai fatta!"
step5_1: "Adesso, cerca di seguire altre persone per vivacizzare la tua timeline. "
step5_2: "La pagina {featured} mostra le note di tendenza su questa istanza e, sfogliandole, magari toverai degli account che ti piacciono e che vorrai seguire. Oppure, potrai trovare utenti popolari usando {explore}."
step5_3: "Per seguire altrə utenti, clicca sul loro avatar per aprire la pagina di profilo dove puoi premere il pulsante \"Seguire\". "
step5_4: "Alcunə utenti scelgono di confermare manualmente le richieste di follow che ricevono, quindi a seconda delle persone potrebbe volerci un pò prima che la tua richiesta sia accolta."
step6_1: "Ora, se puoi visualizzare le note di altrə utenti sulla tua timeline, ce l'hai fatta!"
step6_2: "Puoi inviare una risposta rapida alle note di altrə utenti mandando loro \"reazioni\"."
step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji che vuoi mandare."
step7_1: "Complimenti! Sei arrivat@ alla fine dell'esercitazione di base su come usare Misskey. "
step7_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}."
step7_3: "Da ultimo, buon divertimento su Misskey! 🚀"
_permissions:
"read:blocks": "Visualizza gli account che hai bloccato."
"write:blocks": "Gestisci gli account che hai bloccato."
"read:favorites": "Visualizza Preferiti"
"write:favorites": "Gestisci Preferiti"
"read:blocks": "Visualizza gli account bloccati"
"write:blocks": "Gestisci gli account bloccati"
"read:favorites": "Visualizza i tuoi preferiti"
"write:favorites": "Gestisci i tuoi preferiti"
"read:following": "Vedi le informazioni di follow"
"write:following": "Seguiti/ Smetti di seguire"
"read:mutes": "Vedi account silenziati"
"write:mutes": "Gerisci account silenziati"
"write:notes": "Creare / Eliminare note"
"read:notifications": "Visualizza notifiche"
"write:notifications": "Gerisci notifiche"
"read:reactions": "Vedi reazioni"
"write:reactions": "Gerisci reazioni"
"read:user-groups": "Vedi gruppi di utenti"
"write:user-groups": "Gestisci gruppi di utenti"
"read:channels": "Visualizza canali"
"write:channels": "Gerisci canali"
_antennaSources:
all: "Tutte le note"
homeTimeline: "Note dagli utenti che segui"
users: "Note dagli utenti selezionati"
userList: "Note dagli utenti della lista selezionata"
userGroup: "Note dagli utenti del gruppo selezionato"
_weekday:
sunday: "Domenica"
monday: "Lunedì"
@ -409,6 +799,9 @@ _widgets:
photos: "Foto"
digitalClock: "Orologio digitale"
federation: "Federazione"
button: "Pulsante"
onlineUsers: "Utenti online"
jobQueue: "Coda di lavoro"
_cw:
hide: "Nascondere"
show: "Mostra di più"
@ -422,14 +815,20 @@ _poll:
voted: "Votato"
closed: "Terminato"
_visibility:
public: "Pubblico"
public: "Pubblica"
publicDescription: "Visibile per tutti sul Fediverso"
home: "Home"
followers: "Seguaci"
localOnly: "Solo Locale"
localOnlyDescription: "Solo locale"
homeDescription: "Visibile solo sulla timeline \"Home\""
followers: "Followers"
followersDescription: "Visibile solo per i tuoi followers"
specified: "Diretta"
specifiedDescription: "Visibile solo per gli/le utenti menzionatə"
localOnly: "Soltanto locale"
localOnlyDescription: "Nascosta per gli/le utenti remotə"
_postForm:
replyPlaceholder: "Nota la tua risposta.."
quotePlaceholder: "Cita Nota..."
channelPlaceholder: "Pubblica in canale"
_profile:
name: "Nome"
username: "Nome utente"
@ -437,16 +836,29 @@ _profile:
metadata: "Metadati"
metadataLabel: "Etichetta"
metadataContent: "Contenuto"
changeBanner: "Cambia intestazione"
_exportOrImport:
followingList: "Seiguiti"
muteList: "Silenzia"
blockingList: "Blocca"
allNotes: "Tutte le note"
followingList: "Follows"
muteList: "Account silenziati"
blockingList: "Account bloccati"
userLists: "Liste"
_charts:
usersIncDec: "Variazione del numero di utenti"
usersTotal: "Numero totale di utenti"
activeUsers: "Numero di utenti attivi"
notesTotal: "Conteggio totale di note"
_instanceCharts:
users: "Variazione del numero di utenti"
usersTotal: "Totale cumulativo di utenti"
_timelines:
home: "Home"
local: "Locale"
social: "Sociale"
_rooms:
roomOf: "Camera di {user}"
_roomType:
default: "Predefinito"
washitsu: "Washitsu"
_furnitures:
milk: "Cartone del latte"
@ -477,31 +889,59 @@ _rooms:
photoframe: "Cornice"
cube: "Cubo"
tv: "Televisore"
pinguin: "Pinguini"
pinguin: "Pinguino"
bin: "Cestino"
cup-noodle: "Noodle istantanei"
_pages:
created: "Pagina creata!"
pageSetting: "Impostazioni pagina"
viewSource: "Visualizza sorgente"
like: "Mi piace"
unlike: "Togli Mi piace"
featured: "Popolari"
content: "Blocco di pagina"
variables: "Variabili"
title: "Titolo"
hideTitleWhenPinned: "Nascondere il titolo pagina quando è fissata in cima al profilo."
font: "Tipo di carattere"
chooseBlock: "Aggiungi blocco"
blocks:
text: "Testo"
textarea: "Area di testo"
section: "Sezione"
image: "Immagini"
button: "Pulsante"
if: "Se"
_if:
variable: "Variabili"
_post:
text: "Contenuto"
_textInput:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
_textareaInput:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
_numberInput:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
_canvas:
width: "Larghezza"
height: "Altezza"
note: "Nota integrata"
_note:
id: "ID nota"
idDescription: "Qui puoi anche incollare l'URL della nota che vuoi impostare."
detailed: "Visualizzazione dettagliata"
_switch:
name: "Nome della variabile"
text: "Titolo"
default: "Valore predefinito"
_counter:
name: "Nome della variabile"
text: "Titolo"
_button:
text: "Titolo"
@ -509,7 +949,9 @@ _pages:
_dialog:
content: "Contenuto"
_radioButton:
name: "Nome della variabile"
title: "Titolo"
default: "Valore predefinito"
script:
categories:
comparison: "Metodo comparativo"
@ -518,6 +960,15 @@ _pages:
fn: "Funzione"
list: "Liste"
blocks:
text: "Testo"
_strLen:
arg1: "Testo"
_strPick:
arg1: "Testo"
_strReplace:
arg1: "Testo"
_strReverse:
arg1: "Testo"
_join:
arg1: "Liste"
_add:
@ -575,26 +1026,46 @@ _pages:
arg1: "Liste"
_listLen:
arg1: "Liste"
_stringToNumber:
arg1: "Testo"
_splitStrByLine:
arg1: "Testo"
ref: "Variabili"
fn: "Funzione"
types:
string: "Testo"
array: "Liste"
_notification:
fileUploaded: "File caricato correttamente"
youGotMention: "{name} ti ha menzionato"
youGotReply: "{name} ti ha risposto"
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
youRenoted: "{name} ha rinota"
youRenoted: "{name} ha rinotato"
youGotPoll: "{name} ha volluto."
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
youWereFollowed: "Ha iniziato a seguirti"
youReceivedFollowRequest: "Hai ricevuto una richiesta di follow"
yourFollowRequestAccepted: "La tua richiesta di follow è stata accettata"
youWereInvitedToGroup: "Invitat@ al gruppo"
_types:
all: "Tutto"
follow: "Seiguiti"
follow: "Follows"
mention: "Menzioni"
reply: "Rispondi"
renote: "Rinota"
quote: "Cita"
reaction: "Reazione"
pollVote: "Voti ricevuti"
receiveFollowRequest: "Richiesta di follow ricevuta"
followRequestAccepted: "Richiesta di follow accettata"
groupInvited: "Invito a un gruppo"
app: "Notifiche da applicazioni"
_deck:
_columns:
notifications: "Notifiche"
tl: "Timeline"
antenna: "Antenne"
list: "Liste"
mentions: "Menzioni"
direct: "Diretta"

View File

@ -322,7 +322,7 @@ proxyRemoteFilesDescription: "この設定を有効にすると、未保存ま
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量"
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量"
inMb: "メガバイト単位"
iconUrl: "アイコン画像のURL"
iconUrl: "アイコン画像のURL (faviconなど)"
bannerUrl: "バナー画像のURL"
basicInfo: "基本情報"
pinnedUsers: "ピン留めユーザー"

View File

@ -81,7 +81,7 @@ pageLoadError: "ページの読み込みに失敗してしもうたで…"
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
enterListName: "リスト名を入れてや"
privacy: "プライバシー"
makeFollowManuallyApprove: "ええって言わなフォローできへんようにする"
makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする"
defaultNoteVisibility: "もとからの公開範囲"
follow: "フォロー"
followRequest: "フォローを頼む"
@ -308,7 +308,7 @@ monthX: "{month}月"
yearX: "{year}年"
pages: "ページ"
integration: "連携"
connectSerice: "つなげる"
connectSerice: "つな"
disconnectSerice: "切ってまう"
enableLocalTimeline: "ローカルタイムラインを使えるようにする"
enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
@ -392,7 +392,7 @@ markAsReadAllUnreadNotes: "投稿は全て読んだわっ"
markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ"
help: "ヘルプ"
inputMessageHere: "ここにメッセージ書いてや"
close: "さいなら"
close: "閉じる"
group: "グループ"
groups: "グループ"
createGroup: "グループを作るで"

View File

@ -241,7 +241,7 @@ explore: "Обзор"
games: "Игры Misskey"
messageRead: "Прочитали"
noMoreHistory: "История закончилась"
startMessaging: "Отправить сообщение"
startMessaging: "Начать общение"
nUsersRead: "Прочитали {n}"
agreeTo: "Я соглашаюсь с {0}"
tos: "Пользовательское соглашение"
@ -329,7 +329,7 @@ pinnedUsers: "Прикреплённый пользователь"
pinnedUsersDescription: "Перечислите по одному имени пользователя в строке. Пользователи, перечисленные здесь, будут привязаны к закладке \"Изучение\"."
pinnedPages: "Закрепленные страницы"
pinnedPagesDescription: "Если хотите закрепить страницы на главной сайта, сюда можно добавить пути к ним, каждый в отдельной строке."
pinnedClipId: "Идентификатор закреплённой памятки"
pinnedClipId: "Идентификатор закреплённой подборки"
pinnedNotes: "Закреплённая заметка"
hcaptcha: "hCaptcha"
enableHcaptcha: "Включить hCaptcha"
@ -405,8 +405,8 @@ invites: "Приглашения"
groupName: "Название группы"
members: "Участники"
transfer: "Отдать"
messagingWithUser: "Сообщения пользователей"
messagingWithGroup: "Чат в группе"
messagingWithUser: "Общение с другим пользователем"
messagingWithGroup: "Общение в группе"
title: "Заголовок"
text: "Текст"
enable: "Включить"
@ -471,7 +471,7 @@ promotion: "Продвинуто"
promote: "Продвинуть"
numberOfDays: "Количество дней"
hideThisNote: "Спрятать эту запись"
showFeaturedNotesInTimeline: "Показывать в ленте заметки из подборки сайта"
showFeaturedNotesInTimeline: "Показывать в ленте заметки из «Горячего»"
objectStorage: "Хранилище"
useObjectStorage: "Занято в хранилище"
objectStorageBaseUrl: "Базовый адрес"
@ -524,7 +524,7 @@ deleteAllFiles: "Удалить все файлы"
deleteAllFilesConfirm: "Вы хотите удалить все файлы?"
removeAllFollowing: "Удалить всех подписчиков"
removeAllFollowingDescription: "Отменить все подписки с домена {host}? Пожалуйста, применяйте это действие, если инстанс больше не существует."
userSuspended: "Этот пользователь был заморожен"
userSuspended: "Эта учётная запись заморожена"
userSilenced: "Этот пользователь был заглушен"
sidebar: "Боковая панель"
divider: "Линия-разделитель"
@ -623,30 +623,30 @@ random: "Случайные"
system: "Система"
switchUi: "Выбор вида"
desktop: "Стол"
clip: "В памятку"
clip: "В подборку"
createNew: "Новый документ"
optional: "Необязательно"
createNewClip: "Новая памятка"
createNewClip: "Новая подборка"
public: "Общедоступно"
i18nInfo: "Misskey переводят на разные языки добровольцы со всего света. Ваша помощь тоже пригодится здесь: {link}."
manageAccessTokens: "Управление токенами доступа"
accountInfo: "Сведения об учётной записи"
notesCount: "Количество заметок"
repliesCount: "Сколько раз пользователь кому-то ответил"
renotesCount: "Сколько раз пользователь передал чужие заметки"
renotesCount: "Сколько раз пользователь делился заметками"
repliedCount: "Сколько раз ответили пользователю"
renotedCount: "Сколько раз передавали заметки пользователя"
renotedCount: "Сколько раз делились заметками пользователя"
followingCount: "Количество подписок"
followersCount: "Количество подписавшихся"
sentReactionsCount: "Сколько раз пользователь отреагировал"
receivedReactionsCount: "Сколько раз отреагировали на заметки пользователя"
pollVotesCount: "Сколько раз участвовал в опросах"
sentReactionsCount: "Количество реакций пользователя"
receivedReactionsCount: "Количество реакций на заметки пользователя"
pollVotesCount: "Сколько раз пользователь участвовал в опросах"
pollVotedCount: "Сколько раз участвовали в опросах пользователя"
yes: "Да"
no: "Нет"
driveFilesCount: "Количество файлов на диске"
driveUsage: "Сколько места занято на диске"
noCrawle: "Паукам вход воспрещён"
driveUsage: "Занято места на диске"
noCrawle: "Запретить паукам индексировать сайт"
noCrawleDescription: "Просьба поисковым системам не ходить по вашему профилю, по заметкам, страницам и не индексировать их."
lockedAccountInfo: "Даже если вы вручную подтверждаете подписки, кто угодно может читать ваши заметки, если вы не отмечаете их «для подписчиков»."
alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию"
@ -661,7 +661,7 @@ pageLikedCount: "Количество страниц, понравившихся
reversiCount: "Количество сыгранных игр в реверси"
contact: "Как связаться"
useSystemFont: "Использовать шрифт, предлагаемый системой"
clips: амятки"
clips: одборки"
experimentalFeatures: "Экспериментальные функции"
developer: "Разработчик"
makeExplorable: "Опубликовать профиль в «Обзоре»."
@ -983,7 +983,7 @@ _tutorial:
step7_2: "Хотите изучить Misskey глубже — добро пожаловать в раздел «{help}»."
step7_3: "Приятно вам провести время с Misskey🚀"
_2fa:
alreadyRegistered: "Настройка завершена"
alreadyRegistered: "Двухфакторная аутентификация уже настроена."
registerDevice: "Зарегистрируйте ваше устройство"
registerKey: "Зарегистрировать ключ"
step1: "Прежде всего, установите на устройство приложение для аутентификации, например, {a} или {b}."
@ -1240,7 +1240,7 @@ _pages:
liked: "Понравившиеся страницы"
featured: "Популярные"
inspector: "Инспектор"
contents: "Содержательные"
contents: "Содержимое"
content: "Содержимое"
variables: "Переменные"
title: "Заголовок"

View File

@ -579,7 +579,7 @@ smtpPort: "端口"
smtpUser: "用户名"
smtpPass: "密码"
emptyToDisableSmtpAuth: "用户名和密码留空可以禁用SMTP验证"
smtpSecure: "在 SMTP 连接中默认使用 SSL / TLS"
smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS"
smtpSecureInfo: "使用STARTTLS时关闭。"
testEmail: "邮件发送测试"
wordMute: "文字屏蔽"

View File

@ -438,6 +438,7 @@ signinWith: "以{x}登錄"
signinFailed: "登入失敗。 請檢查用戶名和密碼。"
tapSecurityKey: "點擊安全密鑰"
or: "或者"
language: "語言"
uiLanguage: "介面語言"
groupInvited: "您有新的群組邀請"
aboutX: "關於{x}"
@ -677,9 +678,12 @@ newVersionOfClientAvailable: "新版本的用戶端可用。"
usageAmount: "使用量"
capacity: "容量"
inUse: "已使用"
clear: "清除"
_email:
_follow:
title: "您有新的追隨者"
_plugin:
manage: "管理插件"
_registry:
scope: "範圍"
key: "機碼"
@ -702,7 +706,9 @@ _nsfw:
_mfm:
cheatSheet: "MFM代碼小抄"
intro: "MFM是Misskey專用的標記語言可以在Misskey中的各個位置使用。 您可以這裏看到MFM可用語法列表。"
dummy: "通過Misskey擴展Fediverse的世界"
mention: "提及"
mentionDescription: "透過 @+用戶名 來標示特定使用者。"
hashtag: "#tag"
url: "URL"
link: "鏈接"

View File

@ -0,0 +1,218 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class chartV21615965918224 implements MigrationInterface {
name = 'chartV21615965918224'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE FROM "__chart__active_users" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__drive" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__federation" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__hashtag" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__instance" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__network" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__notes" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__per_user_drive" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__per_user_following" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__per_user_notes" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__per_user_reaction" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__test" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__test_grouped" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__test_unique" WHERE "span" = 'day'`);
await queryRunner.query(`DELETE FROM "__chart__users" WHERE "span" = 'day'`);
await queryRunner.query(`DROP INDEX "IDX_15e91a03aeeac9dbccdf43fc06"`);
await queryRunner.query(`DROP INDEX "IDX_20f57cc8f142c131340ee16742"`);
await queryRunner.query(`DROP INDEX "IDX_c26e2c1cbb6e911e0554b27416"`);
await queryRunner.query(`DROP INDEX "IDX_3fa0d0f17ca72e3dc80999a032"`);
await queryRunner.query(`DROP INDEX "IDX_6e1df243476e20cbf86572ecc0"`);
await queryRunner.query(`DROP INDEX "IDX_06690fc959f1c9fdaf21928222"`);
await queryRunner.query(`DROP INDEX "IDX_e447064455928cf627590ef527"`);
await queryRunner.query(`DROP INDEX "IDX_2d416e6af791a82e338c79d480"`);
await queryRunner.query(`DROP INDEX "IDX_e9cd07672b37d8966cf3709283"`);
await queryRunner.query(`DROP INDEX "IDX_fcc181fb8283009c61cc4083ef"`);
await queryRunner.query(`DROP INDEX "IDX_49975586f50ed7b800fdd88fbd"`);
await queryRunner.query(`DROP INDEX "IDX_6d6f156ceefc6bc5f273a0e370"`);
await queryRunner.query(`DROP INDEX "IDX_c12f0af4a66cdd30c2287ce8aa"`);
await queryRunner.query(`DROP INDEX "IDX_d0a4f79af5a97b08f37b547197"`);
await queryRunner.query(`DROP INDEX "IDX_f5448d9633cff74208d850aabe"`);
await queryRunner.query(`DROP INDEX "IDX_f8dd01baeded2ffa833e0a610a"`);
await queryRunner.query(`DROP INDEX "IDX_08fac0eb3b11f04c200c0b40dd"`);
await queryRunner.query(`DROP INDEX "IDX_9ff6944f01acb756fdc92d7563"`);
await queryRunner.query(`DROP INDEX "IDX_e69096589f11e3baa98ddd64d0"`);
await queryRunner.query(`DROP INDEX "IDX_0c9a159c5082cbeef3ca6706b5"`);
await queryRunner.query(`DROP INDEX "IDX_924fc196c80ca24bae01dd37e4"`);
await queryRunner.query(`DROP INDEX "IDX_328f259961e60c4fa0bfcf55ca"`);
await queryRunner.query(`DROP INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53"`);
await queryRunner.query(`DROP INDEX "IDX_f2aeafde2ae6fbad38e857631b"`);
await queryRunner.query(`DROP INDEX "IDX_f92dd6d03f8d994f29987f6214"`);
await queryRunner.query(`DROP INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f"`);
await queryRunner.query(`DROP INDEX "IDX_4db3b84c7be0d3464714f3e0b1"`);
await queryRunner.query(`DROP INDEX "IDX_8d2cbbc8114d90d19b44d626b6"`);
await queryRunner.query(`DROP INDEX "IDX_046feeb12e9ef5f783f409866a"`);
await queryRunner.query(`DROP INDEX "IDX_f68a5ab958f9f5fa17a32ac23b"`);
await queryRunner.query(`DROP INDEX "IDX_65633a106bce43fc7c5c30a5c7"`);
await queryRunner.query(`DROP INDEX "IDX_edeb73c09c3143a81bcb34d569"`);
await queryRunner.query(`DROP INDEX "IDX_e316f01a6d24eb31db27f88262"`);
await queryRunner.query(`DROP INDEX "IDX_2be7ec6cebddc14dc11e206686"`);
await queryRunner.query(`DROP INDEX "IDX_a5133470f4825902e170328ca5"`);
await queryRunner.query(`DROP INDEX "IDX_84e661abb7bd1e51b690d4b017"`);
await queryRunner.query(`DROP INDEX "IDX_5c73bf61da4f6e6f15bae88ed1"`);
await queryRunner.query(`DROP INDEX "IDX_d70c86baedc68326be11f9c0ce"`);
await queryRunner.query(`DROP INDEX "IDX_66e1e1ecd2f29e57778af35b59"`);
await queryRunner.query(`DROP INDEX "IDX_92255988735563f0fe4aba1f05"`);
await queryRunner.query(`DROP INDEX "IDX_c5870993e25c3d5771f91f5003"`);
await queryRunner.query(`DROP INDEX "IDX_f170de677ea75ad4533de2723e"`);
await queryRunner.query(`DROP INDEX "IDX_7c184198ecf66a8d3ecb253ab3"`);
await queryRunner.query(`DROP INDEX "IDX_f091abb24193d50c653c6b77fc"`);
await queryRunner.query(`DROP INDEX "IDX_a770a57c70e668cc61590c9161"`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__active_users_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_count"`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_count"`);
await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__drive_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__drive" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__federation_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__hashtag_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___local_count"`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___remote_count"`);
await queryRunner.query(`ALTER TABLE "__chart__instance" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__instance_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__instance" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__network" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__network_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__network" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__notes_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__notes" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__per_user_drive_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_following" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__per_user_following_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_following" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__per_user_notes_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__per_user_reaction_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__test_grouped" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__test_grouped_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__test_grouped" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__test_unique_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "___foo"`);
await queryRunner.query(`ALTER TABLE "__chart__test" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__test_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__test" DROP COLUMN "unique"`);
await queryRunner.query(`ALTER TABLE "__chart__users" DROP COLUMN "span"`);
await queryRunner.query(`DROP TYPE "public"."__chart__users_span_enum"`);
await queryRunner.query(`ALTER TABLE "__chart__users" DROP COLUMN "unique"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "__chart__users" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__users_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__users" ADD "span" "__chart__users_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__test" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__test_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__test" ADD "span" "__chart__test_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "___foo" bigint NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__test_unique_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "span" "__chart__test_unique_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__test_grouped" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__test_grouped_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__test_grouped" ADD "span" "__chart__test_grouped_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_reaction_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_reaction" ADD "span" "__chart__per_user_reaction_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_notes_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_notes" ADD "span" "__chart__per_user_notes_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_following_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_following" ADD "span" "__chart__per_user_following_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__per_user_drive_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__per_user_drive" ADD "span" "__chart__per_user_drive_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__notes_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__notes" ADD "span" "__chart__notes_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__network" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__network_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__network" ADD "span" "__chart__network_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__instance" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__instance_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__instance" ADD "span" "__chart__instance_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___remote_count" bigint NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___local_count" bigint NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__hashtag_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "span" "__chart__hashtag_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__federation_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "span" "__chart__federation_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__drive_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__drive" ADD "span" "__chart__drive_span_enum" NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_count" bigint NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_count" bigint NOT NULL`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "unique" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`CREATE TYPE "public"."__chart__active_users_span_enum" AS ENUM('hour', 'day')`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "span" "__chart__active_users_span_enum" NOT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_a770a57c70e668cc61590c9161" ON "__chart__users" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_f091abb24193d50c653c6b77fc" ON "__chart__users" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_7c184198ecf66a8d3ecb253ab3" ON "__chart__users" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_f170de677ea75ad4533de2723e" ON "__chart__test" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_c5870993e25c3d5771f91f5003" ON "__chart__test" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_92255988735563f0fe4aba1f05" ON "__chart__test" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_66e1e1ecd2f29e57778af35b59" ON "__chart__test_unique" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_d70c86baedc68326be11f9c0ce" ON "__chart__test_unique" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_5c73bf61da4f6e6f15bae88ed1" ON "__chart__test_unique" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_84e661abb7bd1e51b690d4b017" ON "__chart__test_grouped" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_a5133470f4825902e170328ca5" ON "__chart__test_grouped" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_2be7ec6cebddc14dc11e206686" ON "__chart__test_grouped" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_e316f01a6d24eb31db27f88262" ON "__chart__per_user_reaction" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_edeb73c09c3143a81bcb34d569" ON "__chart__per_user_reaction" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_65633a106bce43fc7c5c30a5c7" ON "__chart__per_user_reaction" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_f68a5ab958f9f5fa17a32ac23b" ON "__chart__per_user_notes" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_046feeb12e9ef5f783f409866a" ON "__chart__per_user_notes" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_8d2cbbc8114d90d19b44d626b6" ON "__chart__per_user_notes" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_4db3b84c7be0d3464714f3e0b1" ON "__chart__per_user_following" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_57b5458d0d3d6d1e7f13d4e57f" ON "__chart__per_user_following" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_f92dd6d03f8d994f29987f6214" ON "__chart__per_user_following" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_f2aeafde2ae6fbad38e857631b" ON "__chart__per_user_drive" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_42ea9381f0fda8dfe0fa1c8b53" ON "__chart__per_user_drive" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_328f259961e60c4fa0bfcf55ca" ON "__chart__per_user_drive" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_924fc196c80ca24bae01dd37e4" ON "__chart__notes" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_0c9a159c5082cbeef3ca6706b5" ON "__chart__notes" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_e69096589f11e3baa98ddd64d0" ON "__chart__notes" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_9ff6944f01acb756fdc92d7563" ON "__chart__network" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_08fac0eb3b11f04c200c0b40dd" ON "__chart__network" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_f8dd01baeded2ffa833e0a610a" ON "__chart__network" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_f5448d9633cff74208d850aabe" ON "__chart__instance" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_d0a4f79af5a97b08f37b547197" ON "__chart__instance" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_c12f0af4a66cdd30c2287ce8aa" ON "__chart__instance" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_6d6f156ceefc6bc5f273a0e370" ON "__chart__hashtag" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_49975586f50ed7b800fdd88fbd" ON "__chart__hashtag" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_fcc181fb8283009c61cc4083ef" ON "__chart__hashtag" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_e9cd07672b37d8966cf3709283" ON "__chart__federation" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_2d416e6af791a82e338c79d480" ON "__chart__federation" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_e447064455928cf627590ef527" ON "__chart__federation" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_06690fc959f1c9fdaf21928222" ON "__chart__drive" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_6e1df243476e20cbf86572ecc0" ON "__chart__drive" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_3fa0d0f17ca72e3dc80999a032" ON "__chart__drive" ("span") `);
await queryRunner.query(`CREATE INDEX "IDX_c26e2c1cbb6e911e0554b27416" ON "__chart__active_users" ("date", "group", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_20f57cc8f142c131340ee16742" ON "__chart__active_users" ("date", "span") `);
await queryRunner.query(`CREATE INDEX "IDX_15e91a03aeeac9dbccdf43fc06" ON "__chart__active_users" ("span") `);
}
}

View File

@ -0,0 +1,22 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class chartV221615966519402 implements MigrationInterface {
name = 'chartV221615966519402'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___local_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" ADD "___remote_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___local_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" ADD "___remote_users" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`ALTER TABLE "__chart__test_unique" ADD "___foo" character varying array NOT NULL DEFAULT '{}'::varchar[]`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "__chart__test_unique" DROP COLUMN "___foo"`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___remote_users"`);
await queryRunner.query(`ALTER TABLE "__chart__hashtag" DROP COLUMN "___local_users"`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___remote_users"`);
await queryRunner.query(`ALTER TABLE "__chart__active_users" DROP COLUMN "___local_users"`);
}
}

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.74.1",
"version": "12.75.0",
"codename": "indigo",
"repository": {
"type": "git",
@ -11,20 +11,20 @@
"private": true,
"scripts": {
"start": "node ./index.js",
"start-product": "cross-env NODE_ENV=production node ./index.js",
"init": "npm run migrate",
"ormconfig": "node ./built/ormconfig.js",
"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
"migrateandstart": "npm run migrate && npm run start",
"build": "webpack && gulp build",
"build-product": "cross-env NODE_ENV=production webpack && gulp build",
"webpack": "webpack",
"watch": "webpack --watch",
"gulp": "gulp build",
"build": "npm run build-webpack && npm run build-gulp",
"build-webpack": "webpack",
"build-gulp": "gulp build",
"watch": "concurrently \"npm:watch-*\"",
"watch-webpack": "webpack --watch",
"watch-gulp": "gulp watch",
"clean": "gulp clean",
"cleanall": "gulp cleanall",
"lint": "tslint 'src/**/*.ts'",
"test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_COMPILER_OPTIONS=\"{\\\"target\\\":\\\"es2017\\\",\\\"module\\\":\\\"commonjs\\\",\\\"typeRoots\\\":[\\\"node_modules/@types\\\",\\\"src/@types\\\"]}\" mocha",
"test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"format": "gulp format"
},
"resolutions": {
@ -35,25 +35,24 @@
"lodash": "^4.17.20"
},
"dependencies": {
"@babel/plugin-transform-runtime": "7.13.9",
"@babel/plugin-transform-runtime": "7.13.10",
"@elastic/elasticsearch": "7.11.0",
"@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.15.2",
"@fortawesome/free-regular-svg-icons": "5.15.2",
"@fortawesome/free-solid-svg-icons": "5.15.2",
"@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-brands-svg-icons": "5.15.3",
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/vue-fontawesome": "3.0.0-3",
"@koa/cors": "3.1.0",
"@koa/multer": "3.0.0",
"@koa/router": "9.0.1",
"@sentry/browser": "5.29.2",
"@sentry/tracing": "5.29.2",
"@sinonjs/fake-timers": "6.0.1",
"@sinonjs/fake-timers": "7.0.2",
"@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.0",
"@types/cbor": "5.0.1",
"@types/dateformat": "3.0.1",
"@types/double-ended-queue": "2.1.1",
"@types/escape-regexp": "0.0.0",
"@types/glob": "7.1.3",
"@types/gulp": "4.0.8",
@ -61,10 +60,10 @@
"@types/gulp-replace": "0.0.31",
"@types/is-url": "1.2.28",
"@types/js-yaml": "4.0.0",
"@types/jsdom": "16.2.6",
"@types/jsonld": "1.5.4",
"@types/jsdom": "16.2.7",
"@types/jsonld": "1.5.5",
"@types/katex": "0.11.0",
"@types/koa": "2.13.0",
"@types/koa": "2.13.1",
"@types/koa-bodyparser": "4.3.0",
"@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19",
@ -78,9 +77,9 @@
"@types/markdown-it": "12.0.1",
"@types/matter-js": "0.14.10",
"@types/mocha": "8.2.1",
"@types/node": "14.14.31",
"@types/node": "14.14.35",
"@types/node-fetch": "2.5.8",
"@types/nodemailer": "6.4.0",
"@types/nodemailer": "6.4.1",
"@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1",
"@types/parse5": "6.0.0",
@ -105,44 +104,44 @@
"@types/web-push": "3.3.0",
"@types/webpack": "4.41.26",
"@types/webpack-stream": "3.2.11",
"@types/websocket": "1.0.1",
"@types/websocket": "1.0.2",
"@types/ws": "7.4.0",
"@typescript-eslint/parser": "4.16.1",
"@vue/compiler-sfc": "3.0.5",
"@typescript-eslint/parser": "4.18.0",
"@vue/compiler-sfc": "3.0.7",
"abort-controller": "3.0.0",
"apexcharts": "3.25.0",
"apexcharts": "3.26.0",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.848.0",
"aws-sdk": "2.867.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.3",
"broadcast-channel": "3.4.1",
"bull": "3.20.1",
"broadcast-channel": "3.5.3",
"bull": "3.21.1",
"cafy": "15.2.1",
"cbor": "7.0.3",
"cbor": "7.0.4",
"chalk": "4.1.0",
"chart.js": "2.9.4",
"cli-highlight": "2.1.10",
"commander": "4.1.1",
"concurrently": "6.0.0",
"content-disposition": "0.5.3",
"core-js": "3.9.0",
"core-js": "3.9.1",
"crc-32": "1.2.0",
"css-loader": "5.0.2",
"css-loader": "5.1.3",
"cssnano": "4.1.10",
"dateformat": "4.5.1",
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1",
"eslint": "7.21.0",
"eslint-plugin-vue": "7.6.0",
"eslint": "7.22.0",
"eslint-plugin-vue": "7.7.0",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
"fibers": "5.0.0",
"file-type": "16.2.0",
"file-type": "16.3.0",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"got": "11.8.1",
"got": "11.8.2",
"gulp": "4.0.2",
"gulp-cssnano": "2.1.3",
"gulp-rename": "2.0.0",
@ -155,17 +154,17 @@
"http-proxy-agent": "4.0.1",
"http-signature": "1.3.5",
"https-proxy-agent": "5.0.0",
"idb-keyval": "5.0.2",
"idb-keyval": "5.0.4",
"insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0",
"is-svg": "4.2.1",
"is-svg": "4.3.1",
"js-yaml": "4.0.0",
"jsdom": "16.4.0",
"jsdom": "16.5.1",
"json5": "2.2.0",
"json5-loader": "4.0.1",
"jsonld": "4.0.1",
"jsrsasign": "8.0.20",
"katex": "0.12.0",
"katex": "0.13.0",
"koa": "2.13.1",
"koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0",
@ -174,13 +173,13 @@
"koa-mount": "4.0.0",
"koa-send": "5.0.1",
"koa-slow": "2.1.0",
"koa-views": "6.3.1",
"koa-views": "7.0.1",
"langmap": "0.0.16",
"lookup-dns-cache": "2.1.0",
"markdown-it": "12.0.4",
"markdown-it-anchor": "7.0.2",
"markdown-it-anchor": "7.1.0",
"matter-js": "0.16.1",
"mocha": "8.3.0",
"mocha": "8.3.2",
"moji": "0.5.1",
"ms": "2.1.3",
"multer": "1.4.2",
@ -189,20 +188,19 @@
"nodemailer": "6.5.0",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
"p-cancelable": "2.0.0",
"parse5": "6.0.1",
"parsimmon": "1.16.0",
"pg": "8.5.1",
"portscanner": "2.2.0",
"postcss": "8.2.7",
"postcss-loader": "5.0.0",
"postcss": "8.2.8",
"postcss-loader": "5.2.0",
"prismjs": "1.23.0",
"probe-image-size": "6.0.0",
"probe-image-size": "7.0.1",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
"pug": "2.0.4",
"pug": "3.0.2",
"punycode": "2.1.1",
"pureimage": "0.2.5",
"pureimage": "0.2.7",
"qrcode": "1.4.4",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
@ -221,49 +219,49 @@
"sass": "1.32.8",
"sass-loader": "11.0.1",
"seedrandom": "3.0.5",
"sharp": "0.27.1",
"sharp": "0.27.2",
"speakeasy": "2.0.0",
"stringz": "2.1.0",
"style-loader": "2.0.0",
"summaly": "2.4.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.6.1",
"systeminformation": "5.6.7",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.117.1",
"throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "8.0.17",
"ts-loader": "8.0.18",
"ts-node": "9.1.1",
"tslint": "6.1.3",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.31",
"typescript": "4.1.5",
"typescript": "4.2.3",
"ulid": "2.3.0",
"url-loader": "4.1.1",
"uuid": "8.3.2",
"v-debounce": "0.1.2",
"vanilla-tilt": "1.7.0",
"vue": "3.0.5",
"vue": "3.0.7",
"vue-color": "2.8.1",
"vue-json-pretty": "1.7.1",
"vue-loader": "16.1.2",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "4.0.4",
"vue-router": "4.0.5",
"vue-style-loader": "4.1.3",
"vuedraggable": "4.0.1",
"web-push": "3.4.4",
"webpack": "5.24.2",
"webpack": "5.26.3",
"webpack-cli": "4.5.0",
"websocket": "1.0.33",
"ws": "7.4.3",
"ws": "7.4.4",
"xev": "2.0.1"
},
"devDependencies": {
"@types/chai": "4.2.15",
"@types/fluent-ffmpeg": "2.1.16",
"chai": "4.3.0",
"chai": "4.3.4",
"cross-env": "7.0.3"
}
}

6
src/.eslintrc Normal file
View File

@ -0,0 +1,6 @@
{
"env": {
"node": true,
"commonjs": true
}
}

View File

@ -1,4 +1,24 @@
{
"env": {
"node": false,
},
"extends": [
"eslint:recommended",
"plugin:vue/recommended"
],
"rules": {
"vue/require-v-for-key": 0,
"vue/max-attributes-per-line": 0,
"vue/html-indent": 0,
"vue/html-self-closing": 0,
"vue/no-unused-vars": 0,
"vue/attributes-order": 0,
"vue/require-prop-types": 0,
"vue/require-default-prop": 0,
"vue/html-closing-bracket-spacing": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/no-v-html": 0
},
"globals": {
"_DEV_": false,
"_LANGS_": false,

View File

@ -350,7 +350,8 @@ export default defineComponent({
capture(withHandler = false) {
if (this.$i) {
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
// TODO: sr
this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},

View File

@ -325,7 +325,8 @@ export default defineComponent({
capture(withHandler = false) {
if (this.$i) {
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
// TODO: sr
this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},

View File

@ -1,7 +1,7 @@
<template>
<transition :name="$store.state.animation ? popup ? 'modal-popup' : 'modal' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
<div v-show="manualShowing != null ? manualShowing : showing" class="mk-modal" v-hotkey.global="keymap" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<div class="bg _modalBg" @click="onBgClick"></div>
<div class="bg _modalBg" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
<div class="content" :class="{ popup, fixed, top: position === 'top' }" @click.self="onBgClick" ref="content">
<slot></slot>
</div>

View File

@ -4,40 +4,6 @@
import '@/style.scss';
// TODO: そのうち消す
if (localStorage.getItem('vuex') != null) {
const vuex = JSON.parse(localStorage.getItem('vuex'));
localStorage.setItem('account', JSON.stringify({
...vuex.i,
token: localStorage.getItem('i')
}));
localStorage.setItem('accounts', JSON.stringify(vuex.device.accounts));
localStorage.setItem('miux:themes', JSON.stringify(vuex.device.themes));
if (vuex.device.userData) {
for (const [k, v] of Object.entries(vuex.device.userData)) {
localStorage.setItem('pizzax::base::' + k, JSON.stringify({
widgets: v.widgets
}));
if (v.deck) {
localStorage.setItem('pizzax::deck::' + k, JSON.stringify({
columns: v.deck.columns,
layout: v.deck.layout,
}));
}
}
}
localStorage.setItem('vuex-old', JSON.stringify(vuex));
localStorage.removeItem('vuex');
localStorage.removeItem('i');
localStorage.removeItem('locale');
location.reload();
}
import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing';
import { createApp, watch } from 'vue';

View File

@ -60,7 +60,7 @@ export default defineComponent({
methods: {
fetchDoc() {
fetch(`${url}/assets/docs/${lang}/${this.doc}.md`).then(res => res.text()).then(md => {
fetch(`${url}/doc-assets/${lang}/${this.doc}.md`).then(res => res.text()).then(md => {
this.parse(md);
});
},

View File

@ -4,9 +4,9 @@
<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $ts._pages.viewPage }}</MkA>
<div class="buttons" style="margin: 16px 0;">
<MkButton inline @click="save" primary class="save"><Fa :icon="faSave"/> {{ $ts.save }}</MkButton>
<MkButton inline @click="save" primary class="save" v-if="!readonly"><Fa :icon="faSave"/> {{ $ts.save }}</MkButton>
<MkButton inline @click="duplicate" class="duplicate" v-if="pageId"><Fa :icon="faCopy"/> {{ $ts.duplicate }}</MkButton>
<MkButton inline @click="del" class="delete" v-if="pageId"><Fa :icon="faTrashAlt"/> {{ $ts.delete }}</MkButton>
<MkButton inline @click="del" class="delete" v-if="pageId && !readonly"><Fa :icon="faTrashAlt"/> {{ $ts.delete }}</MkButton>
</div>
<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
@ -134,12 +134,18 @@ export default defineComponent({
data() {
return {
INFO: computed(() => this.initPageId ? {
title: this.$ts._pages.editPage,
icon: faPencilAlt,
} : {
title: this.$ts._pages.newPage,
icon: faPencilAlt,
INFO: computed(() => {
let title = this.$ts._pages.newPage;
if (this.initPageId) {
title = this.$ts._pages.editPage;
}
else if (this.initPageName && this.initUser) {
title = this.$ts._pages.readPage;
}
return {
title: title,
icon: faPencilAlt,
};
}),
author: this.$i,
readonly: false,

View File

@ -212,7 +212,6 @@ type Plugin = {
*/
export class ColdDeviceStorage {
public static default = {
themes: [] as Theme[], // TODO: そのうち消す
// TODO: テーマをアカウントに保存するようになったのにもかかわらず、以下のどのテーマを使うかという情報だけがブラウザ保存になっていて、アカウント切り替えたりログアウトしたときに不具合が発生するのでなんとかする
// テーマIDを保存するのではなく、テーマ自体を保存するようにすれば解決するかも
darkTheme: '8050783a-7f63-445a-b270-36d0f6ba1677',

View File

@ -33,30 +33,3 @@ export async function removeTheme(theme: Theme): Promise<void> {
await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
}
// TODO: そのうち消す
if (ColdDeviceStorage.get('themes').length > 0) {
const lsThemes = ColdDeviceStorage.get('themes');
let registryThemes;
try {
registryThemes = await api('i/registry/get', { scope: ['client'], key: 'themes' });
} catch (e) {
if (e.code === 'NO_SUCH_KEY') {
registryThemes = [];
} else {
throw e;
}
}
const themes = [] as Theme[];
for (const theme of lsThemes) {
if (themes.some(x => x.id === theme.id)) continue;
themes.push(theme);
}
for (const theme of registryThemes) {
if (themes.some(x => x.id === theme.id)) continue;
themes.push(theme);
}
await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
ColdDeviceStorage.set('themes', []);
}

View File

@ -325,7 +325,8 @@ export default defineComponent({
capture(withHandler = false) {
if (this.$i) {
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
// TODO: sr
this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},

View File

@ -1,3 +1,5 @@
// TODO: 消したい
const interval = 30 * 60 * 1000;
import { AttestationChallenges } from '../models';
import { LessThan } from 'typeorm';

View File

@ -1,5 +1,5 @@
import Xev from 'xev';
import { deliverQueue, inboxQueue } from '../queue';
import { deliverQueue, inboxQueue } from '../queue/queues';
const ev = new Xev();

View File

@ -75,5 +75,5 @@ async function net() {
// FS STAT
async function fs() {
const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
return data;
return data || { rIO_sec: 0, wIO_sec: 0 };
}

View File

@ -1,3 +1,7 @@
// https://github.com/typeorm/typeorm/issues/2400
const types = require('pg').types;
types.setTypeParser(20, Number);
import { createConnection, Logger, getConnection } from 'typeorm';
import config from '../config';
import { entities as charts } from '../services/chart/entities';

View File

@ -1,58 +1,58 @@
# Misskey API
# API de Misskey
MisskeyAPIを使ってMisskeyクライアント、Misskey連携Webサービス、Bot等(以下「アプリケーション」と呼びます)を開発できます。 ストリーミングAPIもあるので、リアルタイム性のあるアプリケーションを作ることも可能です。
Vous pouvez utiliser l'API de Misskey pour développer des clients Misskey, des services web s'intégrant à Misskey, des Bots (que nous appellerons plus loin "Applications"), etc. Comme l'API streaming est aussi implémenté, vous avez la possibilité de créer des applications de temps réel.
APIを使い始めるには、まずアクセストークンを取得する必要があります。 このドキュメントでは、アクセストークンを取得する手順を説明した後、基本的なAPIの使い方を説明します。
Pour pouvoir vous servir de l'API, il vous faudra d'abord obtenir un jeton d'accès. Ce guide a été conçu pour vous accompagner dans le processus d'obtention du jeton d'accès, puis donner des instructions de base sur l'utilisation de l'API.
## アクセストークンの取得
基本的に、APIはリクエストにはアクセストークンが必要となります。 APIにリクエストするのが自分自身なのか、不特定の利用者に使ってもらうアプリケーションなのかによって取得手順は異なります。
## Obtenir le jeton d'accès
Une requête d'API, par essence, nécessite un jeton d'accès. La procédure d'acquisition du jeton diffère selon que vous effectuez la requête vous-même, ou qu'elle est envoyée via une application par un utilisateur final non défini.
* 前者の場合: [「自分自身のアクセストークンを手動発行する」](#自分自身のアクセストークンを手動発行する)に進む
* 後者の場合: [「アプリケーション利用者にアクセストークンの発行をリクエストする」](#アプリケーション利用者にアクセストークンの発行をリクエストする)に進む
* Dans le premier cas : allez à [« Générer manuellement un jeton d'accès pour son propre compte »](#自分自身のアクセストークンを手動発行する).
* Dans le second cas : allez à [« Demander la génération du jeton d'accès via un utilisateur d'application »](#アプリケーション利用者にアクセストークンの発行をリクエストする).
### 自分自身のアクセストークンを手動発行する
「設定 > API」で、自分のアクセストークンを発行できます。
### Générer manuellement un jeton d'accès pour son propre compte
Vous pouvez générer votre propre jeton d'accès en allant dans { Paramètres > API }.
[「APIの使い方」へ進む](#APIの使い方)
[Continuer avec « Utiliser l'API ».](#APIの使い方)
### アプリケーション利用者にアクセストークンの発行をリクエストする
アプリケーション利用者のアクセストークンを取得するには、以下の手順で発行をリクエストします。
### Demander la génération du jeton d'accès via un utilisateur d'application
Pour obtenir un jeton d'accès pour le compte utilisateur final de votre application, suivez la procédure de génération ci-dessous.
#### Step 1
#### Étape 1
UUIDを生成する。以後これをセッションIDと呼びます。
Générez un UUID. Nous l'appellerons « ID de session » dans la suite de ce guide.
> このセッションIDは毎回生成し、使いまわさないようにしてください。
> Un même ID de session ne devrait pas être utilisé plusieurs fois ; veillez à en générer un nouveau pour chaque jeton d'accès.
#### Step 2
#### Étape 2
`{_URL_}/miauth/{session}`をユーザーのブラウザで表示させる。`{session}`の部分は、セッションIDに置き換えてください。
> : `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f`
Ouvrez l'adresse `{_URL_}/miauth/{session}` dans le navigateur de l'utilisateur. Remplacez alors la partie `{session}` de l'URL par l'ID de session que vous venez de générer.
> Par ex. : `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f`
表示する際、URLにクエリパラメータとしていくつかのオプションを設定できます:
* `name` ... アプリケーション名
* > : `MissDeck`
* `icon` ... アプリケーションのアイコン画像URL
* > : `https://missdeck.example.com/icon.png`
* `callback` ... 認証が終わった後にリダイレクトするURL
* > : `https://missdeck.example.com/callback`
* リダイレクト時には、`session`というクエリパラメータでセッションIDが付きます
* `permission` ... アプリケーションが要求する権限
* > : `write:notes,write:following,read:drive`
* 要求する権限を`,`で区切って列挙します
* どのような権限があるかは[APIリファレンス](/api-doc)で確認できます
En ouvrant cette URL, vous pourrez configurer un certain nombre d'options pour les paramètres de requête :
* `name` : nom de l'application
* > Ex. : `MissDeck`
* `icon` : URL de l'icône de l'application
* > Ex. : `https://missdeck.example.com/icon.png`
* `callback` : URL de redirection après l'authentification
* > Ex. : `https://missdeck.example.com/callback`
* Lors de la redirection, un paramètre de requête `session` contenant l'ID de session sera joint.
* `permission` : permissions requises par l'application
* > Ex. : `write:notes,write:following,read:drive`
* Listez les permissions requises en utilisant une `,` pour les séparer.
* Vous pouvez vérifier quelles sont les permissions disponibles sur [les références API de Misskey](/api-doc).
#### Step 3
ユーザーが発行を許可した後、`{_URL_}/api/miauth/{session}/check`にPOSTリクエストすると、レスポンスとしてアクセストークンを含むJSONが返ります。
#### Étape 3
Si vous envoyez une requête POST à `{_URL_}/api/miauth/{session}/check` une fois que l'utilisateur a validé le jeton d'accès, la réponse arrivera sous forme de fichier JSON contenant ce jeton.
レスポンスに含まれるプロパティ:
* `token` ... ユーザーのアクセストークン
* `user` ... ユーザーの情報
Propriétés incluses dans la réponse :
* `token` : jeton d'accès de l'utilisateur
* `user` : données de l'utilisateur
[「APIの使い方」へ進む](#APIの使い方)
[Continuer avec « Utiliser l'API ».](#APIの使い方)
## APIの使い方
**APIはすべてPOSTで、リクエスト/レスポンスともにJSON形式です。RESTではありません。** アクセストークンは、`i`というパラメータ名でリクエストに含めます。
## Utiliser l'API
**L'API utilise seulement la méthode POST, et toutes les requêtes / réponses sont au format JSON. REST n'est pas pris en charge. ** Le jeton d'accès s'insère dans le paramètre de requête nommé `i`.
* [APIリファレンス](/api-doc)
* [ストリーミングAPI](./stream)
* [Références API de Misskey](/api-doc)
* [API streaming](./stream)

View File

@ -34,7 +34,7 @@ Misskey Webクライアントのプラグイン機能を使うと、クライア
#### default
設定のデフォルト値
## APIリファレンス
## Références API de Misskey
AiScript標準で組み込まれているAPIは掲載しません。
### Mk:dialog(title text type)

View File

@ -1,2 +1,2 @@
# Abonnements
Lorsque vous suivez un·e utilisateur·rice, ses publications apparaissent dans votre fil.Cela n'inclut toutefois pas ses réponses aux autres utilisateur·ice·s. Vous pouvez vous désabonner du compte en cliquant une seconde fois.
Lorsque vous suivez un·e utilisateur·rice, ses publications apparaissent dans votre fil.Cela n'inclut toutefois pas ses réponses aux autres utilisateur·ice·s. Pour suivre un compte, rendez-vous sur sa page et cliquez sur le bouton « s'abonner ». Vous pouvez vous désabonner du compte en cliquant une seconde fois.

View File

@ -3,8 +3,8 @@
## Variables
Vous pouvez créer des pages dynamiques en utilisant des variables.Vous pouvez incorporer la valeur d'une variable en insérant le <b>{ variablename }</b> dans votre texte.Par exemple, si la valeur de la variable "thing" dans le texte <b>Hello { thing } world!</b> est <b>ai</b>, votre trexte devient alors : <b>Hello ai world!</b>.
Les variables sont évaluées du haut vers le bas, il n'est donc pas possible de référencer une variable située plus bas que celle en cours.Par exemple, si vous définissez, dans l'ordre, 3 variables telles que <b>A、B、C</b>, vous pourrez référencer en <b>C</b> aussi bien <b>A</b> que <b>B</b> ; par contre, vous ne pourrez référencer en <b>A</b> ni <b>B</b> ni <b>C</b>.
Les variables sont prises en compte dans l'ordre chronologique, de haut en bas. Il n'est donc pas possible d'appeler une variable située plus bas dans le code. Par exemple, si vous définissez, dans l'ordre, 3 variables telles que <b>A, B, C</b>, vous pourrez appeler en <b>C</b> aussi bien <b>A</b> que <b>B</b> ; par contre, vous ne pourrez appeler en <b>A</b> ni <b>B</b> ni <b>C</b>.
Pour recevoir une entrée utilisateur, ajoutez un bloc "Entrée" sur la page et définissez le nom des variables que vous souhaitez stocker dans le champ "Nom de la variable" (les variables seront créées automatiquement).Vous pourrez alors exécuter les actions en fonction de l'entrée utilisateur de ces variables.
Utiliser des fonctions vous permettra de mettre en place une façon de calculer des valeurs que vous pourrez réutiliser.Pour créer des fonctions, il faut d'abord définir une variable du type "fonction".Ensuite, vous pouvez configurer des arguments dont la valeur sera utilisable comme une variable à l'intérieur de la fonction. Par ailleurs, il existe ce que l'on appelle des "fonctions d'ordre supérieur" dont les arguments sont aussi des fonctions. En plus de paramétrer des fonctions à l'avance, vous avez également la possibilité de définir des fonctions à l'improviste directement dans les arguments de ces "fonctions d'ordre supérieur".
Appeler des fonctions vous permet de définir des valeurs que vous pourrez réutiliser. Pour créer des fonctions, il faut d'abord définir une variable du type "fonction". Vous pouvez y configurer des « slots » (arguments), dont la valeur devient alors disponible en tant que variable à l'intérieur de la fonction. Par ailleurs, il existe ce que l'on appelle des "fonctions d'ordre supérieur" dont les arguments sont aussi des fonctions. En plus de paramétrer des fonctions à l'avance, vous avez également la possibilité de définir des fonctions à l'improviste directement dans les « slots » de ces fonctions d'ordre supérieur.

View File

@ -1,4 +1,4 @@
# ストリーミングAPI
# API streaming
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます。

View File

@ -6,7 +6,7 @@ Vous pouvez modifier l'apparence de votre client Misskey à l'aide de thèmes.
Paramètres > Thèmes
## Créer un thème
Les codes des thèmes sont écrits sous forme d'objets JSON5. Les thèmes comprennent les objets suivants :
Le code des thèmes est écrit sous forme d'objets JSON5. Les thèmes comprennent les objets suivants :
``` js
{
id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
@ -43,7 +43,7 @@ Les codes des thèmes sont écrits sous forme d'objets JSON5. Les thèmes compre
* `props` ... Définir un style de thème.Voir les explications ci-après.
### Définir un style de thème
C'est dans `props` que vous définirez le style de thème. Les propriétés deviendront des variables CSS et les valeurs spécifieront le contenu. Par ailleurs, les objets présents par défaut dans `props` sont hérités du thème de base. Ainsi, si le thème de `base` est clair `light` ce sera l'objet [_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5) ; et s'il est sombre `dark` ce sera l'objet [_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5). Cela signifie, par exemple, que s'il n'y pas de propriété `panel` définie dans les `props` du thème, alors ce sera la valeur `panel` du thème de base qui sera prise en compte.
C'est dans `props` que vous définirez le style de thème. Les propriétés deviendront des variables CSS et les valeurs associées spécifieront le contenu de ces variables. Par ailleurs, les objets présents par défaut dans `props` sont hérités du thème de base. Ainsi, si le thème de `base` est clair `light` ce sera l'objet [_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5) ; et s'il est sombre `dark` ce sera l'objet [_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5). Cela signifie, par exemple, que s'il n'y pas de propriété `panel` définie dans les `props` du thème, alors ce sera la valeur `panel` du thème de base qui sera prise en compte.
#### Syntaxe des valeurs
* Codes de couleur Hex

View File

@ -1,2 +1,2 @@
# カスタム絵文字
カスタム絵文字は、インスタンスで用意された画像を絵文字のように使える機能です。 ノート、リアクション、チャット、自己紹介、名前などの場所で使うことができます。 カスタム絵文字をそれらの場所で使うには、絵文字ピッカーボタン(ある場合)を押すか、`:`を入力して絵文字サジェストを表示します。 テキスト内に`:foo:`のような形式の文字列が見つかると、`foo`の部分がカスタム絵文字名と解釈され、表示時には対応したカスタム絵文字に置き換わります。
# Emoji personalizzati
Gli emoji personalizzati sono una funzionalità che ti permette di usare delle immagini preparate dalla tua istanza come emoji. Si possono usare in note, reazioni, chat, nella biografia di profilo, nel tuo nome, e altrove su Misskey. Per usare gli emoji personalizzati, puoi aprire la tastiera emoji (quando c'è), oppure visualizzare suggerimenti emoji scrivendo `:`. Quando una sequenza di caratteri del tipo `:foo:` è trovata in un testo, la parte centrale `foo` viene interpretata come un nome di emoji personalizzato e quindi viene sostituita dall'emoji corrispondente.

View File

@ -1,2 +1,2 @@
# Seiguiti
ユーザーをフォローすると、タイムラインにそのユーザーの投稿が表示されるようになります。ただし、他のユーザーに対する返信は含まれません。 ユーザーをフォローするには、ユーザーページの「フォロー」ボタンをクリックします。フォローを解除するには、もう一度クリックします。
# Follow
Se segui un utente, le sue pubblicazioni verranno mostrate sulla tua timeline. A esclusione delle sue risposte ad altrə utenti. Per seguire un utente, bisogna premere il pulsante "seguire" della sua pagina. Premi una seconda volta sul pulsante per smettere di seguire l'account.

View File

@ -1,17 +1,17 @@
# キーボードショートカット
# Scorciatoie da tastiera
## グローバル
これらのショートカットは基本的にどこでも使えます。
## Generali
Le scorciatoie da tastiera sotto citate si possono usare praticamente ovunque.
<table>
<thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
<tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>Nuova pubblicazione</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">T</kbd></td><td>Evidenziare l'ultima pubblicazione sulla timeline</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>Mostrare/nascondere notifiche</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>Cerca</td><td><b>S</b>earch</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>Visualizzare l'aiuto</td><td><b>H</b>elp</td></tr>
</tbody>
</table>
@ -19,7 +19,7 @@
<table>
<thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
<tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>上の投稿にフォーカスを移動</td><td>-</td></tr>
@ -37,24 +37,24 @@
</tbody>
</table>
## Renoteフォーム
## Finestra Rinota
<table>
<thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
<tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key">Enter</kbd></td><td>Renoteする</td><td>-</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>フォームを展開する</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>フォームを閉じる</td><td>-</td></tr>
<tr><td><kbd class="key">Enter</kbd></td><td>Rinotare</td><td>-</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>Aprire finestra</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>Chiudere finestra</td><td>-</td></tr>
</tbody>
</table>
## リアクションフォーム
デフォルトで「👍」にフォーカスが当たっている状態です。
## Pannello reazioni
La reazione "👍" è impostata come reazione predefinita.
<table>
<thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
<tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd></td><td>上のリアクションにフォーカスを移動</td><td>-</td></tr>

View File

@ -1,13 +1,13 @@
# Silenzia
# Silenziare
ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります:
Quando si silenzia un utente, i successivi contenuti che lo riguardano non saranno più visualizzati su Misskey:
* タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRenote)
* そのユーザーからの通知
* メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴
* le pubblicazioni dell'utente sia nelle timeline che nei risultati di ricerca, così come le sue risposte e Rinote;
* le notifiche riguardo all'utente;
* la cronologia dei messaggi scambiati con l'utente nella chat.
ユーザーをミュートするには、対象のユーザーのユーザーページに表示されている「ミュート」ボタンを押します。
Per silenziare un utente, premi il pulsante "Silenzia" che si trova sulla sua pagina profilo.
ミュートを行ったことは相手に通知されず、ミュートされていることを知ることもできません。
Gli utenti silenziati da te non vengono informati; nello stesso modo, tu non sarai infomat@ se vieni silenziat@ da un altr@ utente.
設定>ミュート から、自分がミュートしているユーザー一覧を確認することができます。
Puoi controllare la lista di utenti che hai silenziato nelle Impostazioni account > "Silenziati / Bloccati".

View File

@ -1,4 +1,4 @@
# Pages
# Pagine
## Variabili
変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。

View File

@ -1,11 +1,11 @@
# Reazione
他の人のノートに、絵文字を付けて簡単にあなたの反応を伝えられる機能です。 リアクションするには、ノートの + アイコンをクリックしてピッカーを表示し、絵文字を選択します。 リアクションには[カスタム絵文字](./custom-emoji)も使用できます。
# Reazioni
Puoi mandare una reazione rapida alle note degli/delle altrə utenti apponendoci emoji. Per reagire, premi il pulsante "+" della nota per aprire il pannello reazioni e scegliere un emoji. Si possono anche usare gli [emoji personalizzati](./custom-emoji).
## リアクションピッカーのカスタマイズ
ピッカーに表示される絵文字を自分好みにカスタマイズすることができます。 設定の「リアクション」で設定します。
## Personalizzare il pannello reazioni
È possibile personalizzare il pannello reazioni selezionando gli emoji che vuoi usare. Puoi cambiare i predefiniti nella scheda "Reazioni" delle impostazioni.
## リモート投稿へのリアクションについて
リアクションはMisskeyオリジナルの機能であるため、リモートインスタンスがMisskeyでない限りは、ほとんどの場合「Like」としてアクティビティが送信されます。一般的にはLikeは「お気に入り」として実装されているようです。 また、相手がMisskeyであったとしても、カスタム絵文字リアクションは伝わらず、自動的に「👍」等にフォールバックされます。
## Inviare reazioni a server remoti
Siccome le reazioni sono una funzionalità originale di Misskey, vengono ricevute come semplici "Mi piace" dalla maggior parte delle istanze remote del Fediverso, a meno che non sia un'altra istanza Misskey.In genere sul Fediverso, la funzionalità "Mi piace" viene implementata come funzione "Preferiti". Inoltre, se reagisci con un emoji personalizzato verrà automaticamente inoltrato come un "👍" o simile, anche se quella destinataria è un'istanza Misskey.
## リモートからのリアクションについて
リモートから「Like」アクティビティを受信したとき、Misskeyでは「👍」のリアクションとして解釈されます。
## Ricevere reazioni da server remoti
I "Mi piace" ricevuti da utenti di istanze remote vengono interpretati su Misskey come reazioni a forma di "👍".

View File

@ -1,12 +1,12 @@
# Tema
テーマを設定して、Misskeyクライアントの見た目を変更できます。
Puoi utilizzare i temi per cambiare l'aspetto del client Misskey.
## テーマの設定
設定 > テーマ
## Impostazioni tema
Impostazioni > Tema
## テーマを作成する
テーマコードはJSON5で記述されたテーマオブジェクトです。 テーマは以下のようなオブジェクトです。
## Creare un tema
Il codice dei temi è scritto a forma di oggetti JSON5. I temi contengono gli oggetti sotto citati:
``` js
{
id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
@ -42,10 +42,10 @@
* テーマはここで設定されたベーステーマを継承します。
* `props` ... テーマのスタイル定義。これから説明します。
### テーマのスタイル定義
### Impostare uno stile di tema
`props`下にはテーマのスタイルを定義します。 キーがCSSの変数名になり、バリューで中身を指定します。 なお、この`props`オブジェクトはベーステーマから継承されます。 ベーステーマは、このテーマの`base`が`light`なら[_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5)で、`dark`なら[_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5)です。 つまり、このテーマ内の`props`に`panel`というキーが無くても、そこにはベーステーマの`panel`があると見なされます。
#### バリューで使える構文
#### Sintassi dei valori
* 16進数で表された色
* 例: `#00ff00`
* `rgb(r, g, b)`形式で表された色
@ -61,8 +61,8 @@
* 関数(後述)
* `:{関数名}<{引数}<{色}`
#### Costante
#### Costanti
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。
#### Funzione
#### Funzioni
wip

View File

@ -1,15 +1,15 @@
# タイムラインの比較
# Confronto delle timeline
https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing
## Home
自分のフォローしているユーザーの投稿
Pubblicazioni degli utenti che segui.
## Locale
全てのローカルユーザーの「ホーム」指定されていない投稿
Pubblicazioni degli utenti della tua istanza. Non vengono mostrate le note pubblicate con lo stato "principale".
## ソーシャル
自分のフォローしているユーザーの投稿と、全てのローカルユーザーの「ホーム」指定されていない投稿
## Sociale
Raggruppa le timeline "home" e "locale".
## グローバル
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿
Tutte le pubblicazioni ricevute dall'istanza, sia locali che altre. Non vengono mostrate le note pubblicate con lo stato "home".

1
src/global.d.ts vendored Normal file
View File

@ -0,0 +1 @@
type FIXME = any;

View File

@ -0,0 +1,90 @@
// https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58
'use strict';
/**
* @callback BeforeShutdownListener
* @param {string} [signalOrEvent] The exit signal or event name received on the process.
*/
/**
* System signals the app will listen to initiate shutdown.
* @const {string[]}
*/
const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
/**
* Time in milliseconds to wait before forcing shutdown.
* @const {number}
*/
const SHUTDOWN_TIMEOUT = 15000;
/**
* A queue of listener callbacks to execute before shutting
* down the process.
* @type {BeforeShutdownListener[]}
*/
const shutdownListeners = [];
/**
* Listen for signals and execute given `fn` function once.
* @param {string[]} signals System signals to listen to.
* @param {function(string)} fn Function to execute on shutdown.
*/
const processOnce = (signals, fn) => {
for (const sig of signals) {
process.once(sig, fn);
}
};
/**
* Sets a forced shutdown mechanism that will exit the process after `timeout` milliseconds.
* @param {number} timeout Time to wait before forcing shutdown (milliseconds)
*/
const forceExitAfter = timeout => () => {
setTimeout(() => {
// Force shutdown after timeout
console.warn(`Could not close resources gracefully after ${timeout}ms: forcing shutdown`);
return process.exit(1);
}, timeout).unref();
};
/**
* Main process shutdown handler. Will invoke every previously registered async shutdown listener
* in the queue and exit with a code of `0`. Any `Promise` rejections from any listener will
* be logged out as a warning, but won't prevent other callbacks from executing.
* @param {string} signalOrEvent The exit signal or event name received on the process.
*/
async function shutdownHandler(signalOrEvent) {
console.warn(`Shutting down: received [${signalOrEvent}] signal`);
for (const listener of shutdownListeners) {
try {
await listener(signalOrEvent);
} catch (err) {
console.warn(`A shutdown handler failed before completing with: ${err.message || err}`);
}
}
return process.exit(0);
}
/**
* Registers a new shutdown listener to be invoked before exiting
* the main process. Listener handlers are guaranteed to be called in the order
* they were registered.
* @param {BeforeShutdownListener} listener The shutdown listener to register.
* @returns {BeforeShutdownListener} Echoes back the supplied `listener`.
*/
export function beforeShutdown(listener) {
shutdownListeners.push(listener);
return listener;
}
// Register shutdown callback that kills the process after `SHUTDOWN_TIMEOUT` milliseconds
// This prevents custom shutdown handlers from hanging the process indefinitely
processOnce(SHUTDOWN_SIGNALS, forceExitAfter(SHUTDOWN_TIMEOUT));
// Register process shutdown callback
// Will listen to incoming signal events and execute all registered handlers in the stack
processOnce(SHUTDOWN_SIGNALS, shutdownHandler);

43
src/misc/cache.ts Normal file
View File

@ -0,0 +1,43 @@
export class Cache<T> {
private cache: Map<string | null, { date: number; value: T; }>;
private lifetime: number;
constructor(lifetime: Cache<never>['lifetime']) {
this.cache = new Map();
this.lifetime = lifetime;
}
public set(key: string | null, value: T): void {
this.cache.set(key, {
date: Date.now(),
value
});
}
public get(key: string | null): T | undefined {
const cached = this.cache.get(key);
if (cached == null) return undefined;
if ((Date.now() - cached.date) > this.lifetime) {
this.cache.delete(key);
return undefined;
}
return cached.value;
}
public delete(key: string | null) {
this.cache.delete(key);
}
public async fetch(key: string | null, fetcher: () => Promise<T>): Promise<T> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
// Cache HIT
return cachedValue;
}
// Cache MISS
const value = await fetcher();
this.set(key, value);
return value;
}
}

View File

@ -32,4 +32,4 @@ setInterval(() => {
fetchMeta(true).then(meta => {
cache = meta;
});
}, 5000);
}, 1000 * 10);

10
src/misc/keypair-store.ts Normal file
View File

@ -0,0 +1,10 @@
import { UserKeypairs } from '../models';
import { User } from '../models/entities/user';
import { UserKeypair } from '../models/entities/user-keypair';
import { Cache } from './cache';
const cache = new Cache<UserKeypair>(Infinity);
export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> {
return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId));
}

119
src/misc/populate-emojis.ts Normal file
View File

@ -0,0 +1,119 @@
import { In } from 'typeorm';
import { Emojis } from '../models';
import { Emoji } from '../models/entities/emoji';
import { Note } from '../models/entities/note';
import { Cache } from './cache';
import { isSelfHost, toPunyNullable } from './convert-host';
import { decodeReaction } from './reaction-lib';
const cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12);
/**
*
*/
type PopulatedEmoji = {
name: string;
url: string;
};
function normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
// クエリに使うホスト
let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
: src === undefined ? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
: isSelfHost(src) ? null // 自ホスト指定
: (src || noteUserHost); // 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
host = toPunyNullable(host);
return host;
}
function parseEmojiStr(emojiName: string, noteUserHost: string | null) {
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
if (!match) return { name: null, host: null };
const name = match[1];
// ホスト正規化
const host = toPunyNullable(normalizeHost(match[2], noteUserHost));
return { name, host };
}
/**
*
* @param emojiName (:, @. (decodeReactionで可能))
* @param noteUserHost
* @returns , nullは未マッチを意味する
*/
export async function populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> {
const { name, host } = parseEmojiStr(emojiName, noteUserHost);
if (name == null) return null;
const queryOrNull = async () => (await Emojis.findOne({
name,
host
})) || null;
const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
if (emoji == null) return null;
return {
name: emojiName,
url: emoji.url,
};
}
/**
* (, )
*/
export async function populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<PopulatedEmoji[]> {
const emojis = await Promise.all(emojiNames.map(x => populateEmoji(x, noteUserHost)));
return emojis.filter((x): x is PopulatedEmoji => x != null);
}
export function aggregateNoteEmojis(notes: Note[]) {
let emojis: { name: string | null; host: string | null; }[] = [];
for (const note of notes) {
emojis = emojis.concat(note.emojis
.map(e => parseEmojiStr(e, note.userHost)));
if (note.renote) {
emojis = emojis.concat(note.renote.emojis
.map(e => parseEmojiStr(e, note.renote!.userHost)));
if (note.renote.user) {
emojis = emojis.concat(note.renote.user.emojis
.map(e => parseEmojiStr(e, note.renote!.userHost)));
}
}
const customReactions = Object.keys(note.reactions).map(x => decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
emojis = emojis.concat(customReactions);
if (note.user) {
emojis = emojis.concat(note.user.emojis
.map(e => parseEmojiStr(e, note.userHost)));
}
}
return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[];
}
/**
*
*/
export async function prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
const notCachedEmojis = emojis.filter(emoji => cache.get(`${emoji.name} ${emoji.host}`) == null);
const emojisQuery: any[] = [];
const hosts = new Set(notCachedEmojis.map(e => e.host));
for (const host of hosts) {
emojisQuery.push({
name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)),
host: host
});
}
const _emojis = emojisQuery.length > 0 ? await Emojis.find({
where: emojisQuery,
select: ['name', 'host', 'url']
}) : [];
for (const emoji of _emojis) {
cache.set(`${emoji.name} ${emoji.host}`, emoji);
}
}

View File

@ -77,7 +77,7 @@ export class DriveFile {
default: {},
comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
})
public properties: Record<string, any>;
public properties: { width?: number; height?: number; avgColor?: string };
@Index()
@Column('boolean')

View File

@ -23,7 +23,7 @@ export class NoteReaction {
onDelete: 'CASCADE'
})
@JoinColumn()
public user: User | null;
public user?: User | null;
@Index()
@Column(id())
@ -33,7 +33,7 @@ export class NoteReaction {
onDelete: 'CASCADE'
})
@JoinColumn()
public note: Note | null;
public note?: Note | null;
// TODO: 対象noteのuserIdを非正規化したい(「受け取ったリアクション一覧」のようなものを(JOIN無しで)実装したいため)

View File

@ -56,16 +56,24 @@ export const packedAntennaSchema = {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const
}
}
},
execludeKeywords: {
excludeKeywords: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const
}
}
},
src: {

View File

@ -12,6 +12,12 @@ import { fetchMeta } from '../../misc/fetch-meta';
export type PackedDriveFile = SchemaType<typeof packedDriveFileSchema>;
type PackOptions = {
detail?: boolean,
self?: boolean,
withUser?: boolean,
};
@EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> {
public validateFileName(name: string): boolean {
@ -89,20 +95,19 @@ export class DriveFileRepository extends Repository<DriveFile> {
return parseInt(sum, 10) || 0;
}
public async pack(src: DriveFile['id'], options?: PackOptions): Promise<PackedDriveFile | null>;
public async pack(src: DriveFile, options?: PackOptions): Promise<PackedDriveFile>;
public async pack(
src: DriveFile['id'] | DriveFile,
options?: {
detail?: boolean,
self?: boolean,
withUser?: boolean,
}
): Promise<PackedDriveFile> {
options?: PackOptions
): Promise<PackedDriveFile | null> {
const opts = Object.assign({
detail: false,
self: false
}, options);
const file = typeof src === 'object' ? src : await this.findOneOrFail(src);
const file = typeof src === 'object' ? src : await this.findOne(src);
if (file == null) return null;
const meta = await fetchMeta();
@ -128,15 +133,12 @@ export class DriveFileRepository extends Repository<DriveFile> {
});
}
public packMany(
files: any[],
options?: {
detail?: boolean
self?: boolean,
withUser?: boolean,
}
public async packMany(
files: (DriveFile['id'] | DriveFile)[],
options?: PackOptions
) {
return Promise.all(files.map(f => this.pack(f, options)));
const items = await Promise.all(files.map(f => this.pack(f, options)));
return items.filter(x => x != null);
}
}
@ -197,12 +199,12 @@ export const packedDriveFileSchema = {
properties: {
width: {
type: 'number' as const,
optional: false as const, nullable: false as const,
optional: true as const, nullable: false as const,
example: 1280
},
height: {
type: 'number' as const,
optional: false as const, nullable: false as const,
optional: true as const, nullable: false as const,
example: 720
},
avgColor: {

View File

@ -1,14 +1,14 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { Note } from '../entities/note';
import { User } from '../entities/user';
import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls, Channels } from '..';
import { Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls, Channels } from '..';
import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '../../misc/reaction-lib';
import { toString } from '../../mfm/to-string';
import { parse } from '../../mfm/parse';
import { Emoji } from '../entities/emoji';
import { concat } from '../../prelude/array';
import { NoteReaction } from '../entities/note-reaction';
import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '../../misc/populate-emojis';
export type PackedNote = SchemaType<typeof packedNoteSchema>;
@ -83,6 +83,9 @@ export class NoteRepository extends Repository<Note> {
options?: {
detail?: boolean;
skipHide?: boolean;
_hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
};
}
): Promise<PackedNote> {
const opts = Object.assign({
@ -130,64 +133,17 @@ export class NoteRepository extends Repository<Note> {
};
}
/**
* emojisを解決する
* @param emojiNames Note等に添付されたカスタム絵文字名 (:)
* @param noteUserHost Noteのホスト
* @param reactionNames Note等にリアクションされたカスタム絵文字名 (:)
*/
async function populateEmojis(emojiNames: string[], noteUserHost: string | null, reactionNames: string[]) {
let all = [] as {
name: string,
url: string
}[];
// カスタム絵文字
if (emojiNames?.length > 0) {
const tmp = await Emojis.find({
where: {
name: In(emojiNames),
host: noteUserHost
},
select: ['name', 'host', 'url']
}).then(emojis => emojis.map((emoji: Emoji) => {
return {
name: emoji.name,
url: emoji.url,
};
}));
all = concat([all, tmp]);
}
const customReactions = reactionNames?.map(x => decodeReaction(x)).filter(x => x.name);
if (customReactions?.length > 0) {
const where = [] as {}[];
for (const customReaction of customReactions) {
where.push({
name: customReaction.name,
host: customReaction.host
});
}
const tmp = await Emojis.find({
where,
select: ['name', 'host', 'url']
}).then(emojis => emojis.map((emoji: Emoji) => {
return {
name: `${emoji.name}@${emoji.host || '.'}`, // @host付きでローカルは.
url: emoji.url,
};
}));
all = concat([all, tmp]);
}
return all;
}
async function populateMyReaction() {
if (options?._hint_?.myReactions) {
const reaction = options._hint_.myReactions.get(note.id);
if (reaction) {
return convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
return undefined;
}
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
const reaction = await NoteReactions.findOne({
userId: meId!,
noteId: note.id,
@ -212,11 +168,15 @@ export class NoteRepository extends Repository<Note> {
: await Channels.findOne(note.channelId)
: null;
const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, ''));
const packed = await awaitAll({
id: note.id,
createdAt: note.createdAt.toISOString(),
userId: note.userId,
user: Users.pack(note.user || note.userId, meId),
user: Users.pack(note.user || note.userId, meId, {
detail: false,
}),
text: text,
cw: note.cw,
visibility: note.visibility,
@ -227,7 +187,7 @@ export class NoteRepository extends Repository<Note> {
repliesCount: note.repliesCount,
reactions: convertLegacyReactions(note.reactions),
tags: note.tags.length > 0 ? note.tags : undefined,
emojis: populateEmojis(note.emojis, host, Object.keys(note.reactions)),
emojis: populateEmojis(note.emojis.concat(reactionEmojiNames), host),
fileIds: note.fileIds,
files: DriveFiles.packMany(note.fileIds),
replyId: note.replyId,
@ -244,12 +204,14 @@ export class NoteRepository extends Repository<Note> {
_prId_: (note as any)._prId_ || undefined,
...(opts.detail ? {
reply: note.replyId ? this.pack(note.replyId, meId, {
detail: false
reply: note.replyId ? this.pack(note.reply || note.replyId, meId, {
detail: false,
_hint_: options?._hint_
}) : undefined,
renote: note.renoteId ? this.pack(note.renoteId, meId, {
detail: true
renote: note.renoteId ? this.pack(note.renote || note.renoteId, meId, {
detail: true,
_hint_: options?._hint_
}) : undefined,
poll: note.hasPoll ? populatePoll() : undefined,
@ -272,15 +234,39 @@ export class NoteRepository extends Repository<Note> {
return packed;
}
public packMany(
notes: (Note['id'] | Note)[],
public async packMany(
notes: Note[],
me?: User['id'] | User | null | undefined,
options?: {
detail?: boolean;
skipHide?: boolean;
}
) {
return Promise.all(notes.map(n => this.pack(n, me, options)));
if (notes.length === 0) return [];
const meId = me ? typeof me === 'string' ? me : me.id : null;
const myReactionsMap = new Map<Note['id'], NoteReaction | null>();
if (meId) {
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...notes.map(n => n.id), ...renoteIds];
const myReactions = await NoteReactions.find({
userId: meId,
noteId: In(targets),
});
for (const target of targets) {
myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) || null);
}
}
await prefetchEmojis(aggregateNoteEmojis(notes));
return await Promise.all(notes.map(n => this.pack(n, me, {
...options,
_hint_: {
myReactions: myReactionsMap
}
})));
}
}

View File

@ -1,8 +1,12 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users, Notes, UserGroupInvitations, AccessTokens } from '..';
import { EntityRepository, In, Repository } from 'typeorm';
import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '..';
import { Notification } from '../entities/notification';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType } from '../../misc/schema';
import { Note } from '../entities/note';
import { NoteReaction } from '../entities/note-reaction';
import { User } from '../entities/user';
import { aggregateNoteEmojis, prefetchEmojis } from '../../misc/populate-emojis';
export type PackedNotification = SchemaType<typeof packedNotificationSchema>;
@ -10,6 +14,11 @@ export type PackedNotification = SchemaType<typeof packedNotificationSchema>;
export class NotificationRepository extends Repository<Notification> {
public async pack(
src: Notification['id'] | Notification,
options: {
_hintForEachNotes_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
};
}
): Promise<PackedNotification> {
const notification = typeof src === 'object' ? src : await this.findOneOrFail(src);
const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null;
@ -22,23 +31,41 @@ export class NotificationRepository extends Repository<Notification> {
userId: notification.notifierId,
user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
...(notification.type === 'mention' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId, {
detail: true,
_hint_: options._hintForEachNotes_
}),
} : {}),
...(notification.type === 'reply' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId, {
detail: true,
_hint_: options._hintForEachNotes_
}),
} : {}),
...(notification.type === 'renote' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId, {
detail: true,
_hint_: options._hintForEachNotes_
}),
} : {}),
...(notification.type === 'quote' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId, {
detail: true,
_hint_: options._hintForEachNotes_
}),
} : {}),
...(notification.type === 'reaction' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId, {
detail: true,
_hint_: options._hintForEachNotes_
}),
reaction: notification.reaction
} : {}),
...(notification.type === 'pollVote' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId, {
detail: true,
_hint_: options._hintForEachNotes_
}),
choice: notification.choice
} : {}),
...(notification.type === 'groupInvited' ? {
@ -52,10 +79,33 @@ export class NotificationRepository extends Repository<Notification> {
});
}
public packMany(
notifications: any[],
public async packMany(
notifications: Notification[],
meId: User['id']
) {
return Promise.all(notifications.map(x => this.pack(x)));
if (notifications.length === 0) return [];
const notes = notifications.filter(x => x.note != null).map(x => x.note!);
const noteIds = notes.map(n => n.id);
const myReactionsMap = new Map<Note['id'], NoteReaction | null>();
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...noteIds, ...renoteIds];
const myReactions = await NoteReactions.find({
userId: meId,
noteId: In(targets),
});
for (const target of targets) {
myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) || null);
}
await prefetchEmojis(aggregateNoteEmojis(notes));
return await Promise.all(notifications.map(x => this.pack(x, {
_hintForEachNotes_: {
myReactions: myReactionsMap
}
})));
}
}

View File

@ -1,10 +1,11 @@
import $ from 'cafy';
import { EntityRepository, Repository, In, Not } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '..';
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '..';
import config from '../../config';
import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
import { populateEmojis } from '../../misc/populate-emojis';
export type PackedUser = SchemaType<typeof packedUserSchema>;
@ -160,10 +161,11 @@ export class UserRepository extends Repository<User> {
const meId = me ? typeof me === 'string' ? me : me.id : null;
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.find({
where: { userId: user.id },
order: { id: 'DESC' }
}) : [];
const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin')
.where('pin.userId = :userId', { userId: user.id })
.innerJoinAndSelect('pin.note', 'note')
.orderBy('pin.id', 'DESC')
.getMany() : [];
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null;
const falsy = opts.detail ? false : undefined;
@ -188,15 +190,7 @@ export class UserRepository extends Repository<User> {
faviconUrl: instance.faviconUrl,
themeColor: instance.themeColor,
} : undefined) : undefined,
// カスタム絵文字添付
emojis: user.emojis.length > 0 ? Emojis.find({
where: {
name: In(user.emojis),
host: user.host
},
select: ['name', 'host', 'url', 'aliases']
}) : [],
emojis: populateEmojis(user.emojis, user.host),
...(opts.detail ? {
url: profile!.url,
@ -218,7 +212,7 @@ export class UserRepository extends Repository<User> {
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: pins.map(pin => pin.noteId),
pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, {
pinnedNotes: Notes.packMany(pins.map(pin => pin.note!), meId, {
detail: true
}),
pinnedPageId: profile!.pinnedPageId,

View File

@ -1,4 +1,3 @@
import * as Queue from 'bull';
import * as httpSignature from 'http-signature';
import config from '../config';
@ -13,22 +12,7 @@ import { queueLogger } from './logger';
import { DriveFile } from '../models/entities/drive-file';
import { getJobInfo } from './get-job-info';
import { IActivity } from '../remote/activitypub/type';
function initializeQueue(name: string, limitPerSec = -1) {
return new Queue(name, {
redis: {
port: config.redis.port,
host: config.redis.host,
password: config.redis.pass,
db: config.redis.db || 0,
},
prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue',
limiter: limitPerSec > 0 ? {
max: limitPerSec * 5,
duration: 5000
} : undefined
});
}
import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues';
export type InboxJobData = {
activity: IActivity,
@ -44,11 +28,6 @@ function renderError(e: Error): any {
};
}
export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128);
export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue('db');
export const objectStorageQueue = initializeQueue('objectStorage');
const deliverLogger = queueLogger.createSubLogger('deliver');
const inboxLogger = queueLogger.createSubLogger('inbox');
const dbLogger = queueLogger.createSubLogger('db');

18
src/queue/initialize.ts Normal file
View File

@ -0,0 +1,18 @@
import * as Queue from 'bull';
import config from '../config';
export function initialize(name: string, limitPerSec = -1) {
return new Queue(name, {
redis: {
port: config.redis.port,
host: config.redis.host,
password: config.redis.pass,
db: config.redis.db || 0,
},
prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue',
limiter: limitPerSec > 0 ? {
max: limitPerSec * 5,
duration: 5000
} : undefined
});
}

View File

@ -7,11 +7,15 @@ import { instanceChart } from '../../services/chart';
import { fetchInstanceMetadata } from '../../services/fetch-instance-metadata';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
import { Cache } from '../../misc/cache';
import { Instance } from '../../models/entities/instance';
const logger = new Logger('deliver');
let latest: string | null = null;
const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
export default async (job: Bull.Job) => {
const { host } = new URL(job.data.to);
@ -22,12 +26,15 @@ export default async (job: Bull.Job) => {
}
// isSuspendedなら中断
const suspendedHosts = await Instances.find({
where: {
isSuspended: true
},
cache: 60 * 1000
});
let suspendedHosts = suspendedHostsCache.get(null);
if (suspendedHosts == null) {
suspendedHosts = await Instances.find({
where: {
isSuspended: true
},
});
suspendedHostsCache.set(null, suspendedHosts);
}
if (suspendedHosts.map(x => x.host).includes(toPuny(host))) {
return 'skip (suspended)';
}

View File

@ -40,6 +40,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
return `Old keyId is no longer supported. ${keyIdLower}`;
}
// TDOO: キャッシュ
const dbResolver = new DbResolver();
// HTTP-Signature keyIdを元にDBから取得

7
src/queue/queues.ts Normal file
View File

@ -0,0 +1,7 @@
import config from '../config';
import { initialize as initializeQueue } from './initialize';
export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128);
export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue('db');
export const objectStorageQueue = initializeQueue('objectStorage');

View File

@ -76,7 +76,7 @@ export default class DeliverManager {
public async execute() {
if (!Users.isLocalUser(this.actor)) return;
const inboxes: string[] = [];
const inboxes = new Set<string>();
// build inbox list
for (const recipe of this.recipes) {
@ -89,13 +89,13 @@ export default class DeliverManager {
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!inboxes.includes(inbox)) inboxes.push(inbox);
inboxes.add(inbox);
}
}
} else if (isDirect(recipe)) {
// direct deliver
const inbox = recipe.to.inbox;
if (inbox && !inboxes.includes(inbox)) inboxes.push(inbox);
if (inbox) inboxes.add(inbox);
}
}

View File

@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
import { IActivity } from '../type';
import { LdSignature } from '../misc/ld-signature';
import { ILocalUser } from '../../../models/entities/user';
import { UserKeypairs } from '../../../models';
import { getUserKeypair } from '../../../misc/keypair-store';
export const renderActivity = (x: any): IActivity | null => {
if (x == null) return null;
@ -23,9 +23,7 @@ export const renderActivity = (x: any): IActivity | null => {
export const attachLdSignature = async (activity: any, user: ILocalUser): Promise<IActivity | null> => {
if (activity == null) return null;
const keypair = await UserKeypairs.findOneOrFail({
userId: user.id
});
const keypair = await getUserKeypair(user.id);
const obj = {
// as non-standards

View File

@ -8,7 +8,8 @@ import { getEmojis } from './note';
import renderEmoji from './emoji';
import { IIdentifier } from '../models/identifier';
import renderHashtag from './hashtag';
import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models';
import { DriveFiles, UserProfiles } from '../../../models';
import { getUserKeypair } from '../../../misc/keypair-store';
export async function renderPerson(user: ILocalUser) {
const id = `${config.url}/users/${user.id}`;
@ -49,7 +50,7 @@ export async function renderPerson(user: ILocalUser) {
...hashtagTags,
];
const keypair = await UserKeypairs.findOneOrFail(user.id);
const keypair = await getUserKeypair(user.id);
const person = {
type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person',

View File

@ -5,11 +5,11 @@ import * as crypto from 'crypto';
import config from '../../config';
import { ILocalUser } from '../../models/entities/user';
import { UserKeypairs } from '../../models';
import { getAgentByUrl } from '../../misc/fetch';
import { URL } from 'url';
import got from 'got';
import * as Got from 'got';
import { getUserKeypair } from '../../misc/keypair-store';
export default async (user: ILocalUser, url: string, object: any) => {
const timeout = 10 * 1000;
@ -22,9 +22,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
sha256.update(data);
const hash = sha256.digest('base64');
const keypair = await UserKeypairs.findOneOrFail({
userId: user.id
});
const keypair = await getUserKeypair(user.id);
await new Promise((resolve, reject) => {
const req = https.request({
@ -74,9 +72,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
export async function signedGet(url: string, user: ILocalUser) {
const timeout = 10 * 1000;
const keypair = await UserKeypairs.findOneOrFail({
userId: user.id
});
const keypair = await getUserKeypair(user.id);
const req = got.get<any>(url, {
headers: {

View File

@ -13,10 +13,11 @@ import Following from './activitypub/following';
import Featured from './activitypub/featured';
import { inbox as processInbox } from '../queue';
import { isSelfHost } from '../misc/convert-host';
import { Notes, Users, Emojis, UserKeypairs, NoteReactions } from '../models';
import { Notes, Users, Emojis, NoteReactions } from '../models';
import { ILocalUser, User } from '../models/entities/user';
import { In } from 'typeorm';
import { renderLike } from '../remote/activitypub/renderer/like';
import { getUserKeypair } from '../misc/keypair-store';
// Init router
const router = new Router();
@ -135,7 +136,7 @@ router.get('/users/:user/publickey', async ctx => {
return;
}
const keypair = await UserKeypairs.findOneOrFail(user.id);
const keypair = await getUserKeypair(user.id);
if (Users.isLocalUser(user)) {
ctx.body = renderActivity(renderKey(user, keypair));

View File

@ -2,6 +2,11 @@ import isNativeToken from './common/is-native-token';
import { User } from '../../models/entities/user';
import { Users, AccessTokens, Apps } from '../../models';
import { AccessToken } from '../../models/entities/access-token';
import { Cache } from '../../misc/cache';
// TODO: TypeORMのカスタムキャッシュプロバイダを使っても良いかも
// ref. https://github.com/typeorm/typeorm/blob/master/docs/caching.md
const cache = new Cache<User>(1000 * 60 * 60);
export default async (token: string): Promise<[User | null | undefined, AccessToken | null | undefined]> => {
if (token == null) {
@ -9,6 +14,11 @@ export default async (token: string): Promise<[User | null | undefined, AccessTo
}
if (isNativeToken(token)) {
const cached = cache.get(token);
if (cached) {
return [cached, null];
}
// Fetch user
const user = await Users
.findOne({ token });
@ -17,8 +27,11 @@ export default async (token: string): Promise<[User | null | undefined, AccessTo
throw new Error('user not found');
}
cache.set(token, user);
return [user, null];
} else {
// TODO: cache
const accessToken = await AccessTokens.findOne({
where: [{
hash: token.toLowerCase() // app

View File

@ -23,7 +23,7 @@ export async function injectFeatured(timeline: Note[], user?: User | null) {
.andWhere(`note.score > 0`)
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
.andWhere(`note.visibility = 'public'`)
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user');
if (user) {
query.andWhere('note.userId != :userId', { userId: user.id });

View File

@ -18,6 +18,7 @@ type executor<T extends IEndpointMeta> =
(params: Params<T>, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any, cleanup?: Function) =>
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
// TODO: API関数に user まるごと渡すのではなくユーザーIDなどの最小限のプロパティだけ渡すようにしたい(キャッシュとか考えないでよくなるため)
export default function <T extends IEndpointMeta>(meta: T, cb: executor<T>)
: (params: any, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any) => Promise<any> {
return (params: any, user: T['requireCredential'] extends true ? ILocalUser : ILocalUser | null, token: AccessToken | null, file?: any) => {

View File

@ -38,7 +38,7 @@ export default define(meta, async () => {
chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns)
});
await RegistrationTickets.save({
await RegistrationTickets.insert({
id: genId(),
createdAt: new Date(),
code,

View File

@ -53,7 +53,7 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.alreadyPromoted);
}
await PromoNotes.save({
await PromoNotes.insert({
noteId: note.id,
createdAt: new Date(),
expiresAt: new Date(ps.expiresAt),

View File

@ -73,7 +73,11 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.id IN (${ antennaQuery.getQuery() })`)
.leftJoinAndSelect('note.user', 'user')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser')
.setParameters(antennaQuery.getParameters());
generateVisibilityQuery(query, user);

View File

@ -58,7 +58,7 @@ export default define(meta, async (ps, user) => {
const now = new Date();
// Insert access token doc
await AccessTokens.save({
await AccessTokens.insert({
id: genId(),
createdAt: now,
lastUsedAt: now,

View File

@ -4,6 +4,7 @@ import define from '../../define';
import { ApiError } from '../../error';
import { Channels, ChannelFollowings } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
import { publishUserEvent } from '../../../../services/stream';
export const meta = {
tags: ['channels'],
@ -36,10 +37,12 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.noSuchChannel);
}
await ChannelFollowings.save({
await ChannelFollowings.insert({
id: genId(),
createdAt: new Date(),
followerId: user.id,
followeeId: channel.id,
});
publishUserEvent(user.id, 'followChannel', channel);
});

View File

@ -87,7 +87,11 @@ export default define(meta, async (ps, user) => {
//#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.channelId = :channelId', { channelId: channel.id })
.leftJoinAndSelect('note.user', 'user')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
//#endregion

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Channels, ChannelFollowings } from '../../../../models';
import { publishUserEvent } from '../../../../services/stream';
export const meta = {
tags: ['channels'],
@ -39,4 +40,6 @@ export default define(meta, async (ps, user) => {
followerId: user.id,
followeeId: channel.id,
});
publishUserEvent(user.id, 'unfollowChannel', channel);
});

View File

@ -68,7 +68,7 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.alreadyClipped);
}
await ClipNotes.save({
await ClipNotes.insert({
id: genId(),
noteId: note.id,
clipId: clip.id

View File

@ -71,7 +71,11 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.id IN (${ clipQuery.getQuery() })`)
.leftJoinAndSelect('note.user', 'user')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser')
.setParameters(clipQuery.getParameters());
if (user) {

View File

@ -1,6 +1,5 @@
import define from '../define';
import { RegistryItems, UserProfiles, Users } from '../../../models';
import { genId } from '../../../misc/gen-id';
import { Users } from '../../../models';
export const meta = {
desc: {
@ -23,28 +22,8 @@ export const meta = {
export default define(meta, async (ps, user, token) => {
const isSecure = token == null;
// TODO: そのうち消す
const profile = await UserProfiles.findOneOrFail(user.id);
for (const [k, v] of Object.entries(profile.clientData)) {
await RegistryItems.insert({
id: genId(),
createdAt: new Date(),
updatedAt: new Date(),
userId: user.id,
domain: null,
scope: ['client', 'base'],
key: k,
value: v
});
}
await UserProfiles.createQueryBuilder().update()
.set({
clientData: {},
})
.where('userId = :id', { id: user.id })
.execute();
return await Users.pack(user, user, {
// ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す
return await Users.pack(user.id, user, {
detail: true,
includeSecrets: isSecure
});

View File

@ -85,7 +85,13 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
.andWhere(`notification.notifieeId = :meId`, { meId: user.id })
.leftJoinAndSelect('notification.notifier', 'notifier');
.leftJoinAndSelect('notification.notifier', 'notifier')
.leftJoinAndSelect('notification.note', 'note')
.leftJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
query.andWhere(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`);
query.setParameters(mutingQuery.getParameters());
@ -110,5 +116,5 @@ export default define(meta, async (ps, user) => {
readNotification(user.id, notifications.map(x => x.id));
}
return await Notifications.packMany(notifications);
return await Notifications.packMany(notifications, user.id);
});

View File

@ -52,7 +52,7 @@ export default define(meta, async (ps, user) => {
}
// Create read
await AnnouncementReads.save({
await AnnouncementReads.insert({
id: genId(),
createdAt: new Date(),
announcementId: ps.announcementId,

View File

@ -1,6 +1,6 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import { publishMainStream } from '../../../../services/stream';
import { publishMainStream, publishUserEvent } from '../../../../services/stream';
import acceptAllFollowRequests from '../../../../services/following/requests/accept-all';
import { publishToFollowers } from '../../../../services/i/update';
import define from '../../define';
@ -317,6 +317,7 @@ export default define(meta, async (ps, user, token) => {
// Publish meUpdated event
publishMainStream(user.id, 'meUpdated', iObj);
publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOne(user.id));
// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
if (user.isLocked && ps.isLocked === false) {

View File

@ -52,7 +52,7 @@ export default define(meta, async (ps, user) => {
const now = new Date();
// Insert access token doc
await AccessTokens.save({
await AccessTokens.insert({
id: genId(),
createdAt: now,
lastUsedAt: now,

View File

@ -6,6 +6,7 @@ import { getUser } from '../../common/getters';
import { genId } from '../../../../misc/gen-id';
import { Mutings, NoteWatchings } from '../../../../models';
import { Muting } from '../../../../models/entities/muting';
import { publishUserEvent } from '../../../../services/stream';
export const meta = {
desc: {
@ -82,6 +83,8 @@ export default define(meta, async (ps, user) => {
muteeId: mutee.id,
} as Muting);
publishUserEvent(user.id, 'mute', mutee);
NoteWatchings.delete({
userId: muter.id,
noteUserId: mutee.id

View File

@ -4,6 +4,7 @@ import define from '../../define';
import { ApiError } from '../../error';
import { getUser } from '../../common/getters';
import { Mutings } from '../../../../models';
import { publishUserEvent } from '../../../../services/stream';
export const meta = {
desc: {
@ -76,4 +77,6 @@ export default define(meta, async (ps, user) => {
await Mutings.delete({
id: exist.id
});
publishUserEvent(user.id, 'unmute', mutee);
});

View File

@ -76,7 +76,11 @@ export default define(meta, async (ps) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.visibility = 'public'`)
.andWhere(`note.localOnly = FALSE`)
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
if (ps.local) {
query.andWhere('note.userHost IS NULL');

View File

@ -64,7 +64,11 @@ export default define(meta, async (ps, user) => {
}));
}));
}))
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);

View File

@ -61,7 +61,7 @@ export default define(meta, async (ps, user) => {
}
// Create favorite
await NoteFavorites.save({
await NoteFavorites.insert({
id: genId(),
createdAt: new Date(),
noteId: note.id,

View File

@ -49,7 +49,11 @@ export default define(meta, async (ps, user) => {
.andWhere(`note.score > 0`)
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
.andWhere(`note.visibility = 'public'`)
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
if (user) generateMutedUserQuery(query, user);

View File

@ -8,8 +8,6 @@ import { Notes } from '../../../../models';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
import { activeUsersChart } from '../../../../services/chart';
import { generateRepliesQuery } from '../../common/generate-replies-query';
import { injectPromo } from '../../common/inject-promo';
import { injectFeatured } from '../../common/inject-featured';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
export const meta = {
@ -81,7 +79,11 @@ export default define(meta, async (ps, user) => {
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL')
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
generateRepliesQuery(query, user);
if (user) generateMutedUserQuery(query, user);
@ -94,9 +96,6 @@ export default define(meta, async (ps, user) => {
const timeline = await query.take(ps.limit!).getMany();
await injectPromo(timeline, user);
await injectFeatured(timeline, user);
process.nextTick(() => {
if (user) {
activeUsersChart.update(user);

View File

@ -10,8 +10,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query'
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
import { activeUsersChart } from '../../../../services/chart';
import { generateRepliesQuery } from '../../common/generate-replies-query';
import { injectPromo } from '../../common/inject-promo';
import { injectFeatured } from '../../common/inject-featured';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-query';
@ -129,7 +127,11 @@ export default define(meta, async (ps, user) => {
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id })
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
}))
.leftJoinAndSelect('note.user', 'user')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser')
.setParameters(followingQuery.getParameters());
generateChannelQuery(query, user);
@ -175,9 +177,6 @@ export default define(meta, async (ps, user) => {
const timeline = await query.take(ps.limit!).getMany();
await injectPromo(timeline, user);
await injectFeatured(timeline, user);
process.nextTick(() => {
if (user) {
activeUsersChart.update(user);

View File

@ -10,8 +10,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query'
import { activeUsersChart } from '../../../../services/chart';
import { Brackets } from 'typeorm';
import { generateRepliesQuery } from '../../common/generate-replies-query';
import { injectPromo } from '../../common/inject-promo';
import { injectFeatured } from '../../common/inject-featured';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-query';
@ -98,7 +96,11 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
generateChannelQuery(query, user);
generateRepliesQuery(query, user);
@ -128,9 +130,6 @@ export default define(meta, async (ps, user) => {
const timeline = await query.take(ps.limit!).getMany();
await injectPromo(timeline, user);
await injectFeatured(timeline, user);
process.nextTick(() => {
if (user) {
activeUsersChart.update(user);

View File

@ -63,7 +63,11 @@ export default define(meta, async (ps, user) => {
.where(`:meId = ANY(note.mentions)`, { meId: user.id })
.orWhere(`:meId = ANY(note.visibleUserIds)`, { meId: user.id });
}))
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);
@ -79,9 +83,7 @@ export default define(meta, async (ps, user) => {
const mentions = await query.take(ps.limit!).getMany();
for (const note of mentions) {
read(user.id, note.id);
}
read(user.id, mentions.map(note => note.id));
return await Notes.packMany(mentions, user);
});

View File

@ -68,7 +68,11 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.renoteId = :renoteId`, { renoteId: note.id })
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);

View File

@ -59,7 +59,11 @@ export const meta = {
export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.replyId = :replyId', { replyId: ps.noteId })
.leftJoinAndSelect('note.user', 'user');
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);

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