Merge branch 'develop'

This commit is contained in:
syuilo 2021-03-22 15:27:08 +09:00
commit 9647631b0e
171 changed files with 3368 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" "parser": "@typescript-eslint/parser"
}, },
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended"
"plugin:vue/recommended"
], ],
"rules": { "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-console": 0,
"no-unused-vars": 0, "no-unused-vars": 0,
"no-empty": 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 (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -19,6 +19,6 @@
</head> </head>
<body> <body>
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc> <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> </body>
</html> </html>

View file

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

View file

@ -1,6 +1,7 @@
--- ---
_lang_: "Italiano" _lang_: "Italiano"
headlineMisskey: "Rete collegata tramite note" 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}" monthAndDay: "{day}/{month}"
search: "Cerca" search: "Cerca"
notifications: "Notifiche" notifications: "Notifiche"
@ -11,7 +12,7 @@ ok: "OK"
gotIt: "Capito!" gotIt: "Capito!"
cancel: "Annulla" cancel: "Annulla"
enterUsername: "Inserisci un nome utente" enterUsername: "Inserisci un nome utente"
renotedBy: "Condiviso da {user}" renotedBy: "Rinotato da {user}"
noNotes: "Nessuna nota!" noNotes: "Nessuna nota!"
noNotifications: "Nessuna notifica" noNotifications: "Nessuna notifica"
instance: "Istanza" instance: "Istanza"
@ -21,6 +22,7 @@ otherSettings: "Altre impostazioni"
openInWindow: "Apri in una finestra" openInWindow: "Apri in una finestra"
profile: "Profilo" profile: "Profilo"
timeline: "Timeline" timeline: "Timeline"
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
login: "Accedi" login: "Accedi"
loggingIn: "Accesso in corso..." loggingIn: "Accesso in corso..."
logout: "Esci" logout: "Esci"
@ -32,15 +34,16 @@ addUser: "Aggiungi utente"
favorite: "Preferiti" favorite: "Preferiti"
favorites: "Preferiti" favorites: "Preferiti"
unfavorite: "Rimuovi nota dai preferiti" unfavorite: "Rimuovi nota dai preferiti"
favorited: "Aggiunta ai preferiti." favorited: "Aggiunta ai tuoi preferiti."
alreadyFavorited: "Già tra i preferiti." alreadyFavorited: "Già tra i tuoi preferiti."
cantFavorite: "Impossibile aggiungere ai Preferiti." cantFavorite: "Impossibile aggiungere la nota ai preferiti."
pin: "Fissa sul profilo" pin: "Fissa sul profilo"
unpin: "Non fissare sul profilo" unpin: "Non fissare sul profilo"
copyContent: "Copia il contenuto" copyContent: "Copia il contenuto"
copyLink: "Copia link" copyLink: "Copia il link"
delete: "Elimina" 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" addToList: "Aggiungi alla lista"
sendMessage: "Invia messaggio" sendMessage: "Invia messaggio"
copyUsername: "Copia nome utente" copyUsername: "Copia nome utente"
@ -49,65 +52,104 @@ reply: "Rispondi"
loadMore: "Mostra di più" loadMore: "Mostra di più"
showMore: "Mostra di più" showMore: "Mostra di più"
youGotNewFollower: "Ha iniziato a seguirti" 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" mention: "Menzioni"
mentions: "Menzioni" mentions: "Menzioni"
directNotes: "Note dirette" directNotes: "Note dirette"
importAndExport: "Importa ed Esporta" importAndExport: "Importa ed esporta"
import: "Importa" import: "Importa"
export: "Esporta" export: "Esporta"
files: "Allegato" files: "Allegati"
download: "Scarica" 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" lists: "Liste"
noLists: "Qui non c'è ancora niente" noLists: "Nessuna lista"
note: "Nota" note: "Nota"
notes: "Note" notes: "Note"
following: "Seiguiti" following: "Follows"
followers: "Seguaci" followers: "Followers"
followsYou: "Ti segue" followsYou: "Ti segue"
createList: "Crea una nuova lista" createList: "Aggiungi una nuova lista"
manageLists: "Modifica lista" manageLists: "Gestisci liste"
error: "Errore" error: "Errore"
somethingHappened: "Qualcosa è andato storto." somethingHappened: "Si è verificato un problema"
retry: "Riprova" retry: "Riprova"
enterListName: "Inserisci il nome della lista" pageLoadError: "Caricamento pagina non riuscito. "
enterListName: "Nome della lista"
privacy: "Privacy" privacy: "Privacy"
makeFollowManuallyApprove: "Richiedi di approvare i follower manualmente"
defaultNoteVisibility: "Privacy predefinita delle note"
follow: "Segui" follow: "Segui"
followRequest: "Richiesta di seguire" followRequest: "Richiesta di follow"
followRequests: "Richiesta di seguire" followRequests: "Richieste di follow"
unfollow: "Smetti di seguire" unfollow: "Smetti di seguire"
followRequestPending: "In sospeso" followRequestPending: "La richiesta di follow deve essere approvata"
enterEmoji: "Inserisci emoji" enterEmoji: "Inserisci emoji"
renote: "Rinota" renote: "Rinota"
unrenote: "Annulla rinota" unrenote: "Annulla rinota"
renoted: "Condiviso!" renoted: "Rinotato!"
cantReRenote: "È impossibile rinota una condivisione." cantRenote: "È impossibile rinotare questa nota."
cantReRenote: "È impossibile rinotare una Rinota."
quote: "Cita" quote: "Cita"
pinnedNote: "Nota fissata"
pinned: "Fissa sul profilo" pinned: "Fissa sul profilo"
you: "Tu" you: "Tu"
clickToShow: "Clicca per visualizzare" clickToShow: "Clicca per visualizzare"
sensitive: "Contenuto sensibile" sensitive: "Contenuto sensibile"
add: "Aggiungi" add: "Aggiungi"
reaction: "Reazione" 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" attachCancel: "Rimuovi allegato"
markAsSensitive: "Segna come sensibile" markAsSensitive: "Segna come sensibile"
unmarkAsSensitive: "Segna come non sensibile" unmarkAsSensitive: "Segna come non sensibile"
enterFileName: "Nome del file"
mute: "Silenzia" mute: "Silenzia"
unmute: "Riattiva" unmute: "Riattiva"
block: "Blocca" block: "Blocca"
unblock: "Sblocca" unblock: "Sblocca"
suspend: "Sospendi" suspend: "Sospendi"
unsuspend: "Annulla la sospensione dell'account" unsuspend: "Annulla la sospensione dell'account"
blockConfirm: "Vuoi bloccare?" blockConfirm: "Vuoi davvero bloccare l'account?"
unblockConfirm: "Vuoi sbloccare?" 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" editWidgetsExit: "Modifica fine"
customEmojis: "Emoji personalizzati"
emoji: "Emoji" 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" general: "Generali"
wallpaper: "Sfondo" wallpaper: "Sfondo"
setWallpaper: "Imposta sfondo" setWallpaper: "Imposta sfondo"
removeWallpaper: "Elimina lo sfondo" removeWallpaper: "Elimina lo sfondo"
searchWith: "Cerca: {q}" 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" annotation: "Descrizione"
federation: "Federazione" federation: "Federazione"
instances: "Istanza" instances: "Istanza"
@ -115,36 +157,62 @@ storageUsage: "Volume di dischi"
charts: "Grafici" charts: "Grafici"
perHour: "All'ora" perHour: "All'ora"
perDay: "al giorno" perDay: "al giorno"
blockThisInstance: "Blocca l'istanza"
operations: "Operazioni"
software: "Software" software: "Software"
version: "Versione" version: "Versione"
metadata: "Metadato" metadata: "Metadato"
withNFiles: "{n} file in allegato"
monitor: "Monitorare"
jobQueue: "Coda di lavoro"
cpuAndMemory: "CPU e Memoria" cpuAndMemory: "CPU e Memoria"
network: "Rete" network: "Rete"
disk: "Disco" disk: "Disco"
instanceInfo: "Informazioni di istanza" instanceInfo: "Informazioni sull'istanza"
statistics: "Statistiche" statistics: "Statistiche"
clearQueue: "Cancella coda" clearQueue: "Svuota coda"
clearQueueConfirmTitle: "Cancella coda?" clearQueueConfirmTitle: "Vuoi davvero svuotare la coda?"
blockedInstances: "Istanza bloccati" clearCachedFiles: "Svuota cache"
muteAndBlock: "Silenziamento e blocco" 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" mutedUsers: "Account silenziati"
blockedUsers: "Account bloccati" blockedUsers: "Account bloccati"
noUsers: "Nessun utente trovato"
editProfile: "Modifica profilo" editProfile: "Modifica profilo"
noteDeleteConfirm: "Eliminare questo Nota?" 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" done: "Fine"
processing: "In elaborazione" processing: "In elaborazione"
blocked: "Bloccati" preview: "Anteprima"
default: "Predefinito"
noCustomEmojis: "Nessun emoji"
noJobs: "Nessun lavoro"
federating: "Federando"
blocked: "Bloccato"
suspended: "Sospes@"
all: "Tutti" all: "Tutti"
subscribing: "Iscrivendo"
publishing: "Pubblicando"
notResponding: "Nessuna risposta" notResponding: "Nessuna risposta"
instanceFollowing: "Seguiti dall'istanza"
instanceFollowers: "Followers dell'istanza"
instanceUsers: "Utenti dell'istanza"
changePassword: "Aggiorna Password" changePassword: "Aggiorna Password"
security: "Sicurezza" security: "Sicurezza"
retypedNotMatch: "Le password non corrispondono." retypedNotMatch: "Le password non corrispondono."
currentPassword: "Password attuale" currentPassword: "Password attuale"
newPassword: "Nuova Password" newPassword: "Nuova Password"
newPasswordRetype: "Conferma nuova password" newPasswordRetype: "Conferma password"
attachFile: "Allega file"
more: "Altri!" more: "Altri!"
featured: "Tendenze"
usernameOrUserId: "Nome utente o ID utente"
noSuchUser: "Nessun utente trovato"
lookup: "Cercare" lookup: "Cercare"
announcements: "Annuncio" announcements: "Annunci"
imageUrl: "URL dell'immagine" imageUrl: "URL dell'immagine"
remove: "Elimina" remove: "Elimina"
removed: "Il tuo Tweet è stato eliminato" removed: "Il tuo Tweet è stato eliminato"
@ -154,31 +222,66 @@ resetAreYouSure: "Reimposta"
saved: "Salvato" saved: "Salvato"
messaging: "Messaggi" messaging: "Messaggi"
upload: "Carica" upload: "Carica"
fromDrive: "Dal Drive"
fromUrl: "Dall'URL"
uploadFromUrl: "Incolla URL immagine" uploadFromUrl: "Incolla URL immagine"
uploadFromUrlDescription: "URL del file che vuoi caricare"
uploadFromUrlRequested: "Caricamento richiesto"
uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo."
explore: "Esplora" explore: "Esplora"
games: "Misskey Giochi" games: "Misskey Giochi"
messageRead: "Visualizzato" messageRead: "Visualizzato"
noMoreHistory: "Non c'è più cronologia da visualizzare"
startMessaging: "Nuovo messaggio" startMessaging: "Nuovo messaggio"
nUsersRead: "Letto da {n} persone"
agreeTo: "Sono d'accordo con {0}"
tos: "Termini di servizio" tos: "Termini di servizio"
start: "Inizia!"
home: "Home" home: "Home"
remoteUserCaution: "Può darsi che le informazioni siano incomplete perché questo è un utente remoto."
activity: "Attività"
images: "Immagini" images: "Immagini"
birthday: "Compleanno" birthday: "Compleanno"
yearsOld: "{age}Anni" yearsOld: "{age}Anni"
registeredDate: "Iscrizione a.." registeredDate: "Iscrizione a.."
location: "Posizione" location: "Posizione"
theme: "Tema" theme: "Tema"
themeForLightMode: "Tema da utilizzare per il modo chiaro"
themeForDarkMode: "Tema da utilizzare per il modo scuro"
light: "Chiaro" light: "Chiaro"
dark: "Scuro" dark: "Scuro"
lightThemes: "Tema Chiaro" lightThemes: "Tema Chiaro"
darkThemes: "Tema Scuro" darkThemes: "Tema Scuro"
syncDeviceDarkMode: "Sincronizza il tema scuro con le impostazioni del dispositivo"
drive: "Drive" drive: "Drive"
fileName: "Nome dell'allegato" 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" copyUrl: "Copia URL"
rename: "Modifica nome" rename: "Modifica nome"
avatar: "Foto del profilo" avatar: "Foto del profilo"
banner: "Foto d'intestazione" banner: "Intestazione"
nsfw: "Contenuti sensibili" nsfw: "Contenuti sensibili"
whenServerDisconnected: "Quando la connessione col server è persa"
disconnectedFromServer: "Disconness@ dal server"
reload: "Ricarica" reload: "Ricarica"
doNothing: "Nessun'azione"
reloadConfirm: "Vuoi ricaricare?"
watch: "Osserva" watch: "Osserva"
unwatch: "Smetti di Osserva" unwatch: "Smetti di Osserva"
accept: "Accetta" accept: "Accetta"
@ -195,21 +298,64 @@ today: "Oggi"
dayX: "{day}" dayX: "{day}"
monthX: "{month}" monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Pagine"
integration: "App collegate" integration: "App collegate"
connectSerice: "Connetti" connectSerice: "Connetti"
disconnectSerice: "Disconnetti" 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" registration: "Iscriviti"
enableRegistration: "Permettere nuove registrazioni"
invite: "Invita" 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" 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" hcaptcha: "hCaptcha"
enableHcaptcha: "Abilita hCaptcha" enableHcaptcha: "Abilita hCaptcha"
hcaptchaSiteKey: "Chiave del sito"
hcaptchaSecretKey: "Chiave segreta"
recaptcha: "reCAPTCHA" recaptcha: "reCAPTCHA"
enableRecaptcha: "Abilita reCAPTCHA" enableRecaptcha: "Abilita reCAPTCHA"
recaptchaSiteKey: "Chiave del sito"
recaptchaSecretKey: "Chiave segreta"
antennas: "Antenne"
manageAntennas: "Gestore delle antenne"
name: "Nome" 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" 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" 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" popularTags: "Tag di tendenza"
userList: "Liste" userList: "Liste"
about: "Informazioni" about: "Informazioni"
@ -218,96 +364,243 @@ administrator: "Amministratore"
token: "Token" token: "Token"
twoStepAuthentication: "Autenticazione a due fattori" twoStepAuthentication: "Autenticazione a due fattori"
moderator: "Moderatore" moderator: "Moderatore"
nUsersMentioned: "{n} utenti menzionatə"
securityKey: "Chiave di sicurezza"
securityKeyName: "Nome della chiave"
registerSecurityKey: "Registra una chiave di sicurezza"
lastUsed: "Ultima attività" lastUsed: "Ultima attività"
unregister: "Disattiva account" unregister: "Annulla l'iscrizione"
passwordLessLogin: "Accedi senza password"
resetPassword: "Reimposta 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" 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" help: "Guida"
inputMessageHere: "Scrivi messaggio qui"
close: "Chiudi" close: "Chiudi"
group: "Gruppo" group: "Gruppo"
groups: "Gruppi" groups: "Gruppi"
createGroup: "Nuovo gruppo" 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" transfer: "Trasferisci"
messagingWithUser: "Iniziare una chat con un altr@ utente"
messagingWithGroup: "Chattare in gruppo"
title: "Titolo" title: "Titolo"
text: "Testo"
enable: "Abilita"
next: "Avanti" next: "Avanti"
retype: "Conferma"
noteOf: "Note di {user}" 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" invitations: "Invita"
invitationCode: "Codice di invito" invitationCode: "Codice di invito"
checking: "Confermando"
available: "Consigliati" available: "Consigliati"
unavailable: "Il nome utente è già in uso" unavailable: "Il nome utente è già in uso"
usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'" usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'"
tooShort: "Troppo breve" tooShort: "Troppo breve"
tooLong: "Troppo lungo" tooLong: "Troppo lungo"
weakPassword: "Password debole"
normalPassword: "Password buona"
strongPassword: "Password forte"
passwordMatched: "Corretta"
passwordNotMatched: "Le password non corrispondono." 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" signinHistory: "Cronologia di accesso all'account"
category: "Categoria"
tags: "Tag" tags: "Tag"
docSource: "Sorgente della scheda"
createAccount: "Crea il tuo account" createAccount: "Crea il tuo account"
existingAcount: "Account esistente" 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" local: "Locale"
remote: "Remoto" remote: "Remoto"
accountSettings: "Impostazioni Account" total: "Totale"
weekOverWeekChanges: "Settimanale"
dayOverDayChanges: "Giornaliero"
appearance: "Aspetto"
clientSettings: "Impostazioni client"
accountSettings: "Impostazioni account"
promotion: "Promossa"
promote: "Pubblicizza" 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" objectStorageBaseUrl: "Base URL"
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "I file saranno conservati sotto la directory di questo prefisso."
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageUseSSL: "Usare SSL" objectStorageUseSSL: "Usare SSL"
serverLogs: "Log del server" serverLogs: "Log del server"
deleteAll: "Cancella cronologia" 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" listen: "Ascolta"
none: "Niente" none: "Niente"
showInPage: "Visualizza in pagina"
popout: "Finestra pop-out"
volume: "Volume" volume: "Volume"
masterVolume: "Volume principale"
details: "Dettagli" details: "Dettagli"
chooseEmoji: "Scegli emoji"
unableToProcess: "Impossibile compiere l'operazione"
recentUsed: "Usato di recente"
install: "Installa" install: "Installa"
uninstall: "Disinstalla" uninstall: "Disinstalla"
installedApps: "Applicazioni installate"
nothing: "Niente da visualizzare"
installedDate: "Data installazione" installedDate: "Data installazione"
lastUsedDate: "Data di ultimo uso"
state: "Stato" state: "Stato"
sort: "Ordina per" 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" poll: "Sondaggio"
useCw: "Nascondere media" useCw: "Nascondere media"
expandTweet: "Espandi tweet"
themeEditor: "Editor di temi"
description: "Descrizione" description: "Descrizione"
author: "Autore" author: "Autore"
leaveConfirm: "Ci sono delle modifiche ancora non salvate. Vuoi cancellarle?"
manage: "Gestione"
useFullReactionPicker: "Usa la totalità del pannello di reazioni"
width: "Larghezza" width: "Larghezza"
height: "Altezza" height: "Altezza"
large: "Grande" large: "Grande"
medium: "Predefinito" medium: "Predefinito"
small: "Piccolo" small: "Piccolo"
enableAll: "Abilita tutto"
disableAll: "Disabilita tutto"
tokenRequested: "Autorizza accesso all'account"
notificationType: "Tipo di notifiche"
edit: "Modifica" edit: "Modifica"
useStarForReactionFallback: "Se è sconosciuto l'emoji di reazione, usare la ★ come alternativa."
emailConfig: "Impostazioni server email"
email: "Email" email: "Email"
smtpHost: "Server remoto"
smtpUser: "Nome utente" smtpUser: "Nome utente"
smtpPass: "Password" smtpPass: "Password"
wordMute: "Parole silenziate" wordMute: "Filtri parole"
userSaysSomething: "{name} ha detto qualcosa"
display: "Visualizza" display: "Visualizza"
copy: "Copia" copy: "Copia"
logs: "Log" logs: "Log"
database: "Base di dati" database: "Base di dati"
channel: "Canale" channel: "Canale"
notificationSetting: "impostazioni delle notifiche" create: "Crea"
notificationSetting: "Impostazioni notifiche"
notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare."
other: "Avanzate" other: "Avanzate"
fileIdOrUrl: "ID o URL del file"
abuseReports: "Segnala" abuseReports: "Segnala"
reportAbuse: "Segnala" reportAbuse: "Segnala"
reportAbuseOf: "Segnala {name}" reportAbuseOf: "Segnala {name}"
send: "Inviare" send: "Inviare"
openInNewTab: "Apri in una nuova scheda" openInNewTab: "Apri in una nuova scheda"
editTheseSettingsMayBreakAccount: "Modificare queste impostazioni può danneggiare l'account."
waitingFor: "Aspettando {x}"
random: "Casuale" random: "Casuale"
system: "Sistema" system: "Sistema"
switchUi: "Cambiare interfaccia utente"
desktop: "Desktop" desktop: "Desktop"
clip: "Clip"
createNew: "Crea nuov@"
optional: "Opzionale" 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ì" yes: "Sì"
no: "No" 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" contact: "Contatti"
clips: "Clip"
experimentalFeatures: "Funzioni sperimentali"
developer: "Sviluppatore" developer: "Sviluppatore"
showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline"
duplicate: "Duplica" duplicate: "Duplica"
left: "Sinistra" left: "Sinistra"
center: "Centro" center: "Centro"
wide: "Largo" wide: "Largo"
clearCache: "Svuota cache"
onlineUsersCount: "{n} utenti online"
nUsers: "{n} utenti"
nNotes: "{n}Note" nNotes: "{n}Note"
myTheme: "I miei temi"
backgroundColor: "Sfondo" backgroundColor: "Sfondo"
textColor: "Testo"
value: "Valore" value: "Valore"
saveConfirm: "Vuoi salvare le modifiche?" saveConfirm: "Vuoi salvare le modifiche?"
deleteConfirm: "Rimuovere?" deleteConfirm: "Rimuovere?"
@ -317,45 +610,103 @@ currentVersion: "Versione attuale"
latestVersion: "Ultima versione" latestVersion: "Ultima versione"
editCode: "Modifica codice" editCode: "Modifica codice"
apply: "Applica" 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: _email:
_follow: _follow:
title: "Ha iniziato a seguirti" title: "Ha iniziato a seguirti"
_receiveFollowRequest:
title: "Hai ricevuto una richiesta di follow"
_registry: _registry:
key: "Dati" key: "Dati"
keys: "Dati" keys: "Dati"
createKey: "Crea chiave"
_aboutMisskey: _aboutMisskey:
source: "Codice sorgente"
morePatrons: "Ci sono molti altri che ci sostengono. Grazie 🥰" morePatrons: "Ci sono molti altri che ci sostengono. Grazie 🥰"
_mfm: _mfm:
mention: "Menzioni" mention: "Menzioni"
mentionDescription: "Si può menzionare un utente specifico digitando il suo nome utente subito dopo il segno @."
hashtag: "Hashtag"
url: "URL" url: "URL"
link: "Link" link: "Link"
bold: "Grassetto" bold: "Grassetto"
blockCode: "Codice(blocco)" blockCode: "Codice (blocco)"
inlineMath: "Espressione matematica(Immersione)" inlineMath: "Espressione matematica(Immersione)"
blockMath: "Espressione matematica(blocco)" blockMath: "Formula matematica (blocco)"
quote: "Cita il nota" quote: "Cita il nota"
emoji: "Emoji personalizzati"
search: "Cerca" search: "Cerca"
blur: "Sfocatura" blur: "Sfocatura"
font: "Tipo di carattere" font: "Tipo di carattere"
_reversi: _reversi:
gameSettings: "Impostazioni di gioco"
botSettings: "Opzioni del bot"
black: "Nero" black: "Nero"
white: "Bianco" white: "Bianco"
total: "Totale"
ended: "Esci" ended: "Esci"
_instanceTicker:
none: "Nascondi"
remote: "Mostra solo per gli/le utenti remotə"
always: "Mostra sempre"
_channel: _channel:
create: "Nuovo canale"
edit: "Gerisci canale"
setBanner: "Scegli intestazione"
removeBanner: "Rimuovi intestazione"
featured: "Tendenze" featured: "Tendenze"
owned: "I miei canali"
following: "Seguiti"
usersCount: "{n} partecipanti"
notesCount: "{n} note"
_sidebar: _sidebar:
icon: "Foto del profilo" icon: "Icone"
hide: "Nascondere" 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: _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" constant: "Costante"
defaultValue: "Valore predefinito" defaultValue: "Valore predefinito"
color: "Colore" color: "Colore"
key: "Chiave"
func: "Funzione" func: "Funzione"
argument: "Argomento"
darken: "Scuro" darken: "Scuro"
lighten: "Chiaro" lighten: "Chiaro"
keys: keys:
bg: "Sfondo" bg: "Sfondo"
fg: "Testo"
focus: "Focalizzazione"
indicator: "Indicatore"
panel: "Pannello"
shadow: "Ombra" 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" mention: "Menzioni"
renote: "Rinota" renote: "Rinota"
divider: "Interruzione di linea" divider: "Interruzione di linea"
@ -363,6 +714,8 @@ _sfx:
note: "Nota" note: "Nota"
notification: "Notifiche" notification: "Notifiche"
chat: "Messaggi" chat: "Messaggi"
antenna: "Ricezione dell'antenna"
channel: "Notifiche di canale"
_ago: _ago:
unknown: "Sconosciuto" unknown: "Sconosciuto"
future: "Futuro" future: "Futuro"
@ -381,14 +734,51 @@ _time:
day: "giorni" day: "giorni"
_tutorial: _tutorial:
title: "Come usare Misskey" 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: _permissions:
"read:blocks": "Visualizza gli account che hai bloccato." "read:blocks": "Visualizza gli account bloccati"
"write:blocks": "Gestisci gli account che hai bloccato." "write:blocks": "Gestisci gli account bloccati"
"read:favorites": "Visualizza Preferiti" "read:favorites": "Visualizza i tuoi preferiti"
"write:favorites": "Gestisci Preferiti" "write:favorites": "Gestisci i tuoi preferiti"
"read:following": "Vedi le informazioni di follow"
"write:following": "Seguiti/ Smetti di seguire" "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" "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: _weekday:
sunday: "Domenica" sunday: "Domenica"
monday: "Lunedì" monday: "Lunedì"
@ -409,6 +799,9 @@ _widgets:
photos: "Foto" photos: "Foto"
digitalClock: "Orologio digitale" digitalClock: "Orologio digitale"
federation: "Federazione" federation: "Federazione"
button: "Pulsante"
onlineUsers: "Utenti online"
jobQueue: "Coda di lavoro"
_cw: _cw:
hide: "Nascondere" hide: "Nascondere"
show: "Mostra di più" show: "Mostra di più"
@ -422,14 +815,20 @@ _poll:
voted: "Votato" voted: "Votato"
closed: "Terminato" closed: "Terminato"
_visibility: _visibility:
public: "Pubblico" public: "Pubblica"
publicDescription: "Visibile per tutti sul Fediverso"
home: "Home" home: "Home"
followers: "Seguaci" homeDescription: "Visibile solo sulla timeline \"Home\""
localOnly: "Solo Locale" followers: "Followers"
localOnlyDescription: "Solo locale" 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: _postForm:
replyPlaceholder: "Nota la tua risposta.." replyPlaceholder: "Nota la tua risposta.."
quotePlaceholder: "Cita Nota..." quotePlaceholder: "Cita Nota..."
channelPlaceholder: "Pubblica in canale"
_profile: _profile:
name: "Nome" name: "Nome"
username: "Nome utente" username: "Nome utente"
@ -437,16 +836,29 @@ _profile:
metadata: "Metadati" metadata: "Metadati"
metadataLabel: "Etichetta" metadataLabel: "Etichetta"
metadataContent: "Contenuto" metadataContent: "Contenuto"
changeBanner: "Cambia intestazione"
_exportOrImport: _exportOrImport:
followingList: "Seiguiti" allNotes: "Tutte le note"
muteList: "Silenzia" followingList: "Follows"
blockingList: "Blocca" muteList: "Account silenziati"
blockingList: "Account bloccati"
userLists: "Liste" 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: _timelines:
home: "Home" home: "Home"
local: "Locale" local: "Locale"
social: "Sociale"
_rooms: _rooms:
roomOf: "Camera di {user}"
_roomType: _roomType:
default: "Predefinito"
washitsu: "Washitsu" washitsu: "Washitsu"
_furnitures: _furnitures:
milk: "Cartone del latte" milk: "Cartone del latte"
@ -477,31 +889,59 @@ _rooms:
photoframe: "Cornice" photoframe: "Cornice"
cube: "Cubo" cube: "Cubo"
tv: "Televisore" tv: "Televisore"
pinguin: "Pinguini" pinguin: "Pinguino"
bin: "Cestino" bin: "Cestino"
cup-noodle: "Noodle istantanei" cup-noodle: "Noodle istantanei"
_pages: _pages:
created: "Pagina creata!"
pageSetting: "Impostazioni pagina"
viewSource: "Visualizza sorgente"
like: "Mi piace" like: "Mi piace"
unlike: "Togli Mi piace" unlike: "Togli Mi piace"
featured: "Popolari"
content: "Blocco di pagina"
variables: "Variabili" variables: "Variabili"
title: "Titolo" title: "Titolo"
hideTitleWhenPinned: "Nascondere il titolo pagina quando è fissata in cima al profilo."
font: "Tipo di carattere" font: "Tipo di carattere"
chooseBlock: "Aggiungi blocco"
blocks: blocks:
text: "Testo"
textarea: "Area di testo"
section: "Sezione"
image: "Immagini" image: "Immagini"
button: "Pulsante"
if: "Se" if: "Se"
_if: _if:
variable: "Variabili" variable: "Variabili"
_post: _post:
text: "Contenuto" text: "Contenuto"
_textInput: _textInput:
name: "Nome della variabile"
text: "Titolo" text: "Titolo"
default: "Valore predefinito"
_textareaInput: _textareaInput:
name: "Nome della variabile"
text: "Titolo" text: "Titolo"
default: "Valore predefinito"
_numberInput: _numberInput:
name: "Nome della variabile"
text: "Titolo" 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: _switch:
name: "Nome della variabile"
text: "Titolo" text: "Titolo"
default: "Valore predefinito"
_counter: _counter:
name: "Nome della variabile"
text: "Titolo" text: "Titolo"
_button: _button:
text: "Titolo" text: "Titolo"
@ -509,7 +949,9 @@ _pages:
_dialog: _dialog:
content: "Contenuto" content: "Contenuto"
_radioButton: _radioButton:
name: "Nome della variabile"
title: "Titolo" title: "Titolo"
default: "Valore predefinito"
script: script:
categories: categories:
comparison: "Metodo comparativo" comparison: "Metodo comparativo"
@ -518,6 +960,15 @@ _pages:
fn: "Funzione" fn: "Funzione"
list: "Liste" list: "Liste"
blocks: blocks:
text: "Testo"
_strLen:
arg1: "Testo"
_strPick:
arg1: "Testo"
_strReplace:
arg1: "Testo"
_strReverse:
arg1: "Testo"
_join: _join:
arg1: "Liste" arg1: "Liste"
_add: _add:
@ -575,26 +1026,46 @@ _pages:
arg1: "Liste" arg1: "Liste"
_listLen: _listLen:
arg1: "Liste" arg1: "Liste"
_stringToNumber:
arg1: "Testo"
_splitStrByLine:
arg1: "Testo"
ref: "Variabili" ref: "Variabili"
fn: "Funzione" fn: "Funzione"
types: types:
string: "Testo"
array: "Liste" array: "Liste"
_notification: _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" youGotQuote: "{name} ha citato il tuo Nota e ha detto"
youRenoted: "{name} ha rinota" youRenoted: "{name} ha rinotato"
youGotPoll: "{name} ha volluto." 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" 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: _types:
all: "Tutto" all: "Tutto"
follow: "Seiguiti" follow: "Follows"
mention: "Menzioni" mention: "Menzioni"
reply: "Rispondi" reply: "Rispondi"
renote: "Rinota" renote: "Rinota"
quote: "Cita" quote: "Cita"
reaction: "Reazione" 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: _deck:
_columns: _columns:
notifications: "Notifiche" notifications: "Notifiche"
tl: "Timeline" tl: "Timeline"
antenna: "Antenne"
list: "Liste" list: "Liste"
mentions: "Menzioni" mentions: "Menzioni"
direct: "Diretta"

View file

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

View file

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

View file

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

View file

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

View file

@ -438,6 +438,7 @@ signinWith: "以{x}登錄"
signinFailed: "登入失敗。 請檢查用戶名和密碼。" signinFailed: "登入失敗。 請檢查用戶名和密碼。"
tapSecurityKey: "點擊安全密鑰" tapSecurityKey: "點擊安全密鑰"
or: "或者" or: "或者"
language: "語言"
uiLanguage: "介面語言" uiLanguage: "介面語言"
groupInvited: "您有新的群組邀請" groupInvited: "您有新的群組邀請"
aboutX: "關於{x}" aboutX: "關於{x}"
@ -677,9 +678,12 @@ newVersionOfClientAvailable: "新版本的用戶端可用。"
usageAmount: "使用量" usageAmount: "使用量"
capacity: "容量" capacity: "容量"
inUse: "已使用" inUse: "已使用"
clear: "清除"
_email: _email:
_follow: _follow:
title: "您有新的追隨者" title: "您有新的追隨者"
_plugin:
manage: "管理插件"
_registry: _registry:
scope: "範圍" scope: "範圍"
key: "機碼" key: "機碼"
@ -702,7 +706,9 @@ _nsfw:
_mfm: _mfm:
cheatSheet: "MFM代碼小抄" cheatSheet: "MFM代碼小抄"
intro: "MFM是Misskey專用的標記語言可以在Misskey中的各個位置使用。 您可以這裏看到MFM可用語法列表。" intro: "MFM是Misskey專用的標記語言可以在Misskey中的各個位置使用。 您可以這裏看到MFM可用語法列表。"
dummy: "通過Misskey擴展Fediverse的世界"
mention: "提及" mention: "提及"
mentionDescription: "透過 @+用戶名 來標示特定使用者。"
hashtag: "#tag" hashtag: "#tag"
url: "URL" url: "URL"
link: "鏈接" 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", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.74.1", "version": "12.75.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -11,20 +11,20 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./index.js", "start": "node ./index.js",
"start-product": "cross-env NODE_ENV=production node ./index.js",
"init": "npm run migrate", "init": "npm run migrate",
"ormconfig": "node ./built/ormconfig.js", "ormconfig": "node ./built/ormconfig.js",
"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run", "migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
"migrateandstart": "npm run migrate && npm run start", "migrateandstart": "npm run migrate && npm run start",
"build": "webpack && gulp build", "build": "npm run build-webpack && npm run build-gulp",
"build-product": "cross-env NODE_ENV=production webpack && gulp build", "build-webpack": "webpack",
"webpack": "webpack", "build-gulp": "gulp build",
"watch": "webpack --watch", "watch": "concurrently \"npm:watch-*\"",
"gulp": "gulp build", "watch-webpack": "webpack --watch",
"watch-gulp": "gulp watch",
"clean": "gulp clean", "clean": "gulp clean",
"cleanall": "gulp cleanall", "cleanall": "gulp cleanall",
"lint": "tslint 'src/**/*.ts'", "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" "format": "gulp format"
}, },
"resolutions": { "resolutions": {
@ -35,25 +35,24 @@
"lodash": "^4.17.20" "lodash": "^4.17.20"
}, },
"dependencies": { "dependencies": {
"@babel/plugin-transform-runtime": "7.13.9", "@babel/plugin-transform-runtime": "7.13.10",
"@elastic/elasticsearch": "7.11.0", "@elastic/elasticsearch": "7.11.0",
"@fortawesome/fontawesome-svg-core": "1.2.34", "@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-brands-svg-icons": "5.15.2", "@fortawesome/free-brands-svg-icons": "5.15.3",
"@fortawesome/free-regular-svg-icons": "5.15.2", "@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.2", "@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/vue-fontawesome": "3.0.0-3", "@fortawesome/vue-fontawesome": "3.0.0-3",
"@koa/cors": "3.1.0", "@koa/cors": "3.1.0",
"@koa/multer": "3.0.0", "@koa/multer": "3.0.0",
"@koa/router": "9.0.1", "@koa/router": "9.0.1",
"@sentry/browser": "5.29.2", "@sentry/browser": "5.29.2",
"@sentry/tracing": "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", "@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.0", "@types/bull": "3.15.0",
"@types/cbor": "5.0.1", "@types/cbor": "5.0.1",
"@types/dateformat": "3.0.1", "@types/dateformat": "3.0.1",
"@types/double-ended-queue": "2.1.1",
"@types/escape-regexp": "0.0.0", "@types/escape-regexp": "0.0.0",
"@types/glob": "7.1.3", "@types/glob": "7.1.3",
"@types/gulp": "4.0.8", "@types/gulp": "4.0.8",
@ -61,10 +60,10 @@
"@types/gulp-replace": "0.0.31", "@types/gulp-replace": "0.0.31",
"@types/is-url": "1.2.28", "@types/is-url": "1.2.28",
"@types/js-yaml": "4.0.0", "@types/js-yaml": "4.0.0",
"@types/jsdom": "16.2.6", "@types/jsdom": "16.2.7",
"@types/jsonld": "1.5.4", "@types/jsonld": "1.5.5",
"@types/katex": "0.11.0", "@types/katex": "0.11.0",
"@types/koa": "2.13.0", "@types/koa": "2.13.1",
"@types/koa-bodyparser": "4.3.0", "@types/koa-bodyparser": "4.3.0",
"@types/koa-cors": "0.0.0", "@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19", "@types/koa-favicon": "2.0.19",
@ -78,9 +77,9 @@
"@types/markdown-it": "12.0.1", "@types/markdown-it": "12.0.1",
"@types/matter-js": "0.14.10", "@types/matter-js": "0.14.10",
"@types/mocha": "8.2.1", "@types/mocha": "8.2.1",
"@types/node": "14.14.31", "@types/node": "14.14.35",
"@types/node-fetch": "2.5.8", "@types/node-fetch": "2.5.8",
"@types/nodemailer": "6.4.0", "@types/nodemailer": "6.4.1",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
"@types/parse5": "6.0.0", "@types/parse5": "6.0.0",
@ -105,44 +104,44 @@
"@types/web-push": "3.3.0", "@types/web-push": "3.3.0",
"@types/webpack": "4.41.26", "@types/webpack": "4.41.26",
"@types/webpack-stream": "3.2.11", "@types/webpack-stream": "3.2.11",
"@types/websocket": "1.0.1", "@types/websocket": "1.0.2",
"@types/ws": "7.4.0", "@types/ws": "7.4.0",
"@typescript-eslint/parser": "4.16.1", "@typescript-eslint/parser": "4.18.0",
"@vue/compiler-sfc": "3.0.5", "@vue/compiler-sfc": "3.0.7",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"apexcharts": "3.25.0", "apexcharts": "3.26.0",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.848.0", "aws-sdk": "2.867.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.3", "blurhash": "1.1.3",
"broadcast-channel": "3.4.1", "broadcast-channel": "3.5.3",
"bull": "3.20.1", "bull": "3.21.1",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "7.0.3", "cbor": "7.0.4",
"chalk": "4.1.0", "chalk": "4.1.0",
"chart.js": "2.9.4", "chart.js": "2.9.4",
"cli-highlight": "2.1.10", "cli-highlight": "2.1.10",
"commander": "4.1.1", "commander": "4.1.1",
"concurrently": "6.0.0",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"core-js": "3.9.0", "core-js": "3.9.1",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "5.0.2", "css-loader": "5.1.3",
"cssnano": "4.1.10", "cssnano": "4.1.10",
"dateformat": "4.5.1", "dateformat": "4.5.1",
"diskusage": "1.1.3", "diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "7.21.0", "eslint": "7.22.0",
"eslint-plugin-vue": "7.6.0", "eslint-plugin-vue": "7.7.0",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"feed": "4.2.2", "feed": "4.2.2",
"fibers": "5.0.0", "fibers": "5.0.0",
"file-type": "16.2.0", "file-type": "16.3.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"glob": "7.1.6", "glob": "7.1.6",
"got": "11.8.1", "got": "11.8.2",
"gulp": "4.0.2", "gulp": "4.0.2",
"gulp-cssnano": "2.1.3", "gulp-cssnano": "2.1.3",
"gulp-rename": "2.0.0", "gulp-rename": "2.0.0",
@ -155,17 +154,17 @@
"http-proxy-agent": "4.0.1", "http-proxy-agent": "4.0.1",
"http-signature": "1.3.5", "http-signature": "1.3.5",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"idb-keyval": "5.0.2", "idb-keyval": "5.0.4",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0", "is-root": "2.1.0",
"is-svg": "4.2.1", "is-svg": "4.3.1",
"js-yaml": "4.0.0", "js-yaml": "4.0.0",
"jsdom": "16.4.0", "jsdom": "16.5.1",
"json5": "2.2.0", "json5": "2.2.0",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"jsonld": "4.0.1", "jsonld": "4.0.1",
"jsrsasign": "8.0.20", "jsrsasign": "8.0.20",
"katex": "0.12.0", "katex": "0.13.0",
"koa": "2.13.1", "koa": "2.13.1",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
@ -174,13 +173,13 @@
"koa-mount": "4.0.0", "koa-mount": "4.0.0",
"koa-send": "5.0.1", "koa-send": "5.0.1",
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "6.3.1", "koa-views": "7.0.1",
"langmap": "0.0.16", "langmap": "0.0.16",
"lookup-dns-cache": "2.1.0", "lookup-dns-cache": "2.1.0",
"markdown-it": "12.0.4", "markdown-it": "12.0.4",
"markdown-it-anchor": "7.0.2", "markdown-it-anchor": "7.1.0",
"matter-js": "0.16.1", "matter-js": "0.16.1",
"mocha": "8.3.0", "mocha": "8.3.2",
"moji": "0.5.1", "moji": "0.5.1",
"ms": "2.1.3", "ms": "2.1.3",
"multer": "1.4.2", "multer": "1.4.2",
@ -189,20 +188,19 @@
"nodemailer": "6.5.0", "nodemailer": "6.5.0",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"p-cancelable": "2.0.0",
"parse5": "6.0.1", "parse5": "6.0.1",
"parsimmon": "1.16.0", "parsimmon": "1.16.0",
"pg": "8.5.1", "pg": "8.5.1",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss": "8.2.7", "postcss": "8.2.8",
"postcss-loader": "5.0.0", "postcss-loader": "5.2.0",
"prismjs": "1.23.0", "prismjs": "1.23.0",
"probe-image-size": "6.0.0", "probe-image-size": "7.0.1",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"promise-sequential": "1.1.1", "promise-sequential": "1.1.1",
"pug": "2.0.4", "pug": "3.0.2",
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.2.5", "pureimage": "0.2.7",
"qrcode": "1.4.4", "qrcode": "1.4.4",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
@ -221,49 +219,49 @@
"sass": "1.32.8", "sass": "1.32.8",
"sass-loader": "11.0.1", "sass-loader": "11.0.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.27.1", "sharp": "0.27.2",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"style-loader": "2.0.0", "style-loader": "2.0.0",
"summaly": "2.4.0", "summaly": "2.4.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.6.1", "systeminformation": "5.6.7",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.117.1", "three": "0.117.1",
"throttle-debounce": "3.0.1", "throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "8.0.17", "ts-loader": "8.0.18",
"ts-node": "9.1.1", "ts-node": "9.1.1",
"tslint": "6.1.3", "tslint": "6.1.3",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"typeorm": "0.2.31", "typeorm": "0.2.31",
"typescript": "4.1.5", "typescript": "4.2.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"uuid": "8.3.2", "uuid": "8.3.2",
"v-debounce": "0.1.2", "v-debounce": "0.1.2",
"vanilla-tilt": "1.7.0", "vanilla-tilt": "1.7.0",
"vue": "3.0.5", "vue": "3.0.7",
"vue-color": "2.8.1", "vue-color": "2.8.1",
"vue-json-pretty": "1.7.1", "vue-json-pretty": "1.7.1",
"vue-loader": "16.1.2", "vue-loader": "16.1.2",
"vue-prism-editor": "2.0.0-alpha.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", "vue-style-loader": "4.1.3",
"vuedraggable": "4.0.1", "vuedraggable": "4.0.1",
"web-push": "3.4.4", "web-push": "3.4.4",
"webpack": "5.24.2", "webpack": "5.26.3",
"webpack-cli": "4.5.0", "webpack-cli": "4.5.0",
"websocket": "1.0.33", "websocket": "1.0.33",
"ws": "7.4.3", "ws": "7.4.4",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "4.2.15", "@types/chai": "4.2.15",
"@types/fluent-ffmpeg": "2.1.16", "@types/fluent-ffmpeg": "2.1.16",
"chai": "4.3.0", "chai": "4.3.4",
"cross-env": "7.0.3" "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": { "globals": {
"_DEV_": false, "_DEV_": false,
"_LANGS_": false, "_LANGS_": false,

View file

@ -350,7 +350,8 @@ export default defineComponent({
capture(withHandler = false) { capture(withHandler = false) {
if (this.$i) { 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); if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
} }
}, },

View file

@ -325,7 +325,8 @@ export default defineComponent({
capture(withHandler = false) { capture(withHandler = false) {
if (this.$i) { 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); if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
} }
}, },

View file

@ -1,7 +1,7 @@
<template> <template>
<transition :name="$store.state.animation ? popup ? 'modal-popup' : 'modal' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered"> <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 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"> <div class="content" :class="{ popup, fixed, top: position === 'top' }" @click.self="onBgClick" ref="content">
<slot></slot> <slot></slot>
</div> </div>

View file

@ -4,40 +4,6 @@
import '@/style.scss'; 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 * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing'; import { Integrations } from '@sentry/tracing';
import { createApp, watch } from 'vue'; import { createApp, watch } from 'vue';

View file

@ -60,7 +60,7 @@ export default defineComponent({
methods: { methods: {
fetchDoc() { 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); 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> <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;"> <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="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> </div>
<MkContainer :body-togglable="true" :expanded="true" class="_vMargin"> <MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
@ -134,12 +134,18 @@ export default defineComponent({
data() { data() {
return { return {
INFO: computed(() => this.initPageId ? { INFO: computed(() => {
title: this.$ts._pages.editPage, let title = this.$ts._pages.newPage;
icon: faPencilAlt, if (this.initPageId) {
} : { title = this.$ts._pages.editPage;
title: this.$ts._pages.newPage, }
icon: faPencilAlt, else if (this.initPageName && this.initUser) {
title = this.$ts._pages.readPage;
}
return {
title: title,
icon: faPencilAlt,
};
}), }),
author: this.$i, author: this.$i,
readonly: false, readonly: false,

View file

@ -212,7 +212,6 @@ type Plugin = {
*/ */
export class ColdDeviceStorage { export class ColdDeviceStorage {
public static default = { public static default = {
themes: [] as Theme[], // TODO: そのうち消す
// TODO: テーマをアカウントに保存するようになったのにもかかわらず、以下のどのテーマを使うかという情報だけがブラウザ保存になっていて、アカウント切り替えたりログアウトしたときに不具合が発生するのでなんとかする // TODO: テーマをアカウントに保存するようになったのにもかかわらず、以下のどのテーマを使うかという情報だけがブラウザ保存になっていて、アカウント切り替えたりログアウトしたときに不具合が発生するのでなんとかする
// テーマIDを保存するのではなく、テーマ自体を保存するようにすれば解決するかも // テーマIDを保存するのではなく、テーマ自体を保存するようにすれば解決するかも
darkTheme: '8050783a-7f63-445a-b270-36d0f6ba1677', 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 }); await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
localStorage.setItem(lsCacheKey, JSON.stringify(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) { capture(withHandler = false) {
if (this.$i) { 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); if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
} }
}, },

View file

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

View file

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

View file

@ -75,5 +75,5 @@ async function net() {
// FS STAT // FS STAT
async function fs() { async function fs() {
const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); 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 { createConnection, Logger, getConnection } from 'typeorm';
import config from '../config'; import config from '../config';
import { entities as charts } from '../services/chart/entities'; 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.
## アクセストークンの取得 ## Obtenir le jeton d'accès
基本的に、APIはリクエストにはアクセストークンが必要となります。 APIにリクエストするのが自分自身なのか、不特定の利用者に使ってもらうアプリケーションなのかによって取得手順は異なります。 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 »](#アプリケーション利用者にアクセストークンの発行をリクエストする).
### 自分自身のアクセストークンを手動発行する ### Générer manuellement un jeton d'accès pour son propre compte
「設定 > API」で、自分のアクセストークンを発行できます。 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に置き換えてください。 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.
> : `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f` > Par ex. : `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f`
表示する際、URLにクエリパラメータとしていくつかのオプションを設定できます: En ouvrant cette URL, vous pourrez configurer un certain nombre d'options pour les paramètres de requête :
* `name` ... アプリケーション名 * `name` : nom de l'application
* > : `MissDeck` * > Ex. : `MissDeck`
* `icon` ... アプリケーションのアイコン画像URL * `icon` : URL de l'icône de l'application
* > : `https://missdeck.example.com/icon.png` * > Ex. : `https://missdeck.example.com/icon.png`
* `callback` ... 認証が終わった後にリダイレクトするURL * `callback` : URL de redirection après l'authentification
* > : `https://missdeck.example.com/callback` * > Ex. : `https://missdeck.example.com/callback`
* リダイレクト時には、`session`というクエリパラメータでセッションIDが付きます * Lors de la redirection, un paramètre de requête `session` contenant l'ID de session sera joint.
* `permission` ... アプリケーションが要求する権限 * `permission` : permissions requises par l'application
* > : `write:notes,write:following,read:drive` * > Ex. : `write:notes,write:following,read:drive`
* 要求する権限を`,`で区切って列挙します * Listez les permissions requises en utilisant une `,` pour les séparer.
* どのような権限があるかは[APIリファレンス](/api-doc)で確認できます * Vous pouvez vérifier quelles sont les permissions disponibles sur [les références API de Misskey](/api-doc).
#### Step 3 #### Étape 3
ユーザーが発行を許可した後、`{_URL_}/api/miauth/{session}/check`にPOSTリクエストすると、レスポンスとしてアクセストークンを含むJSONが返ります。 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.
レスポンスに含まれるプロパティ: Propriétés incluses dans la réponse :
* `token` ... ユーザーのアクセストークン * `token` : jeton d'accès de l'utilisateur
* `user` ... ユーザーの情報 * `user` : données de l'utilisateur
[「APIの使い方」へ進む](#APIの使い方) [Continuer avec « Utiliser l'API ».](#APIの使い方)
## APIの使い方 ## Utiliser l'API
**APIはすべてPOSTで、リクエスト/レスポンスともにJSON形式です。RESTではありません。** アクセストークンは、`i`というパラメータ名でリクエストに含めます。 **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) * [Références API de Misskey](/api-doc)
* [ストリーミングAPI](./stream) * [API streaming](./stream)

View file

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

View file

@ -1,2 +1,2 @@
# Abonnements # 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 ## 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>. 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. 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を使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます。 ストリーミング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 Paramètres > Thèmes
## Créer un thème ## 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 ``` js
{ {
id: '17587283-dd92-4a2c-a22c-be0637c9e22a', 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. * `props` ... Définir un style de thème.Voir les explications ci-après.
### Définir un style de thème ### 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 #### Syntaxe des valeurs
* Codes de couleur Hex * Codes de couleur Hex

View file

@ -1,2 +1,2 @@
# カスタム絵文字 # Emoji personalizzati
カスタム絵文字は、インスタンスで用意された画像を絵文字のように使える機能です。 ノート、リアクション、チャット、自己紹介、名前などの場所で使うことができます。 カスタム絵文字をそれらの場所で使うには、絵文字ピッカーボタン(ある場合)を押すか、`:`を入力して絵文字サジェストを表示します。 テキスト内に`:foo:`のような形式の文字列が見つかると、`foo`の部分がカスタム絵文字名と解釈され、表示時には対応したカスタム絵文字に置き換わります。 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> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead> </thead>
<tbody> <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">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>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</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>通知を表示/隠す</td><td><b>N</b>otifications</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">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> </tbody>
</table> </table>
@ -19,7 +19,7 @@
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead> </thead>
<tbody> <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> <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> </tbody>
</table> </table>
## Renoteフォーム ## Finestra Rinota
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td><kbd class="key">Enter</kbd></td><td>Renoteする</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>フォームを展開する</td><td><b>Q</b>uote</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>フォームを閉じる</td><td>-</td></tr> <tr><td><kbd class="key">Esc</kbd></td><td>Chiudere finestra</td><td>-</td></tr>
</tbody> </tbody>
</table> </table>
## リアクションフォーム ## Pannello reazioni
デフォルトで「👍」にフォーカスが当たっている状態です。 La reazione "👍" è impostata come reazione predefinita.
<table> <table>
<thead> <thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr> <tr><th>Scorciatoia</th><th>Effetto</th><th>Accesso universale</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd></td><td>上のリアクションにフォーカスを移動</td><td>-</td></tr> <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 ## Variabili
変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。 変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。

View file

@ -1,11 +1,11 @@
# Reazione # Reazioni
他の人のノートに、絵文字を付けて簡単にあなたの反応を伝えられる機能です。 リアクションするには、ノートの + アイコンをクリックしてピッカーを表示し、絵文字を選択します。 リアクションには[カスタム絵文字](./custom-emoji)も使用できます。 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.
## リモート投稿へのリアクションについて ## Inviare reazioni a server remoti
リアクションはMisskeyオリジナルの機能であるため、リモートインスタンスがMisskeyでない限りは、ほとんどの場合「Like」としてアクティビティが送信されます。一般的にはLikeは「お気に入り」として実装されているようです。 また、相手がMisskeyであったとしても、カスタム絵文字リアクションは伝わらず、自動的に「👍」等にフォールバックされます。 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.
## リモートからのリアクションについて ## Ricevere reazioni da server remoti
リモートから「Like」アクティビティを受信したとき、Misskeyでは「👍」のリアクションとして解釈されます。 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 # Tema
テーマを設定して、Misskeyクライアントの見た目を変更できます。 Puoi utilizzare i temi per cambiare l'aspetto del client Misskey.
## テーマの設定 ## Impostazioni tema
設定 > テーマ Impostazioni > Tema
## テーマを作成する ## Creare un tema
テーマコードはJSON5で記述されたテーマオブジェクトです。 テーマは以下のようなオブジェクトです。 Il codice dei temi è scritto a forma di oggetti JSON5. I temi contengono gli oggetti sotto citati:
``` js ``` js
{ {
id: '17587283-dd92-4a2c-a22c-be0637c9e22a', id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
@ -42,10 +42,10 @@
* テーマはここで設定されたベーステーマを継承します。 * テーマはここで設定されたベーステーマを継承します。
* `props` ... テーマのスタイル定義。これから説明します。 * `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`があると見なされます。 `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進数で表された色 * 16進数で表された色
* 例: `#00ff00` * 例: `#00ff00`
* `rgb(r, g, b)`形式で表された色 * `rgb(r, g, b)`形式で表された色
@ -61,8 +61,8 @@
* 関数(後述) * 関数(後述)
* `:{関数名}<{引数}<{色}` * `:{関数名}<{引数}<{色}`
#### Costante #### Costanti
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。 「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。
#### Funzione #### Funzioni
wip wip

View file

@ -1,15 +1,15 @@
# タイムラインの比較 # Confronto delle timeline
https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing
## Home ## Home
自分のフォローしているユーザーの投稿 Pubblicazioni degli utenti che segui.
## Locale ## 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 => { fetchMeta(true).then(meta => {
cache = 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: {}, default: {},
comment: 'The any properties of the DriveFile. For example, it includes image width/height.' 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() @Index()
@Column('boolean') @Column('boolean')

View file

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

View file

@ -56,16 +56,24 @@ export const packedAntennaSchema = {
type: 'array' as const, type: 'array' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
items: { items: {
type: 'string' as const, type: 'array' as const,
optional: false as const, nullable: false 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, type: 'array' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
items: { items: {
type: 'string' as const, type: 'array' as const,
optional: false as const, nullable: false as const optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const
}
} }
}, },
src: { src: {

View file

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

View file

@ -1,14 +1,14 @@
import { EntityRepository, Repository, In } from 'typeorm'; import { EntityRepository, Repository, In } from 'typeorm';
import { Note } from '../entities/note'; import { Note } from '../entities/note';
import { User } from '../entities/user'; 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 { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all'; import { awaitAll } from '../../prelude/await-all';
import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '../../misc/reaction-lib'; import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '../../misc/reaction-lib';
import { toString } from '../../mfm/to-string'; import { toString } from '../../mfm/to-string';
import { parse } from '../../mfm/parse'; import { parse } from '../../mfm/parse';
import { Emoji } from '../entities/emoji'; import { NoteReaction } from '../entities/note-reaction';
import { concat } from '../../prelude/array'; import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '../../misc/populate-emojis';
export type PackedNote = SchemaType<typeof packedNoteSchema>; export type PackedNote = SchemaType<typeof packedNoteSchema>;
@ -83,6 +83,9 @@ export class NoteRepository extends Repository<Note> {
options?: { options?: {
detail?: boolean; detail?: boolean;
skipHide?: boolean; skipHide?: boolean;
_hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
};
} }
): Promise<PackedNote> { ): Promise<PackedNote> {
const opts = Object.assign({ 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() { 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({ const reaction = await NoteReactions.findOne({
userId: meId!, userId: meId!,
noteId: note.id, noteId: note.id,
@ -212,11 +168,15 @@ export class NoteRepository extends Repository<Note> {
: await Channels.findOne(note.channelId) : await Channels.findOne(note.channelId)
: null; : 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({ const packed = await awaitAll({
id: note.id, id: note.id,
createdAt: note.createdAt.toISOString(), createdAt: note.createdAt.toISOString(),
userId: note.userId, userId: note.userId,
user: Users.pack(note.user || note.userId, meId), user: Users.pack(note.user || note.userId, meId, {
detail: false,
}),
text: text, text: text,
cw: note.cw, cw: note.cw,
visibility: note.visibility, visibility: note.visibility,
@ -227,7 +187,7 @@ export class NoteRepository extends Repository<Note> {
repliesCount: note.repliesCount, repliesCount: note.repliesCount,
reactions: convertLegacyReactions(note.reactions), reactions: convertLegacyReactions(note.reactions),
tags: note.tags.length > 0 ? note.tags : undefined, 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, fileIds: note.fileIds,
files: DriveFiles.packMany(note.fileIds), files: DriveFiles.packMany(note.fileIds),
replyId: note.replyId, replyId: note.replyId,
@ -244,12 +204,14 @@ export class NoteRepository extends Repository<Note> {
_prId_: (note as any)._prId_ || undefined, _prId_: (note as any)._prId_ || undefined,
...(opts.detail ? { ...(opts.detail ? {
reply: note.replyId ? this.pack(note.replyId, meId, { reply: note.replyId ? this.pack(note.reply || note.replyId, meId, {
detail: false detail: false,
_hint_: options?._hint_
}) : undefined, }) : undefined,
renote: note.renoteId ? this.pack(note.renoteId, meId, { renote: note.renoteId ? this.pack(note.renote || note.renoteId, meId, {
detail: true detail: true,
_hint_: options?._hint_
}) : undefined, }) : undefined,
poll: note.hasPoll ? populatePoll() : undefined, poll: note.hasPoll ? populatePoll() : undefined,
@ -272,15 +234,39 @@ export class NoteRepository extends Repository<Note> {
return packed; return packed;
} }
public packMany( public async packMany(
notes: (Note['id'] | Note)[], notes: Note[],
me?: User['id'] | User | null | undefined, me?: User['id'] | User | null | undefined,
options?: { options?: {
detail?: boolean; detail?: boolean;
skipHide?: 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 { EntityRepository, In, Repository } from 'typeorm';
import { Users, Notes, UserGroupInvitations, AccessTokens } from '..'; import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '..';
import { Notification } from '../entities/notification'; import { Notification } from '../entities/notification';
import { awaitAll } from '../../prelude/await-all'; import { awaitAll } from '../../prelude/await-all';
import { SchemaType } from '../../misc/schema'; 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>; export type PackedNotification = SchemaType<typeof packedNotificationSchema>;
@ -10,6 +14,11 @@ export type PackedNotification = SchemaType<typeof packedNotificationSchema>;
export class NotificationRepository extends Repository<Notification> { export class NotificationRepository extends Repository<Notification> {
public async pack( public async pack(
src: Notification['id'] | Notification, src: Notification['id'] | Notification,
options: {
_hintForEachNotes_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
};
}
): Promise<PackedNotification> { ): Promise<PackedNotification> {
const notification = typeof src === 'object' ? src : await this.findOneOrFail(src); const notification = typeof src === 'object' ? src : await this.findOneOrFail(src);
const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null; const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null;
@ -22,23 +31,41 @@ export class NotificationRepository extends Repository<Notification> {
userId: notification.notifierId, userId: notification.notifierId,
user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null, user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
...(notification.type === 'mention' ? { ...(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' ? { ...(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' ? { ...(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' ? { ...(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' ? { ...(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 reaction: notification.reaction
} : {}), } : {}),
...(notification.type === 'pollVote' ? { ...(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 choice: notification.choice
} : {}), } : {}),
...(notification.type === 'groupInvited' ? { ...(notification.type === 'groupInvited' ? {
@ -52,10 +79,33 @@ export class NotificationRepository extends Repository<Notification> {
}); });
} }
public packMany( public async packMany(
notifications: any[], 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 $ from 'cafy';
import { EntityRepository, Repository, In, Not } from 'typeorm'; import { EntityRepository, Repository, In, Not } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user'; 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 config from '../../config';
import { SchemaType } from '../../misc/schema'; import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all'; import { awaitAll } from '../../prelude/await-all';
import { populateEmojis } from '../../misc/populate-emojis';
export type PackedUser = SchemaType<typeof packedUserSchema>; 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 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 relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.find({ const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin')
where: { userId: user.id }, .where('pin.userId = :userId', { userId: user.id })
order: { id: 'DESC' } .innerJoinAndSelect('pin.note', 'note')
}) : []; .orderBy('pin.id', 'DESC')
.getMany() : [];
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null;
const falsy = opts.detail ? false : undefined; const falsy = opts.detail ? false : undefined;
@ -188,15 +190,7 @@ export class UserRepository extends Repository<User> {
faviconUrl: instance.faviconUrl, faviconUrl: instance.faviconUrl,
themeColor: instance.themeColor, themeColor: instance.themeColor,
} : undefined) : undefined, } : undefined) : undefined,
emojis: populateEmojis(user.emojis, user.host),
// カスタム絵文字添付
emojis: user.emojis.length > 0 ? Emojis.find({
where: {
name: In(user.emojis),
host: user.host
},
select: ['name', 'host', 'url', 'aliases']
}) : [],
...(opts.detail ? { ...(opts.detail ? {
url: profile!.url, url: profile!.url,
@ -218,7 +212,7 @@ export class UserRepository extends Repository<User> {
followingCount: user.followingCount, followingCount: user.followingCount,
notesCount: user.notesCount, notesCount: user.notesCount,
pinnedNoteIds: pins.map(pin => pin.noteId), 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 detail: true
}), }),
pinnedPageId: profile!.pinnedPageId, pinnedPageId: profile!.pinnedPageId,

View file

@ -1,4 +1,3 @@
import * as Queue from 'bull';
import * as httpSignature from 'http-signature'; import * as httpSignature from 'http-signature';
import config from '../config'; import config from '../config';
@ -13,22 +12,7 @@ import { queueLogger } from './logger';
import { DriveFile } from '../models/entities/drive-file'; import { DriveFile } from '../models/entities/drive-file';
import { getJobInfo } from './get-job-info'; import { getJobInfo } from './get-job-info';
import { IActivity } from '../remote/activitypub/type'; import { IActivity } from '../remote/activitypub/type';
import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues';
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
});
}
export type InboxJobData = { export type InboxJobData = {
activity: IActivity, 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 deliverLogger = queueLogger.createSubLogger('deliver');
const inboxLogger = queueLogger.createSubLogger('inbox'); const inboxLogger = queueLogger.createSubLogger('inbox');
const dbLogger = queueLogger.createSubLogger('db'); 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 { fetchInstanceMetadata } from '../../services/fetch-instance-metadata';
import { fetchMeta } from '../../misc/fetch-meta'; import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host'; import { toPuny } from '../../misc/convert-host';
import { Cache } from '../../misc/cache';
import { Instance } from '../../models/entities/instance';
const logger = new Logger('deliver'); const logger = new Logger('deliver');
let latest: string | null = null; let latest: string | null = null;
const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
export default async (job: Bull.Job) => { export default async (job: Bull.Job) => {
const { host } = new URL(job.data.to); const { host } = new URL(job.data.to);
@ -22,12 +26,15 @@ export default async (job: Bull.Job) => {
} }
// isSuspendedなら中断 // isSuspendedなら中断
const suspendedHosts = await Instances.find({ let suspendedHosts = suspendedHostsCache.get(null);
where: { if (suspendedHosts == null) {
isSuspended: true suspendedHosts = await Instances.find({
}, where: {
cache: 60 * 1000 isSuspended: true
}); },
});
suspendedHostsCache.set(null, suspendedHosts);
}
if (suspendedHosts.map(x => x.host).includes(toPuny(host))) { if (suspendedHosts.map(x => x.host).includes(toPuny(host))) {
return 'skip (suspended)'; 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}`; return `Old keyId is no longer supported. ${keyIdLower}`;
} }
// TDOO: キャッシュ
const dbResolver = new DbResolver(); const dbResolver = new DbResolver();
// HTTP-Signature keyIdを元にDBから取得 // 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() { public async execute() {
if (!Users.isLocalUser(this.actor)) return; if (!Users.isLocalUser(this.actor)) return;
const inboxes: string[] = []; const inboxes = new Set<string>();
// build inbox list // build inbox list
for (const recipe of this.recipes) { for (const recipe of this.recipes) {
@ -89,13 +89,13 @@ export default class DeliverManager {
for (const following of followers) { for (const following of followers) {
if (Followings.isRemoteFollower(following)) { if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox; const inbox = following.followerSharedInbox || following.followerInbox;
if (!inboxes.includes(inbox)) inboxes.push(inbox); inboxes.add(inbox);
} }
} }
} else if (isDirect(recipe)) { } else if (isDirect(recipe)) {
// direct deliver // direct deliver
const inbox = recipe.to.inbox; 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 { IActivity } from '../type';
import { LdSignature } from '../misc/ld-signature'; import { LdSignature } from '../misc/ld-signature';
import { ILocalUser } from '../../../models/entities/user'; import { ILocalUser } from '../../../models/entities/user';
import { UserKeypairs } from '../../../models'; import { getUserKeypair } from '../../../misc/keypair-store';
export const renderActivity = (x: any): IActivity | null => { export const renderActivity = (x: any): IActivity | null => {
if (x == null) return 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> => { export const attachLdSignature = async (activity: any, user: ILocalUser): Promise<IActivity | null> => {
if (activity == null) return null; if (activity == null) return null;
const keypair = await UserKeypairs.findOneOrFail({ const keypair = await getUserKeypair(user.id);
userId: user.id
});
const obj = { const obj = {
// as non-standards // as non-standards

View file

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

View file

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

View file

@ -13,10 +13,11 @@ import Following from './activitypub/following';
import Featured from './activitypub/featured'; import Featured from './activitypub/featured';
import { inbox as processInbox } from '../queue'; import { inbox as processInbox } from '../queue';
import { isSelfHost } from '../misc/convert-host'; 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 { ILocalUser, User } from '../models/entities/user';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { renderLike } from '../remote/activitypub/renderer/like'; import { renderLike } from '../remote/activitypub/renderer/like';
import { getUserKeypair } from '../misc/keypair-store';
// Init router // Init router
const router = new Router(); const router = new Router();
@ -135,7 +136,7 @@ router.get('/users/:user/publickey', async ctx => {
return; return;
} }
const keypair = await UserKeypairs.findOneOrFail(user.id); const keypair = await getUserKeypair(user.id);
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {
ctx.body = renderActivity(renderKey(user, keypair)); 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 { User } from '../../models/entities/user';
import { Users, AccessTokens, Apps } from '../../models'; import { Users, AccessTokens, Apps } from '../../models';
import { AccessToken } from '../../models/entities/access-token'; 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]> => { export default async (token: string): Promise<[User | null | undefined, AccessToken | null | undefined]> => {
if (token == null) { if (token == null) {
@ -9,6 +14,11 @@ export default async (token: string): Promise<[User | null | undefined, AccessTo
} }
if (isNativeToken(token)) { if (isNativeToken(token)) {
const cached = cache.get(token);
if (cached) {
return [cached, null];
}
// Fetch user // Fetch user
const user = await Users const user = await Users
.findOne({ token }); .findOne({ token });
@ -17,8 +27,11 @@ export default async (token: string): Promise<[User | null | undefined, AccessTo
throw new Error('user not found'); throw new Error('user not found');
} }
cache.set(token, user);
return [user, null]; return [user, null];
} else { } else {
// TODO: cache
const accessToken = await AccessTokens.findOne({ const accessToken = await AccessTokens.findOne({
where: [{ where: [{
hash: token.toLowerCase() // app 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.score > 0`)
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
.andWhere(`note.visibility = 'public'`) .andWhere(`note.visibility = 'public'`)
.leftJoinAndSelect('note.user', 'user'); .innerJoinAndSelect('note.user', 'user');
if (user) { if (user) {
query.andWhere('note.userId != :userId', { userId: user.id }); 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) => (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']>>>; 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>) 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> { : (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) => { 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) chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns)
}); });
await RegistrationTickets.save({ await RegistrationTickets.insert({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
code, code,

View file

@ -53,7 +53,7 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.alreadyPromoted); throw new ApiError(meta.errors.alreadyPromoted);
} }
await PromoNotes.save({ await PromoNotes.insert({
noteId: note.id, noteId: note.id,
createdAt: new Date(), createdAt: new Date(),
expiresAt: new Date(ps.expiresAt), 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) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.id IN (${ antennaQuery.getQuery() })`) .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()); .setParameters(antennaQuery.getParameters());
generateVisibilityQuery(query, user); generateVisibilityQuery(query, user);

View file

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

View file

@ -4,6 +4,7 @@ import define from '../../define';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import { Channels, ChannelFollowings } from '../../../../models'; import { Channels, ChannelFollowings } from '../../../../models';
import { genId } from '../../../../misc/gen-id'; import { genId } from '../../../../misc/gen-id';
import { publishUserEvent } from '../../../../services/stream';
export const meta = { export const meta = {
tags: ['channels'], tags: ['channels'],
@ -36,10 +37,12 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.noSuchChannel); throw new ApiError(meta.errors.noSuchChannel);
} }
await ChannelFollowings.save({ await ChannelFollowings.insert({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
followerId: user.id, followerId: user.id,
followeeId: channel.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 //#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.channelId = :channelId', { channelId: channel.id }) .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'); .leftJoinAndSelect('note.channel', 'channel');
//#endregion //#endregion

View file

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define'; import define from '../../define';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import { Channels, ChannelFollowings } from '../../../../models'; import { Channels, ChannelFollowings } from '../../../../models';
import { publishUserEvent } from '../../../../services/stream';
export const meta = { export const meta = {
tags: ['channels'], tags: ['channels'],
@ -39,4 +40,6 @@ export default define(meta, async (ps, user) => {
followerId: user.id, followerId: user.id,
followeeId: channel.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); throw new ApiError(meta.errors.alreadyClipped);
} }
await ClipNotes.save({ await ClipNotes.insert({
id: genId(), id: genId(),
noteId: note.id, noteId: note.id,
clipId: clip.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) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.id IN (${ clipQuery.getQuery() })`) .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()); .setParameters(clipQuery.getParameters());
if (user) { if (user) {

View file

@ -1,6 +1,5 @@
import define from '../define'; import define from '../define';
import { RegistryItems, UserProfiles, Users } from '../../../models'; import { Users } from '../../../models';
import { genId } from '../../../misc/gen-id';
export const meta = { export const meta = {
desc: { desc: {
@ -23,28 +22,8 @@ export const meta = {
export default define(meta, async (ps, user, token) => { export default define(meta, async (ps, user, token) => {
const isSecure = token == null; const isSecure = token == null;
// TODO: そのうち消す // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す
const profile = await UserProfiles.findOneOrFail(user.id); return await Users.pack(user.id, user, {
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, {
detail: true, detail: true,
includeSecrets: isSecure 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) const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
.andWhere(`notification.notifieeId = :meId`, { meId: user.id }) .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.andWhere(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`);
query.setParameters(mutingQuery.getParameters()); query.setParameters(mutingQuery.getParameters());
@ -110,5 +116,5 @@ export default define(meta, async (ps, user) => {
readNotification(user.id, notifications.map(x => x.id)); 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 // Create read
await AnnouncementReads.save({ await AnnouncementReads.insert({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
announcementId: ps.announcementId, announcementId: ps.announcementId,

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ import define from '../../define';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import { getUser } from '../../common/getters'; import { getUser } from '../../common/getters';
import { Mutings } from '../../../../models'; import { Mutings } from '../../../../models';
import { publishUserEvent } from '../../../../services/stream';
export const meta = { export const meta = {
desc: { desc: {
@ -76,4 +77,6 @@ export default define(meta, async (ps, user) => {
await Mutings.delete({ await Mutings.delete({
id: exist.id 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) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.visibility = 'public'`) .andWhere(`note.visibility = 'public'`)
.andWhere(`note.localOnly = FALSE`) .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) { if (ps.local) {
query.andWhere('note.userHost IS NULL'); 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); generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user); if (user) generateMutedUserQuery(query, user);

View file

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

View file

@ -49,7 +49,11 @@ export default define(meta, async (ps, user) => {
.andWhere(`note.score > 0`) .andWhere(`note.score > 0`)
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
.andWhere(`note.visibility = 'public'`) .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); if (user) generateMutedUserQuery(query, user);

View file

@ -8,8 +8,6 @@ import { Notes } from '../../../../models';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
import { activeUsersChart } from '../../../../services/chart'; import { activeUsersChart } from '../../../../services/chart';
import { generateRepliesQuery } from '../../common/generate-replies-query'; 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 { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
export const meta = { export const meta = {
@ -81,7 +79,11 @@ export default define(meta, async (ps, user) => {
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.visibility = \'public\'') .andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL') .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); generateRepliesQuery(query, user);
if (user) generateMutedUserQuery(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(); const timeline = await query.take(ps.limit!).getMany();
await injectPromo(timeline, user);
await injectFeatured(timeline, user);
process.nextTick(() => { process.nextTick(() => {
if (user) { if (user) {
activeUsersChart.update(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 { generateMutedUserQuery } from '../../common/generate-muted-user-query';
import { activeUsersChart } from '../../../../services/chart'; import { activeUsersChart } from '../../../../services/chart';
import { generateRepliesQuery } from '../../common/generate-replies-query'; 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 { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-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 }) qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id })
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); .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()); .setParameters(followingQuery.getParameters());
generateChannelQuery(query, user); generateChannelQuery(query, user);
@ -175,9 +177,6 @@ export default define(meta, async (ps, user) => {
const timeline = await query.take(ps.limit!).getMany(); const timeline = await query.take(ps.limit!).getMany();
await injectPromo(timeline, user);
await injectFeatured(timeline, user);
process.nextTick(() => { process.nextTick(() => {
if (user) { if (user) {
activeUsersChart.update(user); activeUsersChart.update(user);

View file

@ -10,8 +10,6 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query'
import { activeUsersChart } from '../../../../services/chart'; import { activeUsersChart } from '../../../../services/chart';
import { Brackets } from 'typeorm'; import { Brackets } from 'typeorm';
import { generateRepliesQuery } from '../../common/generate-replies-query'; 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 { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-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'), const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') .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); generateChannelQuery(query, user);
generateRepliesQuery(query, user); generateRepliesQuery(query, user);
@ -128,9 +130,6 @@ export default define(meta, async (ps, user) => {
const timeline = await query.take(ps.limit!).getMany(); const timeline = await query.take(ps.limit!).getMany();
await injectPromo(timeline, user);
await injectFeatured(timeline, user);
process.nextTick(() => { process.nextTick(() => {
if (user) { if (user) {
activeUsersChart.update(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 }) .where(`:meId = ANY(note.mentions)`, { meId: user.id })
.orWhere(`:meId = ANY(note.visibleUserIds)`, { 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); generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user); generateMutedUserQuery(query, user);
@ -79,9 +83,7 @@ export default define(meta, async (ps, user) => {
const mentions = await query.take(ps.limit!).getMany(); const mentions = await query.take(ps.limit!).getMany();
for (const note of mentions) { read(user.id, mentions.map(note => note.id));
read(user.id, note.id);
}
return await Notes.packMany(mentions, user); 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) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(`note.renoteId = :renoteId`, { renoteId: note.id }) .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); generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user); if (user) generateMutedUserQuery(query, user);

View file

@ -59,7 +59,11 @@ export const meta = {
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.replyId = :replyId', { replyId: ps.noteId }) .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); generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user); if (user) generateMutedUserQuery(query, user);

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