1
0
Fork 1
mirror of https://example.com synced 2024-11-22 02:36:38 +09:00

Firefish v1.0.4-beta

This commit is contained in:
naskya 2023-08-02 23:59:31 +09:00
parent f58683e142
commit 52e3595da2
Signed by: naskya
GPG key ID: 164DFF24E2D40139
49 changed files with 2329 additions and 853 deletions

View file

@ -34,7 +34,7 @@ port: 3000
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: database
host: postgres
port: 5432
# Database name

View file

@ -29,7 +29,7 @@ url: https://example.com/
# The port that your Firefish server should listen on.
port: 3000
# The bind host your Calckey server should listen on.
# The bind host your Firefish server should listen on.
# If unspecified, the wildcard address will be used.
#bind: 127.0.0.1

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
# Visual Studio Code
/.vscode
!/.vscode/extensions.json
!/.vscode/launch.json
# Intelij-IDEA
/.idea

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,6 @@ You can contribute without knowing how to code by helping translate here:
[![Translation bars](https://hosted.weblate.org/widgets/firefish/-/multi-auto.svg)](https://hosted.weblate.org/engage/firefish/)
## Roadmap
See [FIREFISH.md](./FIREFISH.md)
## Issues
Before creating an issue, please check the following:
- To avoid duplication, please search for similar issues before creating a new issue.

View file

@ -2177,5 +2177,5 @@ deletePasskeys: Suprimeix les contrasenyes
deletePasskeysConfirm: Això suprimirà de manera irreversible totes les contrasenyes
i claus de seguretat d'aquest compte. Procedir?
inputNotMatch: L'entrada no coincideix
delete2fa: Suprimeix 2FA
delete2fa: Desactivar 2FA
delete2faConfirm: Això suprimirà irreversiblement 2FA en aquest compte. Procedir?

View file

@ -853,7 +853,7 @@ gallery: "Bilder-Galerie"
recentPosts: "Neue Beiträge"
popularPosts: "Beliebte Beiträge"
shareWithNote: "Mit Beitrag teilen"
ads: "Werbeanzeigen"
ads: "Community-Banner"
expiration: "Frist"
memo: "Merkzettel"
priority: "Priorität"
@ -953,9 +953,9 @@ driveCapOverrideCaption: "Gib einen Wert von 0 oder weniger ein, um die Kapazit
auf den Standard zurückzusetzen."
requireAdminForView: "Du musst dich mit einem Administratorkonto anmelden um dies
zu sehen."
isSystemAccount: "Dieses Konto wird vom System erstellt und automatisch verwaltet.
Bitte moderieren, bearbeiten, löschen oder manipulieren Sie dieses Konto nicht,
da es sonst zu einem Server-Absturz kommen könnte."
isSystemAccount: "Ein Nutzerkonto, dass durch das System erstellt und automatisch
kontrolliert wird. Jede Anpassung, Veränderung oder Löschung dieses Nutzerkontos,
kann zu schwerwiegenden Fehlern auf diesem Server führen."
typeToConfirm: "Bitte gib zur Bestätigung {x} ein"
deleteAccount: "Nutzerkonto löschen"
document: "Dokumentation"
@ -966,7 +966,7 @@ logoutConfirm: "Wirklich abmelden?"
lastActiveDate: "Zuletzt verwendet am"
statusbar: "Statusleiste"
pleaseSelect: "Wähle eine Option"
reverse: "Umkehren"
reverse: "Rückgängig machen"
colored: "Farbig"
refreshInterval: "Aktualisierungsintervall "
label: "Beschriftung"
@ -1128,7 +1128,7 @@ _mfm:
bold: "Fett"
boldDescription: "Zeichen zur Betonung dicker erscheinen lassen."
small: "Klein"
smallDescription: "Inhalt klein und dünn erscheinen lassen."
smallDescription: "Inhalt klein und dünn anzeigen."
center: "Zentrieren"
centerDescription: "Inhalt zentriert anzeigen."
inlineCode: "Code (Eingebettet)"
@ -2053,7 +2053,7 @@ userSaysSomethingReasonReply: '{name} hat auf einen Beitrag geantwortet der {rea
userSaysSomethingReasonRenote: '{name} hat einen Beitrag geteilt der {reason} beinhaltet'
userSaysSomethingReasonQuote: '{name} hat einen Beitrag zitiert der {reason} beinhaltet'
seperateRenoteQuote: Getrennte Boost- und Zitat-Schaltflächen
showAds: Anzeigen anzeigen
showAds: Community-Banner anzeigen
splash: Begrüßungsbildschirm
customSplashIconsDescription: URLs für benutzerdefinierte Splash-Screen-Symbole, die
durch Zeilenumbrüche getrennt sind und nach dem Zufallsprinzip jedes Mal angezeigt
@ -2191,3 +2191,13 @@ _skinTones:
showPopup: Benutzer mit Popup benachrichtigen
showWithSparkles: Mit Glitzer anzeigen
removeQuote: Zitat entfernen
objectStorageS3ForcePathStyle: Verwende pfadbasierte Endpunkt-URLs
objectStorageS3ForcePathStyleDesc: Wenn aktiviert, werden Endpunkt-URLs im Format
's3.amazonaws.com/<bucket>/' statt '<bucket>.s3.amazonaws.com' erstellt.
origin: Herkunft
delete2fa: 2FA deaktivieren
deletePasskeys: Passkeys löschen
delete2faConfirm: Passkeys werden unwiderruflich von diesem Account gelöscht. Fortfahren?
deletePasskeysConfirm: Alle Passkeys und Security-Keys werden unwiderruflich von diesem
Account gelöscht. Fortfahren?
inputNotMatch: Eingabe stimmt nicht überein

View file

@ -1135,7 +1135,6 @@ hideFollowButtons: "Hide follow buttons in notifications and user pages"
forMobile: "Mobile"
replaceChatButtonWithAccountButton: "Replace chat button at the bottom with account switch button"
replaceWidgetsButtonWithReloadButton: "Replace widgets button at the bottom with reload button"
addRe: "Add \"re:\" at the beginning of comment in reply to CW'd post"
origin: "Origin"
delete2fa: "Disable 2FA"
deletePasskeys: "Delete passkeys"
@ -1144,6 +1143,7 @@ deletePasskeysConfirm: "This will irreversibly delete all passkeys and security
inputNotMatch: "Input does not match"
detectPostLanguage: "Automatically detect the language and show a translate button for non-English posts"
languageForTranslation: "Language used for post translation"
addRe: "Add \"re:\" at the beginning of comment in reply to a post with a content warning"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
@ -1239,7 +1239,7 @@ _aboutFirefish:
development since 2022."
contributors: "Main contributors"
allContributors: "All contributors"
originalMisskeyContributors: "Original Misskey main contributors"
misskeyContributors: "Misskey main contributors"
source: "Source code"
translation: "Translate Firefish"
donate: "Donate to Firefish"

View file

@ -161,7 +161,7 @@ autoAcceptFollowed: "Aceptar automáticamente las solicitudes de seguimiento de
usuarios que sigues"
addAccount: "Agregar Cuenta"
loginFailed: "Error al iniciar sesión"
showOnRemote: "Ver en servidor remoto"
showOnRemote: "Abrir página original"
general: "General"
wallpaper: "Fondo de pantalla"
setWallpaper: "Establecer fondo de pantalla"
@ -540,7 +540,7 @@ objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir "
serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos"
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo"
newNoteRecived: "Tienes unas publicaciones nuevas"
newNoteRecived: "Hay publicaciones nuevas"
sounds: "Sonidos"
listen: "Escuchar"
none: "Ninguna"
@ -690,7 +690,7 @@ instanceTicker: "Información de publicaciones de el servidor"
waitingFor: "Esperando a {x}"
random: "Aleatorio"
system: "Sistema"
switchUi: "Cambiar interfaz de usuario"
switchUi: "Interfaz"
desktop: "Escritorio"
clip: "Clip"
createNew: "Crear"
@ -700,15 +700,15 @@ unclip: "Quitar clip"
confirmToUnclipAlreadyClippedNote: "Esta publicación ya está incluida en el clip \"\
{name}\". ¿Quiere quitar la nota del clip?"
public: "Público"
i18nInfo: "Firefish está siendo traducido a varios idiomas gracias a voluntarios. Se
puede colaborar traduciendo en {link}"
i18nInfo: "Firefish está siendo traducido a varios idiomas gracias a voluntarios.
Se puede colaborar traduciendo en {link}"
manageAccessTokens: "Administrar tokens de acceso"
accountInfo: "Información de la Cuenta"
notesCount: "Cantidad de publicaciones"
repliesCount: "Cantidad de respuestas hechas"
renotesCount: "Cantidad de renotas hechas"
renotesCount: "Número de impulsos enviados"
repliedCount: "Cantidad de respuestas recibidas"
renotedCount: "Cantidad de renotas recibidas"
renotedCount: "Cantidad de impulsos recibidos"
followingCount: "Cantidad de seguidos"
followersCount: "Cantidad de seguidores"
sentReactionsCount: "Cantidad de reacciones hechas"
@ -722,9 +722,9 @@ driveUsage: "Uso del drive"
noCrawle: "Rechazar indexación del crawler"
noCrawleDescription: "Pedir a los motores de búsqueda que no indexen tu perfil, publicaciones,
páginas, etc."
lockedAccountInfo: "A menos que configures la visibilidad de tus notas como \"Sólo
seguidores\", tus notas serán visibles para cualquiera, incluso si requieres que
los seguidores sean aprobados manualmente."
lockedAccountInfo: "A menos que configures la visibilidad de tus publicaciones como
\"Sólo seguidores\", tus publicaciones serán visibles para cualquiera, incluso si
requieres que los seguidores sean aprobados manualmente."
alwaysMarkSensitive: "Marcar los medios de comunicación como contenido sensible por
defecto"
loadRawImages: "Cargar las imágenes originales en lugar de mostrar las miniaturas"
@ -758,7 +758,7 @@ showTitlebar: "Mostrar la barra de título"
clearCache: "Limpiar caché"
onlineUsersCount: "{n} usuarios en línea"
nUsers: "{n} Usuarios"
nNotes: "{n} Notas"
nNotes: "{n} Publicaciones"
sendErrorReports: "Envíar informe de errores"
sendErrorReportsDescription: "Si habilita esta opción, los detalles de los errores
serán compartidos con Firefish cuando ocurra un problema, lo que ayudará a mejorar
@ -788,7 +788,7 @@ capacity: "Capacidad"
inUse: "Usado"
editCode: "Editar código"
apply: "Aplicar"
receiveAnnouncementFromInstance: "Recibir notificaciones de la instancia"
receiveAnnouncementFromInstance: "Recibir notificaciones de este servidor"
emailNotification: "Notificaciones por correo electrónico"
publish: "Publicar"
inChannelSearch: "Buscar en el canal"
@ -804,9 +804,10 @@ unlikeConfirm: "¿Quitar como favorito?"
fullView: "Vista completa"
quitFullView: "quitar vista completa"
addDescription: "Agregar descripción"
userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando Pin en el menú
de notas individuales"
notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino"
userPagePinTip: "Puede mantener tus publicaciones visibles aquí seleccionando Pin
en el menú de notas individuales."
notSpecifiedMentionWarning: "Esta publicacion contiene menciones a usuarios no incluídos
como destinatarios"
info: "Información"
userInfo: "Información del usuario"
unknown: "Desconocido"
@ -819,7 +820,7 @@ active: "Activo"
offline: "Sin conexión"
notRecommended: "obsoleto"
botProtection: "Protección contra bots"
instanceBlocking: "Instancias bloqueadas"
instanceBlocking: "Gestión de la Federación"
selectAccount: "Elija una cuenta"
switchAccount: "Cambiar de cuenta"
enabled: "Activado"
@ -836,8 +837,8 @@ postToGallery: "Crear una nueva publicación en la galería"
gallery: "Galería"
recentPosts: "Posts recientes"
popularPosts: "Más vistos"
shareWithNote: "Compartir con una nota"
ads: "Anuncios"
shareWithNote: "Compartir con una publicación"
ads: "Banners"
expiration: "Termina el"
memo: "Notas"
priority: "Prioridad"
@ -885,14 +886,14 @@ manageAccounts: "Administrar cuenta"
makeReactionsPublic: "Hacer el historial de reacciones público"
makeReactionsPublicDescription: "Todas las reacciones que hayas hecho serán públicamente
visibles."
classic: "Clásico"
classic: "Centrado"
muteThread: "Ocultar hilo"
unmuteThread: "Mostrar hilo"
ffVisibility: "Visibilidad de seguidores y seguidos"
ffVisibilityDescription: "Puedes configurar quien puede ver a quienes sigues y quienes
te siguen"
continueThread: "Ver la continuación del hilo"
deleteAccountConfirm: "La cuenta será borrada. ¿Está seguro?"
deleteAccountConfirm: "La cuenta será borrada irreversiblemente. ¿Está seguro?"
incorrectPassword: "La contraseña es incorrecta"
voteConfirm: "¿Confirma su voto a {choice}?"
hide: "Ocultar"
@ -934,7 +935,9 @@ driveCapOverrideLabel: "Cambiar la capacidad de la unidad para este usuario"
driveCapOverrideCaption: "Restablecer la capacidad a su predeterminado ingresando
un valor de 0 o menos"
requireAdminForView: "Necesitas iniciar sesión como administrador para ver esto."
isSystemAccount: "Cuenta creada y operada automáticamente por el sistema"
isSystemAccount: "Esta cuenta es creada y operada automaticamente por el sistema.
Porfavor no moderar, editar, borrar o manipular de ninguna forma esta cuenta, o
podría romper tu servidor."
typeToConfirm: "Ingrese {x} para confirmar"
deleteAccount: "Borrar cuenta"
document: "Documento"
@ -1019,8 +1022,9 @@ _forgotPassword:
enterEmail: "Ingrese el correo usado para registrar la cuenta. Se enviará un link
para resetear la contraseña."
ifNoEmail: "Si no utilizó un correo para crear la cuenta, contáctese con el administrador."
contactAdmin: "Esta instancia no admite el uso de direcciones de correo electrónico,
póngase en contacto con el administrador de la instancia para restablecer su contraseña"
contactAdmin: "Este servidor no admite el uso de direcciones de correo electrónico,
póngase en contacto con la persona que administra el servidor para restablecer
su contraseña."
_gallery:
my: "Mi galería"
liked: "Publicaciones que me gustan"
@ -1073,6 +1077,14 @@ _aboutFirefish:
morePatrons: "También apreciamos el apoyo de muchos más que no están enlistados
aquí. ¡Gracias! 🥰"
patrons: "Mecenas de Firefish"
pleaseDonateToFirefish: Por favor considera donar a Firefish para apollar su desarrollo.
donateHost: Dona a {host}
patronsList: Listados cronológicamente no por monto de la donación. ¡Dona con el
vínculo de arriba para que tu nombre aparezca aquí!
donateTitle: ¿Te gusta Firefish?
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
ayudar con los costos de operación.
sponsors: Patrocinadores de Firefish
_nsfw:
respect: "Ocultar medios NSFW"
ignore: "No esconder medios NSFW "
@ -1080,8 +1092,8 @@ _nsfw:
_mfm:
cheatSheet: "Hoja de referencia de MFM"
intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares
dentro de Misskey, Firefish, Akkoma, y mucho más. Aquí puede ver una lista de sintaxis
disponibles en MFM."
dentro de Misskey, Firefish, Akkoma, y mucho más. Aquí puede ver una lista de
sintaxis disponibles en MFM."
dummy: "Firefish expande el mundo de la Fediverso"
mention: "Menciones"
mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar
@ -1106,7 +1118,7 @@ _mfm:
inlineMath: "Fórmula (insertado)"
inlineMathDescription: "Muestra fórmulas (KaTeX) insertadas"
blockMath: "Fórmula (bloque)"
blockMathDescription: "Muestra fórmulas (KaTeX) de varias líneas en un bloque"
blockMathDescription: "Muestra fórmulas matemáticas (KaTeX) en un bloque"
quote: "Citar"
quoteDescription: "Muestra el contenido como una cita"
emoji: "Emojis personalizados"
@ -1151,6 +1163,22 @@ _mfm:
plainDescription: "Desactiva los efectos de todo el contenido MFM con este efecto
MFM."
position: Posición
warn: MFM podría contener movimientos rápidos o animaciones destellantes
advancedDescription: Si está desactivado, solo permitir markup básico, excepto cuando
un MFM animado se reproduce
scale: Escalar
foreground: Color en primer plano
scaleDescription: Ajustar el contenido según un valor especificado.
stop: Detener MFM
crop: Recortar
cropDescription: Recortar contenido.
backgroundDescription: Cambiar el color de fondo del texto.
alwaysPlay: Siempre reproducir todos los MFM animados
fade: Fundido
advanced: MFM avanzado
play: Reproducir MFM
foregroundDescription: Cambiar el color en primer plano del texto.
background: Color de fondo
_instanceTicker:
none: "No mostrar"
remote: "Mostrar a usuarios remotos"
@ -1169,7 +1197,7 @@ _channel:
owned: "Dueño"
following: "Siguiendo"
usersCount: "{n} participantes"
notesCount: "{n} notas"
notesCount: "{n} publicaciones"
nameOnly: Nombre solamente
nameAndDescription: Nombre y descripción
_menuDisplay:
@ -1183,18 +1211,20 @@ _wordMute:
con lineas nuevas indica una declaracion Or。"
muteWordsDescription2: "Encerrar las palabras clave entre numerales para usar expresiones
regulares"
softDescription: "Ocultar en la linea de tiempo las notas que cumplen las condiciones"
hardDescription: "Evitar que se agreguen a la linea de tiempo las notas que cumplen
las condiciones. Las notas no agregadas seguirán quitadas aunque cambien las condiciones."
softDescription: "Ocultar en la linea de tiempo las publicaciones que cumplen las
condiciones"
hardDescription: "Evitar que se agreguen a la linea de tiempo las publicaciones
que cumplen las condiciones, estas no serán agregadas a la linea de tiempo incluso
si cambian las condiciones."
soft: "Suave"
hard: "Duro"
mutedNotes: "Notas silenciadas"
mutedNotes: "Publicaciones silenciadas"
_instanceMute:
instanceMuteDescription: "Silencia todas las notas y reposts de la instancias seleccionadas,
incluyendo respuestas a los usuarios de las mismas"
instanceMuteDescription: "Silencia todas las publicaciones e impusos de los servidores
seleccionados, incluyendo respuestas a los usuarios de las mismas."
instanceMuteDescription2: "Separar por líneas"
title: "Oculta las notas de las instancias listadas."
heading: "Instancias a silenciar"
title: "Oculta las publicaciones de los servidores listados."
heading: "Servidores a silenciar"
_theme:
explore: "Explorar temas"
install: "Instalar tema"
@ -1243,7 +1273,7 @@ _theme:
hashtag: "Hashtag"
mention: "Menciones"
mentionMe: "Menciones (yo)"
renote: "Renotar"
renote: "Impulsar"
modalBg: "Fondo modal"
divider: "Divisor"
scrollbarHandle: "Cuadro de la barra de desplazamiento"
@ -1270,23 +1300,23 @@ _theme:
accentLighten: "Acento (claro)"
fgHighlighted: "Texto resaltado"
_sfx:
note: "Notas"
note: "Nueva publicación"
noteMy: "Nota (a mí mismo)"
notification: "Notificaciones"
chat: "Chat"
chatBg: "Chat (Fondo)"
antenna: "Antena receptora"
antenna: "Antenas"
channel: "Notificaciones del canal"
_ago:
future: "Futuro"
justNow: "Recién ahora"
secondsAgo: "Hace {n} segundos"
minutesAgo: "Hace {n} minutos"
hoursAgo: "Hace {n} horas"
daysAgo: "Hace {n} días"
weeksAgo: "Hace {n} semanas"
monthsAgo: "Hace {n} meses"
yearsAgo: "Hace {n} años"
secondsAgo: "Hace {n} segundo(s)"
minutesAgo: "Hace {n} minuto(s)"
hoursAgo: "Hace {n} hora(s)"
daysAgo: "Hace {n} día(s)"
weeksAgo: "Hace {n} semana(s)"
monthsAgo: "Hace {n} mes(es)"
yearsAgo: "Hace {n} año(s)"
_time:
second: "Segundos"
minute: "Minutos"
@ -1298,16 +1328,16 @@ _tutorial:
step1_2: "Vamos a configurarte. ¡Estarás listo y funcionando en poco tiempo!"
step2_1: "En primer lugar, rellena tu perfil"
step2_2: "Proporcionar algo de información sobre quién eres hará que sea más fácil
para los demás saber si quieren ver tus notas o seguirte."
para los demás saber si quieren ver tus publicaciones o seguirte."
step3_1: "¡Ahora es el momento de seguir a algunas personas!"
step3_2: "Tu página de inicio y tus líneas de tiempo sociales se basan en quién
sigues, así que intenta seguir un par de cuentas para empezar.\nHaz clic en el
círculo más en la parte superior derecha de un perfil para seguirlos."
step4_1: "Vamos a salir a la calle"
step4_2: "Para tu primer post, a algunas personas les gusta hacer un post de {introduction}
o un simple \"¡Hola mundo!\""
step4_2: "Para tu primer publicación, a algunas personas les gusta escribir una
{introduction} o un simple \"¡Hola mundo!\""
step5_1: "¡Líneas de tiempo, líneas de tiempo por todas partes!"
step5_2: "Su instancia tiene {timelines} diferentes líneas de tiempo habilitadas"
step5_2: "Tu servidor tiene {timelines} diferentes líneas de tiempo habilitadas."
step5_3: "La línea de tiempo Inicio {icon} es donde puedes ver las publicaciones
de tus seguidores."
step5_4: "La línea de tiempo Local {icon} es donde puedes ver las publicaciones
@ -1475,7 +1505,8 @@ _profile:
youCanIncludeHashtags: "Puedes añadir hashtags"
metadata: "información adicional"
metadataEdit: "Editar información adicional"
metadataDescription: "Muestra la información adicional en el perfil. ¡Puede agregar una etiqueta {a} o una etiqueta {l} con {rel} para verificar el enlace en su perfil!"
metadataDescription: "Muestra la información adicional en el perfil. ¡Puede agregar
una etiqueta {a} o una etiqueta {l} con {rel} para verificar el enlace en su perfil!"
metadataLabel: "Etiqueta"
metadataContent: "Contenido"
changeAvatar: "Cambiar avatar"
@ -1887,7 +1918,7 @@ renoteUnmute: Dejar de silenciar impulsos
flagSpeakAsCat: Habla como un gato
selectInstance: Selecciona un servidor
flagSpeakAsCatDescription: Tu publicación se "nyanified" cuando esté en modo gato
allowedInstances: Instancias en la lista blanca
allowedInstances: Servidores autorizados
breakFollowConfirm: ¿Estás seguro de que quieres eliminar el seguidor?
subscribePushNotification: Habilitar notificaciones
unsubscribePushNotification: Desactivar notificaciones
@ -1904,12 +1935,12 @@ hiddenTags: Etiquetas Ocultas
noInstances: No hay servidores
accountMoved: 'Usuario ha movido a una cuenta nueva:'
caption: Auto Subtítulos
showAds: Mostrar Anuncios
showAds: Mostrar banners
enterSendsMessage: Presione "RETORNO" en los mensajes para enviar el mensaje (para
apagarlo es Ctrl + RETORNO)
recommendedInstances: Instancias Recomendadas
instanceSecurity: Seguridad de la instancia
seperateRenoteQuote: Separar impulsados y Citar botones
instanceSecurity: Seguridad del servidor
seperateRenoteQuote: Separar botones de Impulsar y Citar
_messaging:
groups: Grupos
dms: Privado
@ -1941,3 +1972,53 @@ hiddenTagsDescription: 'Escriba los hashtags (sin el #) que desea ocultar de las
jumpToPrevious: Ver anterior
enableEmojiReactions: Habilitar reacciones de emoji
cw: Aviso de contenido
sendPushNotificationReadMessage: Eliminar notificaciones una vez que la notificación
o mensaje ha sido leído
sendPushNotificationReadMessageCaption: Una notificación con el texto "{emptyPushNotificationMessage}"
será mostrada por un breve período. Esto podría aumentar el uso de batería de tu
dispositivo.
enableServerMachineStats: Permitir estadísticas del hardware del servidor
customMOTD: Mensaje del día personalizado (mensajes de la pantalla de presentación)
antennasDesc: "Las Antennas muestran nuevas publicaciones que conciden con los criterios
que estableciste.\nPueden ser accedidas desde la sección de Lineas de tiempo."
antennaInstancesDescription: Escribe un servidor por cada linea
expandOnNoteClickDesc: Si está desactivado, puedes abrir publicaciones usando el menú
del botón derecho del ratón o presionando sobre la fecha.
channelFederationWarn: Los Canales aún no federan con otras instancias
clipsDesc: Los clips como marcadores categorizados que pueden ser compartidos. Puedes
crear clips desde el menú de publicaciones.
verifiedLink: Vínculo verificado
cannotUploadBecauseExceedsFileSizeLimit: Este archivo no pudo ser cargado porque excede
el tamaño máximo permitido.
accessibility: Accesibilidad
_filters:
fromUser: Del usuario
fromDomain: Desde el dominio
notesAfter: Publicaciones posteriores
userSaysSomethingReasonReply: '{name} respondió a una publicación que contiene {reason}'
userSaysSomethingReasonQuote: '{name} citó una publicación que contiene {reason}'
privateModeInfo: Al activar, solo servidores autorizados podrán federar con tu servidor.
Todas las publicaiones estáran ocultas del público.
customMOTDDescription: Mensajes del día personalizados (MOTD) de la pantalla de presentación,
separados cada salto de linea. Para ser mostrados aleatoriamente cada vez que un
usuario carga/recarga una página.
customSplashIcons: Icono personalizado de la pantalla de presentación (url)
donationLink: Vinculo a página de donación
delete2fa: Desactivar autentificación en dos pasos
delete2faConfirm: Esto eliminara irreversiblemente la autentificación en dos pasos
de esta cuenta. ¿Quieres continuar?
allowedInstancesDescription: Host de los servidores autorizados para federar, cada
uno separado por una nueva linea (solo aplica en modo pivado).
adminCustomCssWarn: Está configuración solo debería ser utilizado si sabes lo que
hace. Ingresar valores erroneos podría causar que TODOS los clientes dejaran de
funcionar normalmente. Porfavor asegurate que tus CSS funcionan adecuadamente al
probar los en tus configuraciones de usuario.
image: Imagen
showPopup: Notificar a los usuarios con una ventana emergente
showWithSparkles: Mostrar con destellos
youHaveUnreadAnnouncements: Tienes anuncios sin leer
neverShow: No mostrar nuevamente
remindMeLater: Recordar nuevamente
removeQuote: Eliminar cita
removeRecipient: Eliminar destinatario
removeMember: Eliminar miembro

View file

@ -1,5 +1,6 @@
_lang_: "Français"
headlineFirefish: "Réseau relié par des notes"
headlineFirefish: "Une plateforme de réseaux sociaux décentralisé, open source qui
est gratuit pour toujours ! 🚀"
introFirefish: "Bienvenue ! Firefish est une plateforme de réseau social décentralisé
et open source qui est gratuite pour toujours ! 🚀"
monthAndDay: "{day}/{month}"
@ -13,8 +14,8 @@ ok: "OK"
gotIt: "Jai compris !"
cancel: "Annuler"
enterUsername: "Entrer un nom dutilisateur·rice"
renotedBy: "Renoté par {user}"
noNotes: "Aucune note"
renotedBy: "Boosté par {user}"
noNotes: "Aucun post"
noNotifications: "Aucune notification"
instance: "Serveur"
settings: "Paramètres"
@ -45,8 +46,8 @@ copyContent: "Copier le contenu"
copyLink: "Copier le lien"
delete: "Supprimer"
deleteAndEdit: "Supprimer et réécrire"
deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler
? Vous perdrez toutes les réactions, renotes et réponses y afférentes."
deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer ce post et le reformuler
? Vous perdrez toutes les réactions, boosts et réponses liées."
addToList: "Ajouter à une liste"
sendMessage: "Envoyer un message"
copyUsername: "Copier le nom dutilisateur·rice"
@ -60,14 +61,14 @@ receiveFollowRequest: "Demande dabonnement reçue"
followRequestAccepted: "La demande dabonnement a été acceptée"
mention: "Mentionner"
mentions: "Mentions"
directNotes: "Notes directes"
directNotes: "Messages directs"
importAndExport: "Import et export"
import: "Importer"
export: "Exporter"
files: "Fichiers"
download: "Télécharger"
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\"\
\ ? Il sera retiré de toutes ses notes liées."
\ ? Il sera retiré de tous ses posts liées."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre
un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive."
@ -94,18 +95,18 @@ youShouldUpgradeClient: "Si la page ne s'affiche pas correctement, rechargez-la
enterListName: "Nom de la liste"
privacy: "Confidentialité"
makeFollowManuallyApprove: "Accepter manuellement les demandes dabonnement"
defaultNoteVisibility: "Visibilité des notes par défaut"
defaultNoteVisibility: "Visibilité des posts par défaut"
follow: "Sabonner"
followRequest: "Demande dabonnement"
followRequests: "Demandes dabonnement"
unfollow: "Se désabonner"
followRequestPending: "Demande d'abonnement en attente de confirmation"
enterEmoji: "Insérer un émoji"
renote: "Renoter"
unrenote: "Annuler la Renote"
renoted: "Renoté."
cantRenote: "Ce message ne peut pas être renoté."
cantReRenote: "Impossible de renoter une Renote."
renote: "Booster"
unrenote: "Annuler le boost"
renoted: "Boosté."
cantRenote: "Ce message ne peut pas être boosté."
cantReRenote: "Impossible de partager ce boost."
quote: "Citer"
pinnedNote: "Note épinglée"
pinned: "Épingler sur le profil"
@ -117,9 +118,9 @@ reaction: "Réactions"
reactionSetting: "Réactions à afficher dans le sélecteur de réactions"
reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser
« + » pour ajouter."
rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des notes
rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des posts
\" vous permet de réutiliser automatiquement la visibilité utilisée lors de la publication
de votre note précédente."
de votre post précédent."
attachCancel: "Supprimer le fichier attaché"
markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Supprimer le marquage comme sensible"
@ -175,9 +176,9 @@ proxyAccount: "Compte proxy"
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions,
comme un·e abonné·e distant·e pour les utilisateur·rice·s d'autres serveurs. Par
exemple, quand un·e utilisateur·rice local ajoute un·e utilisateur·rice distant·e
à une liste, ses notes ne seront pas visibles sur le serveur si personne ne suit
à une liste, ses posts ne seront pas visibles sur le serveur si personne ne suit
cet·te utilisateur·rice. Le compte proxy va donc suivre cet·te utilisateur·rice
pour que ses notes soient acheminées."
pour que ses posts soient acheminées."
host: "Serveur distant"
selectUser: "Sélectionner un·e utilisateur·rice"
recipient: "Destinataire"
@ -207,7 +208,7 @@ instanceInfo: "Informations du serveur"
statistics: "Statistiques"
clearQueue: "Vider la file dattente"
clearQueueConfirmTitle: "Êtes-vous sûr·e de vouloir vider la file dattente ?"
clearQueueConfirmText: "Les notes non distribuées ne seront pas délivrées. Normalement,
clearQueueConfirmText: "Les posts non distribués ne seront pas délivrés. Normalement,
vous n'avez pas besoin d'effectuer cette opération."
clearCachedFiles: "Vider le cache"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider tout le cache de fichiers
@ -220,8 +221,8 @@ mutedUsers: "Utilisateur·rice·s en sourdine"
blockedUsers: "Utilisateur·rice·s bloqué·e·s"
noUsers: "Il ny a pas dutilisateur·rice·s"
editProfile: "Modifier votre profil"
noteDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note ?"
pinLimitExceeded: "Vous ne pouvez pas épingler plus de notes"
noteDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer ce post ?"
pinLimitExceeded: "Vous ne pouvez pas épingler plus de posts"
intro: "Linstallation de Firefish est terminée ! Veuillez créer un compte administrateur."
done: "Terminé"
processing: "Traitement en cours"
@ -366,7 +367,7 @@ pinnedPages: "Pages épinglées"
pinnedPagesDescription: "Inscrivez le chemin des Pages que vous souhaitez épingler
en haut de la page du serveur. Séparez les d'un retour à la ligne."
pinnedClipId: "Identifiant du clip épinglé"
pinnedNotes: "Note épinglée"
pinnedNotes: "Posts épinglée"
hcaptcha: "hCaptcha"
enableHcaptcha: "Activer hCaptcha"
hcaptchaSiteKey: "Clé du site"
@ -386,14 +387,14 @@ antennaKeywords: "Mots clés à recevoir"
antennaExcludeKeywords: "Mots clés à exclure"
antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer
avec un saut de ligne pour une condition OR."
notifyAntenna: "Je souhaite recevoir les notifications des nouvelles notes"
withFileAntenna: "Notes ayant des attachements uniquement"
notifyAntenna: "Je souhaite recevoir les notifications des nouveaux posts"
withFileAntenna: "Posts ayant des attachements uniquement"
enableServiceworker: "Activer ServiceWorker"
antennaUsersDescription: "Saisissez un seul nom dutilisateur·rice par ligne"
caseSensitive: "Sensible à la casse"
withReplies: "Inclure les réponses"
connectedTo: "Vous êtes connectés aux services suivants"
notesAndReplies: "Notes et Réponses"
notesAndReplies: "Posts et Réponses"
withFiles: "Avec fichiers joints"
silence: "Mettre en sourdine"
silenceConfirm: "Êtes-vous sûr·e de vouloir mettre lutilisateur·rice en sourdine
@ -431,7 +432,7 @@ notFoundDescription: "Aucune page ne correspond à lURL spécifiée."
uploadFolder: "Emplacement de téléversement par défaut"
cacheClear: "Vider le cache"
markAsReadAllNotifications: "Marquer toutes les notifications comme lues"
markAsReadAllUnreadNotes: "Marquer toutes les notes comme lues"
markAsReadAllUnreadNotes: "Marquer tous les posts comme lus"
markAsReadAllTalkMessages: "Marquer toutes les discussions comme lues"
help: "Aide"
inputMessageHere: "Écrivez votre message ici"
@ -452,7 +453,7 @@ text: "Texte"
enable: "Activer"
next: "Suivant"
retype: "Confirmation"
noteOf: "Notes de {user}"
noteOf: "Posts de {user}"
inviteToGroup: "Inviter dans un groupe"
quoteAttached: "Avec citation"
quoteQuestion: "Souhaitez-vous ajouter une citation ?"
@ -512,8 +513,8 @@ accountSettings: "Paramètres du compte"
promotion: "Promu"
promote: "Promouvoir"
numberOfDays: "Nombre de jours"
hideThisNote: "Masquer cette note"
showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'actualité"
hideThisNote: "Masquer ce post"
showFeaturedNotesInTimeline: "Afficher les posts des Tendances dans le fil d'actualité"
objectStorage: "Stockage d'objets"
useObjectStorage: "Utiliser le stockage d'objets"
objectStorageBaseUrl: "Base URL"
@ -544,7 +545,7 @@ objectStorageSetPublicRead: "Régler sur « public » lors de l'envoi"
serverLogs: "Journal du serveur"
deleteAll: "Supprimer tout"
showFixedPostForm: "Afficher le formulaire de publication en haut du fil d'actualité"
newNoteRecived: "Voir les nouvelles notes"
newNoteRecived: "Voir les nouveaux posts"
sounds: "Sons"
listen: "Écouter"
none: "Rien"
@ -594,8 +595,8 @@ addRelay: "Ajouter un relais"
inboxUrl: "Inbox URL"
addedRelays: "Relais ajoutés"
serviceworkerInfo: "Devrait être activé pour les notifications push."
deletedNote: "Note supprimée"
invisibleNote: "Note invisible"
deletedNote: "Post supprimé"
invisibleNote: "Post invisible"
enableInfiniteScroll: "Activer le défilement infini"
visibility: "Visibilité"
poll: "Sondage"
@ -680,7 +681,7 @@ abuseReports: "Signalements"
reportAbuse: "Signaler"
reportAbuseOf: "Signaler {name}"
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit
d'une note particulière, veuillez inclure le lien."
d'un post particulier, veuillez inclure le lien."
abuseReported: "Le rapport est envoyé. Merci."
reporter: "Signalé par"
reporteeOrigin: "Origine du signalement"
@ -693,7 +694,7 @@ openInSideView: "Ouvrir en vue latérale"
defaultNavigationBehaviour: "Navigation par défaut"
editTheseSettingsMayBreakAccount: "La modification de ces paramètres peut endommager
votre compte."
instanceTicker: "Nom du serveur d'origine des notes"
instanceTicker: "Nom du serveur d'origine des posts"
waitingFor: "En attente de {x}"
random: "Aléatoire"
system: "Système"
@ -708,11 +709,11 @@ i18nInfo: "Firefish est traduit dans différentes langues par des bénévoles. V
contribuer à {link}."
manageAccessTokens: "Gérer les jetons d'accès"
accountInfo: " Informations du compte "
notesCount: "Nombre de notes"
notesCount: "Nombre de posts"
repliesCount: "Nombre de réponses envoyées"
renotesCount: "Nombre de notes que vous avez renotées"
renotesCount: "Nombre de boosts que vous avez envoyé"
repliedCount: "Nombre de réponses reçues"
renotedCount: "Nombre de vos notes renotées"
renotedCount: "Nombre de vos posts boostés"
followingCount: "Nombre de comptes suivis"
followersCount: "Nombre d'abonnés"
sentReactionsCount: "Nombre de réactions envoyées"
@ -725,9 +726,9 @@ driveFilesCount: "Nombre de fichiers dans le Drive"
driveUsage: "Utilisation du Drive"
noCrawle: "Refuser l'indexation par les robots"
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page
de profil, vos notes, vos pages, etc."
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre note sur
\"Abonné-e-s\", vos notes sont visibles par tous, même si vous exigez que les demandes
de profil, vos posts, vos pages, etc."
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre post sur
\"Abonné-e-s\", vos posts sont visibles par tous, même si vous exigez que les demandes
d'abonnement soient approuvées manuellement."
alwaysMarkSensitive: "Marquer les médias comme contenu sensible par défaut"
loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
@ -736,7 +737,7 @@ verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez acc
lien pour compléter la vérification."
notSet: "Non défini"
emailVerified: "Votre adresse e-mail a été vérifiée."
noteFavoritesCount: "Nombre de notes dans les favoris"
noteFavoritesCount: "Nombre de posts dans les favoris"
pageLikesCount: "Nombre de pages aimées"
pageLikedCount: "Nombre de vos pages aimées"
contact: "Contact"
@ -747,7 +748,7 @@ developer: "Développeur"
makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"."
makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra
pas sur la page \"Découvrir\"."
showGapBetweenNotesInTimeline: "Afficher un écart entre les notes du fil dactualité"
showGapBetweenNotesInTimeline: "Afficher un écart entre les posts du fil dactualité"
duplicate: "Duliquer"
left: "Gauche"
center: "Centrer"
@ -760,7 +761,7 @@ showTitlebar: "Afficher la barre de titre"
clearCache: "Vider le cache"
onlineUsersCount: "{n} utilisateur(s) en ligne"
nUsers: "{n} utilisateur·rice·s"
nNotes: "{n} Notes"
nNotes: "{n} Posts"
sendErrorReports: "Envoyer les rapports derreur"
sendErrorReportsDescription: "Si vous activez l'envoi des rapports d'erreur, vous
contribuerez à améliorer la qualité de Firefish grâce au partage d'informations
@ -805,8 +806,8 @@ unlikeConfirm: "Êtes-vous sûr·e de ne plus vouloir aimer cette publication ?"
fullView: "Plein écran"
quitFullView: "Quitter le plein écran"
addDescription: "Ajouter une description"
userPagePinTip: "Vous pouvez afficher des notes ici en sélectionnant l'option « Épingler
au profil » dans le menu de chaque note."
userPagePinTip: "Vous pouvez afficher des posts ici en sélectionnant l'option « Épingler
au profil » dans le menu de chaque post."
notSpecifiedMentionWarning: "Vous avez mentionné des utilisateur·rice·s qui ne font
pas partie de la liste des destinataires"
info: "Informations"
@ -838,7 +839,7 @@ postToGallery: "Publier dans la galerie"
gallery: "Galerie"
recentPosts: "Les plus récentes"
popularPosts: "Les plus consultées"
shareWithNote: "Partager dans une note"
shareWithNote: "Partager dans un post"
ads: "Bannière communautaire"
expiration: "Échéance"
memo: "Pense-bête"
@ -1121,7 +1122,7 @@ _channel:
owned: "Mes canaux"
following: "Abonné·e"
usersCount: "{n} Participant·e·s"
notesCount: "{n} Notes"
notesCount: "{n} Posts"
nameAndDescription: Nom et description
nameOnly: Nom uniquement
_menuDisplay:
@ -1135,20 +1136,20 @@ _wordMute:
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 de votre fil dactualité les notes qui répondent aux conditions
softDescription: "Masquez de votre fil dactualité les posts qui répondent aux conditions
définies."
hardDescription: "Empêche les notes qui remplissent les conditions définies d'être
hardDescription: "Empêche les posts qui remplissent les conditions définies d'être
ajoutées au fil d'actualité. Cette action est irréversible : si vous modifiez
ces conditions plus tard, les notes précédemment filtrées ne seront pas récupérées."
ces conditions plus tard, les posts précédemment filtrées ne seront pas récupérées."
soft: "Doux"
hard: "Strict"
mutedNotes: "Notes filtrées"
mutedNotes: "Posts filtrés"
_instanceMute:
instanceMuteDescription2: "Séparer avec des sauts de lignes"
title: "Masque les notes venant des serveurs listés."
title: "Masque les posts provenant des serveurs listés."
heading: "Serveurs à mettre en sourdine/masquer"
instanceMuteDescription: Ceci va masquer toute publication ou partage de serveurs
listés, incluant celles des personnes répondant à des personnes des serveurs masqués.
instanceMuteDescription: Ceci va masquer toute posts ou boosts de serveurs listés,
incluant celles des personnes répondant à des personnes des serveurs masqués.
_theme:
explore: "Explorer les thèmes"
install: "Installer un thème"
@ -1199,7 +1200,7 @@ _theme:
hashtag: "Hashtags"
mention: "Mentionner"
mentionMe: "Mentions (Moi)"
renote: "Renoter"
renote: "Booster"
modalBg: "Modal d'arrière-plan"
divider: "Séparateur"
scrollbarHandle: "Poignée de la barre de navigation"
@ -1226,8 +1227,8 @@ _theme:
accentLighten: "Plus clair"
fgHighlighted: "Texte mis en évidence"
_sfx:
note: "Nouvelle note"
noteMy: "Ma note"
note: "Nouveau post"
noteMy: "Mon post"
notification: "Notifications"
chat: "Discuter"
chatBg: "Discussion (arrière-plan)"
@ -1254,14 +1255,14 @@ _tutorial:
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps"
step2_1: "Tout d'abord, remplissez votre profil"
step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile
pour les autres de savoir s'ils veulent voir vos notes ou vous suivre."
pour les autres de savoir s'ils veulent voir vos posts ou vous suivre."
step3_1: "Maintenant il est temps de suivre des gens !"
step3_2: "Vos fil dactualité Principal et Social sont basés sur les personnes que
vous suivez, alors essayez de suivre quelques comptes pour commencer.\nCliquez
sur le cercle plus en haut à droite d'un profil pour le suivre."
step4_1: "On y va."
step4_2: "Pour votre première note, certaines personnes aiment faire une note {introduction}
ou une simple note 'Bonjours tout le monde !'."
step4_2: "Pour votre premier post, certaines personnes aiment faire un post {introduction}
ou un simple 'Bonjours tout le monde !'"
step5_1: "Des fils, des fils dactualité partout !"
step5_2: "Votre serveur a {timelines} fils différents disponibles !"
step5_3: "Le fil {icon} Principal est l'endroit où vous pouvez voir les publications
@ -1334,7 +1335,7 @@ _permissions:
"write:messaging": "Gérer les discussions"
"read:mutes": "Voir les comptes masqués"
"write:mutes": "Gérer les comptes masqués"
"write:notes": "Créer / supprimer des notes"
"write:notes": "Créer / supprimer des posts"
"read:notifications": "Afficher les notifications"
"write:notifications": "Gérer vos notifications"
"read:reactions": "Lire les réactions"
@ -1363,12 +1364,12 @@ _auth:
copyAsk: "Veuillez coller le code dautorisation suivant dans l'application :"
allPermissions: Accès complet au compte
_antennaSources:
all: "Toutes les notes"
homeTimeline: "Notes provenant des utilisateur·rice·s auxquel·les je suis abonné"
users: "Notes venant de la part dutilisateur·rice·s précis"
userList: "Notes venant dune liste spécifique"
userGroup: "Notes venant dutilisateur·rice·s du groupe spécifié"
instances: Notes de tous les utilisateurs d'un serveur
all: "Tous les posts"
homeTimeline: "Posts provenant des utilisateur·rice·s auxquel·les je suis abonné"
users: "Posts venant de la part dutilisateur·rice·s précis"
userList: "Posts venant dune liste spécifique"
userGroup: "Posts venant dutilisateur·rice·s du groupe spécifié"
instances: Posts de tous les utilisateurs d'un serveur
_weekday:
sunday: "Dimanche"
monday: "Lundi"
@ -1401,7 +1402,7 @@ _widgets:
_userList:
chooseList: Sélectionner une liste
unixClock: Horloge UNIX
meiliIndexCount: Notes indexées
meiliIndexCount: Posts indexés
serverInfo: Info serveur
meiliStatus: État du serveur
meiliSize: Taille de lindex
@ -1446,8 +1447,8 @@ _visibility:
localOnly: "Local seulement"
localOnlyDescription: "Caché pour les utilisateur·rice·s distant"
_postForm:
replyPlaceholder: "Répondre à cette note ..."
quotePlaceholder: "Citez cette note ..."
replyPlaceholder: "Répondre à ce post ..."
quotePlaceholder: "Citez ce post ..."
channelPlaceholder: "Publier sur une chaîne…"
_placeholders:
a: "Quoi de neuf ?"
@ -1473,7 +1474,7 @@ _profile:
locationDescription: Si vous entrez votre ville en premier, votre heure locale sera
affichée aux autres utilisateur·rice·s.
_exportOrImport:
allNotes: "Toutes les notes"
allNotes: "Tous les posts"
followingList: "Abonnements"
muteList: "Comptes masqués"
blockingList: "Comptes bloqués"
@ -1486,10 +1487,10 @@ _charts:
usersIncDec: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Nombre des utilisateur·rice·s au total"
activeUsers: "Nombre d'utilisateurices actif·ve·s"
notesIncDec: "Variation du nombre des notes"
localNotesIncDec: "Variation du nombre de notes locales"
remoteNotesIncDec: "Variation du nombre de notes distantes"
notesTotal: "Nombre total des notes"
notesIncDec: "Variation du nombre de publications"
localNotesIncDec: "Variation du nombre de publications locales"
remoteNotesIncDec: "Variation du nombre de publications distantes"
notesTotal: "Nombre total des publications"
filesIncDec: "Variation du nombre de fichiers"
filesTotal: "Nombre total de fichiers"
storageUsageIncDec: "Variation de l'utilisation du stockage"
@ -1498,8 +1499,8 @@ _instanceCharts:
requests: "Requêtes"
users: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Total cumulé du nombre d'utilisateur·rice·s"
notes: "Variation du nombre de notes"
notesTotal: "Nombre total cumulé des notes"
notes: "Variation du nombre de publications"
notesTotal: "Nombre total cumulé des publications"
ff: "Variation des abonné·e·s / abonnements"
ffTotal: "Total cumulé du nombre d'abonné·e·s / abonnements"
cacheSize: "Variation de la taille du cache"
@ -1587,10 +1588,10 @@ _pages:
id: "Toile ID"
width: "Largeur"
height: "Hauteur"
note: "Note intégrée"
note: "Post intégré"
_note:
id: "Identifiant de la note"
idDescription: "Vous pouvez aussi coller ici l'URL ici."
id: "Identifiant du post"
idDescription: "Vous pouvez aussi coller l'URL du post ici."
detailed: "Afficher les détails"
switch: "Interrupteur"
_switch:
@ -1813,7 +1814,7 @@ _notification:
youGotMention: "{name} vous a mentionné"
youGotReply: "Réponse de {name}"
youGotQuote: "Cité·e par {name}"
youRenoted: "{name} vous a Renoté"
youRenoted: "{name} vous a boosté"
youGotPoll: "{name} a participé à votre sondage"
youGotMessagingMessageFromUser: "{name} vous envoyé un message"
youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
@ -1828,7 +1829,7 @@ _notification:
follow: "Nouvel·le abonné·e"
mention: "Mentions"
reply: "Réponses"
renote: "Renotes"
renote: "Boosts"
quote: "Citations"
reaction: "Réactions"
pollVote: "Votes dans des sondages"
@ -1840,9 +1841,9 @@ _notification:
_actions:
followBack: "Suivre"
reply: "Répondre"
renote: "Renoter"
renote: "Boosts"
reacted: a réagit à votre Note
renoted: a boosté votre Note
renoted: a boosté votre post
voted: a voté pour votre sondage
_deck:
alwaysShowMainColumn: "Toujours afficher la colonne principale"
@ -1931,7 +1932,7 @@ moveAccountDescription: Ce processus est irréversible. Soyez sûr⋅e que vous
un alias pour ce compte sur votre nouveau compte avant de migrer. Merci d'entrer
la mention du compte formaté comme ceci @personne@server.com
moveAccount: Déplacer le compte !
seperateRenoteQuote: Séparer les renotes et les boutons de citation
seperateRenoteQuote: Séparer les boutons de boosts et de citation
failedToFetchAccountInformation: Impossible de récupérer les informations de compte
noEmailServerWarning: Serveur mail non configuré.
deleteAccount: Supprimer le compte
@ -2002,7 +2003,7 @@ forwardReportIsAnonymous: À la place de votre compte, un compte système anonym
affiché comme rapporteur au serveur distant.
noThankYou: Non merci
addInstance: Ajouter un serveur
renoteMute: Mettre en silence les renotes
renoteMute: Masquer les boosts
flagSpeakAsCat: Parler comme un chat
flagSpeakAsCatDescription: Vos messages seront nyanifiés en mode chat
hiddenTags: Hashtags cachés
@ -2059,14 +2060,14 @@ pushNotificationAlreadySubscribed: Notifications push déjà activées
logoImageUrl: URL de l'image du logo
moveToLabel: 'Compte vers lequel vous migrez:'
moveFrom: Migrer vers ce compte depuis un ancien compte
defaultReaction: Émoji de réaction par défaut pour les notes entrantes et sortantes
defaultReaction: Émoji de réaction par défaut pour les posts entrants et sortants
license: Licence
indexPosts: Indexer les Notes
indexPosts: Indexer les Posts
indexNotice: Indexation en cours. Cela prendra certainement du temps, veuillez ne
pas redémarrer votre serveur pour au moins une heure.
customKaTeXMacro: Macros KaTeX personnalisées
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
noteId: ID de Note
noteId: ID des Posts
customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques
simplement ! La notation se conforme aux définitions de commandes LaTeX et s'écrit
\\newcommand{\\·name}{content} ou \\newcommand{\\name}[number of arguments]{content}.
@ -2085,7 +2086,7 @@ deleted: Effacé
editNote: Modifier note
edited: 'Modifié à {date} {time}'
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
utilisatieur·rice·s aux notes des autres.
utilisatieur·rice·s aux posts des autres.
_experiments:
alpha: Alpha
beta: Beta
@ -2096,21 +2097,21 @@ _experiments:
peut entraîner des ralentissements lors du chargement si votre file d'attente
est congestionnée.
findOtherInstance: Trouver un autre serveur
userSaysSomethingReasonQuote: '{name} a cité une note contenant {reason}'
userSaysSomethingReasonQuote: '{name} a cité un post contenant {reason}'
signupsDisabled: Les inscriptions sur ce serveur sont actuellement désactivés, mais
vous pouvez toujours vous inscrire sur un autre serveur ! Si vous avez un code d'invitation
pour ce serveur, entrez-le ci-dessous s'il vous plait.
apps: Applications
userSaysSomethingReasonReply: '{noms} a répondu à une note contenant {raison}'
userSaysSomethingReasonReply: '{noms} a répondu à un post contenant {raison}'
defaultValueIs: 'défaut: {valeur}'
searchPlaceholder: Recherchez sur Firefish
removeReaction: Retirer votre réaction
selectChannel: Sélectionner une chaîne
expandOnNoteClick: Ouvrir la note en cliquant
expandOnNoteClick: Ouvrir le post en cliquant
preventAiLearning: Empêcher le récupération de données par des IA
listsDesc: Les listes vous laissent créer des fils personnalisés avec des utilisateur·rice·s
spécifié·e·s. Elles sont accessibles depuis la page des fils.
indexFromDescription: Laisser vide pour indexer toutes les Notes
indexFromDescription: Laisser vide pour indexer toutes les Posts
_feeds:
jsonFeed: flux JSON
atom: Atom
@ -2118,9 +2119,9 @@ _feeds:
rss: RSS
alt: ALT
swipeOnMobile: Permettre le balayage entre les pages
expandOnNoteClickDesc: Si désactivé, vous pourrez toujours ouvrir les Notes dans le
expandOnNoteClickDesc: Si désactivé, vous pourrez toujours ouvrir les posts dans le
menu du clic droit et en cliquant sur lhorodatage.
indexFrom: Indexer à partir de lID des Notes
indexFrom: Indexer à partir de lID des Posts
older: plus ancien
newer: plus récent
accessibility: Accessibilité
@ -2128,7 +2129,7 @@ silencedInstancesDescription: Listez les noms de domaine de serveurs que vous vo
masquer. Les comptes des serveurs listés seront traités comme "Masqués", ne pourront
faire que des demandes dabonnement, et ne pourront pas mentionner les comptes locaux
si non-suivis. Cela naffectera en rien les serveurs bloqués.
antennasDesc: "Les Antennes affichent de nouvelles notes selon les critères que vous
antennasDesc: "Les Antennes affichent de nouveaux posts selon les critères que vous
indiqués.\nElles peuvent être consultées depuis la page des fils."
image: Image
video: Vidéo
@ -2138,10 +2139,10 @@ cw: Avertissement de contenu
xl: XL
reflectMayTakeTime: Il pourra sécouler un certain temps avant que les changements
ne soient reflétés.
userSaysSomethingReasonRenote: '{name} a boosté une note contenant {reason}'
userSaysSomethingReasonRenote: '{name} a boosté un post contenant {reason}'
sendModMail: Envoyer un avis à la modération
clipsDesc: Les clips sont comme des favoris catégorisés pouvant être partagés. Vous
pouvez créer des clips à partir du menu de chaque note.
pouvez créer des clips à partir du menu de chaque post.
unclip: Dé-clipper
secureMode: Mode sécurisé (Authorized Fetch)
secureModeInfo: Quand sollicité depuis d'autres serveurs, ne pas répondre sans preuve.
@ -2174,8 +2175,8 @@ isPatron: Mécène Firefish
_filters:
fromUser: De lutilisateur
withFile: Avec fichier
notesBefore: Notes avant
notesAfter: Notes après
notesBefore: Posts avant
notesAfter: Posts après
followersOnly: Abonnés uniquement
followingOnly: Abonnements uniquement
fromDomain: Du domaine

View file

@ -1,5 +1,6 @@
_lang_: "Bahasa Indonesia"
headlineFirefish: "Jaringan terhubung melalui catatan"
headlineFirefish: "Platform media sosial sumber terbuka dan terdesentralisasi yang
merdeka sepenuhnya! 🚀"
introFirefish: "Selamat datang! Firefish adalah media sosial sumber terbuka dan terdesentralisasi
yang selamanya bebas! 🚀"
monthAndDay: "{day} {month}"
@ -93,7 +94,7 @@ youShouldUpgradeClient: "Untuk melihat halaman ini, mohon muat ulang untuk memut
enterListName: "Masukkan nama daftar"
privacy: "Privasi"
makeFollowManuallyApprove: "Permintaan mengikuti membutuhkan persetujuan"
defaultNoteVisibility: "Privasi bawaan catatan"
defaultNoteVisibility: "Visibilitas bawaan"
follow: "Ikuti"
followRequest: "Permintaan Mengikuti"
followRequests: "Permintaan mengikuti"
@ -743,7 +744,7 @@ developer: "Pengembang"
makeExplorable: "Buat akun tampil di \"Jelajahi\""
makeExplorableDescription: "Jika kamu menonaktifkan ini, akun kamu tidak akan muncul
di bagian \"Jelajahi\"."
showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada linimasa"
showGapBetweenNotesInTimeline: "Tampilkan jarak diantara postingan pada linimasa"
duplicate: "Duplikat"
left: "Kiri"
center: "Tengah"
@ -834,7 +835,7 @@ gallery: "Galeri"
recentPosts: "Postingan terbaru"
popularPosts: "Postingan populer"
shareWithNote: "Bagikan dengan postingan"
ads: "Iklan"
ads: "Spanduk komunitas"
expiration: "Batas akhir"
memo: "Memo"
priority: "Prioritas"
@ -1245,8 +1246,7 @@ _tutorial:
step1_2: "Ayo siapkan. Lekas mulai tanpa basa-basi!"
step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti
siapapun."
step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti
seseorang."
step2_1: "Pertama, isi profilmu lebih dahulu."
step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuatnya lebih
mudah bagi orang lain untuk menentukan jika mereka ingin postinganmu atau mengikutimu."
step3_1: "Sekarang saatnya mengikuti beberapa orang!"
@ -1558,7 +1558,7 @@ _pages:
if: "Jika"
_if:
variable: "Variabel"
post: "Buat catatan"
post: "Formulir posting"
_post:
text: "Isi"
attachCanvasImage: "Posting dengan kanvas sebagai gambar"
@ -2011,7 +2011,7 @@ pushNotificationAlreadySubscribed: Pemberitahuan dorong sudah aktif
pushNotificationNotSupported: Peramban atau servermu tidak mendukung pemberitahuan
dorong
apps: Aplikasi
showAds: Tampilkan iklan
showAds: Tampilkan spanduk komunitas
enterSendsMessage: Tekan Enter pada Pesan untuk mengirim pesan (matikan dengan Ctrl
+ Enter)
showAdminUpdates: Indikasi versi Firefish baru tersedia (hanya admin)
@ -2157,3 +2157,9 @@ origin: Asal
objectStorageS3ForcePathStyle: Gunakan URL titik akhir berbasis lokasi
objectStorageS3ForcePathStyleDesc: Aktifkan ini untuk membuat URL titik akhir dalam
format 's3.amazonaws.com/<bucket>/' di atas '<bucket>.s3.amazonaws.com'.
deletePasskeys: Hapus passkeys
inputNotMatch: Masukan tidak cocok
delete2fa: Nonaktifkan 2FA
delete2faConfirm: Ini akan menghapus 2FA secara permanen pada akun ini. Lanjutkan?
deletePasskeysConfirm: Ini akan menghapus semua passkeys dan kunci keamanan pada akun
ini secara permanen. Lanjutkan?

View file

@ -1076,7 +1076,7 @@ _aboutFirefish:
about: "Firefishは、2022年に生まれたThatOneCalculatorによるMisskeyのforkです。"
contributors: "主なコントリビューター"
allContributors: "全てのコントリビューター"
originalMisskeyContributors: "フォーク元のMisskeyの主なコントリビューター"
misskeyContributors: "フォーク元のMisskeyの主なコントリビューター"
source: "ソースコード"
translation: "Firefishを翻訳"
donate: "Firefishに寄付"

View file

@ -1,6 +1,6 @@
{
"name": "firefish",
"version": "1.0.4-dev6",
"version": "1.0.4-beta",
"codename": "aqua",
"repository": {
"type": "git",
@ -22,6 +22,8 @@
"dev": "pnpm node ./scripts/dev.js",
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
"lint": "pnpm -r --parallel run lint",
"debug": "pnpm run build:debug && pnpm run start",
"build:debug": "pnpm -r --parallel run build:debug && pnpm run gulp",
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",

View file

@ -42,13 +42,15 @@ async fn main() {
None => "redis",
Some(_) => "rediss",
};
let redis_uri_userpass = match redis_conf.user {
None => "".to_string(),
Some(user) => format!("{}:{}@", user, encode(&redis_conf.pass.unwrap_or_default())),
};
let redis_user = redis_conf.user.unwrap_or("default".to_string());
let redis_uri_userpass = format!(
"{}:{}",
redis_user,
encode(&redis_conf.pass.unwrap_or_default())
);
let redis_uri_hostport = format!("{}:{}", redis_conf.host, redis_conf.port);
let redis_uri = format!(
"{}://{}{}/{}",
"{}://{}@{}/{}",
redis_proto, redis_uri_userpass, redis_uri_hostport, redis_conf.db
);
env::set_var(CACHE_URL_ENV, redis_uri);

View file

@ -36,8 +36,8 @@
"artifacts": "napi artifacts",
"build": "pnpm run build:napi && pnpm run build:migration",
"build:napi": "napi build --features napi --platform --release ./built/",
"build:migration": "cargo build --locked --release --manifest-path ./migration/Cargo.toml && cp ./target/release/migration ./built/migration",
"build:debug": "napi build --platform ./built/ && cargo build --manifest-path ./migration/Cargo.toml",
"build:migration": "cargo build --locked --release --manifest-path ./migration/Cargo.toml && cp -v ./target/release/migration ./built/migration",
"build:debug": "napi build --features napi --platform ./built/ && cargo build --locked --manifest-path ./migration/Cargo.toml && cp -v ./target/debug/migration ./built/migration",
"prepublishOnly": "napi prepublish -t npm",
"test": "pnpm run cargo:test && pnpm run build:napi && ava",
"universal": "napi universal",

View file

@ -14,6 +14,7 @@
"revertmigration:cargo": "./native-utils/built/migration down",
"check:connect": "node ./check_connect.js",
"build": "pnpm swc src -d built -D",
"build:debug": "pnpm swc src -d built -s -D",
"watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check --apply *",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",

View file

@ -156,7 +156,7 @@ export function toHtml(
search(node) {
const a = doc.createElement("a");
a.href = `https://search.annoyingorange.xyz/search?q=${node.props.query}`;
a.href = `/search/${node.props.query}`;
a.textContent = node.props.content;
return a;
},

View file

@ -1,16 +1,18 @@
export type Post = {
text: string | null;
text: string | undefined;
cw: string | null;
localOnly: boolean;
createdAt: Date;
visibility: string;
};
export function parse(acct: any): Post {
return {
text: acct.text,
text: acct.text || undefined,
cw: acct.cw,
localOnly: acct.localOnly,
createdAt: new Date(acct.createdAt),
visibility: "hidden" + (acct.visibility || ""),
};
}

View file

@ -28,6 +28,7 @@ import {
backgroundQueue,
} from "./queues.js";
import type { ThinUser } from "./types.js";
import { Note } from "@/models/entities/note.js";
function renderError(e: Error): any {
return {
@ -358,6 +359,7 @@ export function createImportCkPostJob(
user: ThinUser,
post: any,
signatureCheck: boolean,
parent: Note | null = null,
) {
return dbQueue.add(
"importCkPost",
@ -365,6 +367,7 @@ export function createImportCkPostJob(
user: user,
post: post,
signatureCheck: signatureCheck,
parent: parent,
},
{
removeOnComplete: true,

View file

@ -3,7 +3,13 @@ import create from "@/services/note/create.js";
import { Users } from "@/models/index.js";
import type { DbUserImportMastoPostJobData } from "@/queue/types.js";
import { queueLogger } from "../../logger.js";
import { uploadFromUrl } from "@/services/drive/upload-from-url.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import type Bull from "bull";
import { createImportCkPostJob } from "@/queue/index.js";
import { Notes, NoteEdits } from "@/models/index.js";
import type { Note } from "@/models/entities/note.js";
import { genId } from "@/misc/gen-id.js";
const logger = queueLogger.createSubLogger("import-firefish-post");
@ -17,6 +23,7 @@ export async function importCkPost(
return;
}
const post = job.data.post;
/*
if (post.replyId != null) {
done();
return;
@ -29,23 +36,74 @@ export async function importCkPost(
done();
return;
}
const { text, cw, localOnly, createdAt } = Post.parse(post);
const note = await create(user, {
*/
const urls = (post.files || [])
.map((x: any) => x.url)
.filter((x: String) => x.startsWith("http"));
const files: DriveFile[] = [];
for (const url of urls) {
try {
const file = await uploadFromUrl({
url: url,
user: user,
});
files.push(file);
} catch (e) {
logger.error(`Skipped adding file to drive: ${url}`);
}
}
const { text, cw, localOnly, createdAt, visibility } = Post.parse(post);
let note = await Notes.findOneBy({
createdAt: createdAt,
files: undefined,
poll: undefined,
text: text || undefined,
reply: null,
renote: null,
cw: cw,
localOnly,
visibility: "hidden",
visibleUsers: [],
channel: null,
apMentions: new Array(0),
apHashtags: undefined,
apEmojis: undefined,
text: text,
userId: user.id,
});
if (note && (note?.fileIds?.length || 0) < files.length) {
const update: Partial<Note> = {};
update.fileIds = files.map((x) => x.id);
await Notes.update(note.id, update);
await NoteEdits.insert({
id: genId(),
noteId: note.id,
text: note.text || undefined,
cw: note.cw,
fileIds: note.fileIds,
updatedAt: new Date(),
});
logger.info(`Note file updated`);
}
if (!note) {
note = await create(user, {
createdAt: createdAt,
files: files.length == 0 ? undefined : files,
poll: undefined,
text: text || undefined,
reply: post.replyId ? job.data.parent : null,
renote: post.renoteId ? job.data.parent : null,
cw: cw,
localOnly,
visibility: visibility,
visibleUsers: [],
channel: null,
apMentions: new Array(0),
apHashtags: undefined,
apEmojis: undefined,
});
logger.info(`Create new note`);
} else {
logger.info(`Note exist`);
}
logger.succ("Imported");
if (post.childNotes) {
for (const child of post.childNotes) {
createImportCkPostJob(
job.data.user,
child,
job.data.signatureCheck,
note,
);
}
}
done();
}

View file

@ -8,6 +8,9 @@ import { resolveNote } from "@/remote/activitypub/models/note.js";
import { Note } from "@/models/entities/note.js";
import { uploadFromUrl } from "@/services/drive/upload-from-url.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import { Notes, NoteEdits } from "@/models/index.js";
import type { Note } from "@/models/entities/note.js";
import { genId } from "@/misc/gen-id.js";
const logger = queueLogger.createSubLogger("import-masto-post");
@ -67,23 +70,47 @@ export async function importMastoPost(
}
}
}
const note = await create(user, {
let note = await Notes.findOneBy({
createdAt: new Date(post.object.published),
files: files.length == 0 ? undefined : files,
poll: undefined,
text: text || undefined,
reply,
renote: null,
cw: post.object.sensitive ? post.object.summary : undefined,
localOnly: false,
visibility: "hidden",
visibleUsers: [],
channel: null,
apMentions: new Array(0),
apHashtags: undefined,
apEmojis: undefined,
text: text,
userId: user.id,
});
if (note && (note?.fileIds?.length || 0) < files.length) {
const update: Partial<Note> = {};
update.fileIds = files.map((x) => x.id);
await Notes.update(note.id, update);
await NoteEdits.insert({
id: genId(),
noteId: note.id,
text: note.text || undefined,
cw: note.cw,
fileIds: note.fileIds,
updatedAt: new Date(),
});
logger.info(`Note file updated`);
}
if (!note) {
note = await create(user, {
createdAt: new Date(post.object.published),
files: files.length == 0 ? undefined : files,
poll: undefined,
text: text || undefined,
reply,
renote: null,
cw: post.object.sensitive ? post.object.summary : undefined,
localOnly: false,
visibility: "hiddenpublic",
visibleUsers: [],
channel: null,
apMentions: new Array(0),
apHashtags: undefined,
apEmojis: undefined,
});
logger.info(`Create new note`);
} else {
logger.info(`Note exist`);
}
job.progress(100);
done();

View file

@ -57,7 +57,8 @@ export async function importPosts(
const parsed = JSON.parse(json);
if (parsed instanceof Array) {
logger.info("Parsing key style posts");
for (const post of JSON.parse(json)) {
const arr = recreateChain(parsed);
for (const post of arr) {
createImportCkPostJob(job.data.user, post, job.data.signatureCheck);
}
} else if (parsed instanceof Object) {
@ -74,3 +75,32 @@ export async function importPosts(
logger.succ("Imported");
done();
}
function recreateChain(arr: any[]): any {
type NotesMap = {
[id: string]: any;
};
const notesTree: any[] = [];
const lookup: NotesMap = {};
for (const note of arr) {
lookup[`${note.id}`] = note;
note.childNotes = [];
if (note.replyId == null && note.renoteId == null) {
notesTree.push(note);
}
}
for (const note of arr) {
let parent = null;
if (note.replyId != null) {
parent = lookup[`${note.replyId}`];
}
if (note.renoteId != null) {
parent = lookup[`${note.renoteId}`];
}
if (parent) {
parent.childNotes.push(note);
}
}
return notesTree;
}

View file

@ -52,6 +52,7 @@ export type DbUserImportMastoPostJobData = {
user: ThinUser;
post: any;
signatureCheck: boolean;
parent: Note | null;
};
export type ObjectStorageJobData =

View file

@ -27,6 +27,8 @@ export function apiStatusMastodon(router: Router): void {
let body: any = ctx.request.body;
if (body.in_reply_to_id)
body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.FirefishId);
if (body.quote_id)
body.quote_id = convertId(body.quote_id, IdType.FirefishId);
if (
(!body.poll && body["poll[options][]"]) ||
(!body.media_ids && body["media_ids[]"])

View file

@ -18,6 +18,10 @@ export function argsToBools(q: ParsedUrlQuery) {
const toBoolean = (value: string) =>
!["0", "f", "F", "false", "FALSE", "off", "OFF"].includes(value);
// Keys taken from:
// - https://docs.joinmastodon.org/methods/accounts/#statuses
// - https://docs.joinmastodon.org/methods/timelines/#public
// - https://docs.joinmastodon.org/methods/timelines/#tag
let object: any = q;
if (q.only_media)
if (typeof q.only_media === "string")
@ -25,6 +29,13 @@ export function argsToBools(q: ParsedUrlQuery) {
if (q.exclude_replies)
if (typeof q.exclude_replies === "string")
object.exclude_replies = toBoolean(q.exclude_replies);
if (q.exclude_reblogs)
if (typeof q.exclude_reblogs === "string")
object.exclude_reblogs = toBoolean(q.exclude_reblogs);
if (q.pinned)
if (typeof q.pinned === "string") object.pinned = toBoolean(q.pinned);
if (q.local)
if (typeof q.local === "string") object.local = toBoolean(q.local);
return q;
}

View file

@ -0,0 +1,221 @@
import { Readable, ReadableOptions } from "node:stream";
import { Buffer, constants as BufferConstants } from "node:buffer";
import * as fs from "node:fs";
interface ByteRange {
start: bigint;
end: bigint;
size: bigint;
}
const BOUNDARY_CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const BYTERANGE_SPEC_REGEX = /^bytes=(.+)$/;
const BYTERANGE_REGEX = /(\d*)-(\d*)/;
const BIGINT_0 = BigInt(0);
const BIGINT_1 = BigInt(1);
const BOUNDARY_SIZE = 40;
function extractRanges(
fileSize: bigint,
maxByteRanges: number,
rangeHeaderValue: string,
): ByteRange[] {
const ranges: ByteRange[] = [];
if (!rangeHeaderValue) return ranges;
const rangeSpecMatch = rangeHeaderValue.match(BYTERANGE_SPEC_REGEX);
if (!rangeSpecMatch) return [];
const rangeSpecs = rangeSpecMatch[1].split(",");
for (let i = 0; i < rangeSpecs.length; i = i + 1) {
const byteRange = rangeSpecs[i].match(BYTERANGE_REGEX);
if (!byteRange) return [];
let start: bigint;
let end: bigint;
let size: bigint;
if (byteRange[1]) {
start = BigInt(byteRange[1]);
}
if (byteRange[2]) {
end = BigInt(byteRange[2]);
}
if (start === undefined && end === undefined) {
/* some invalid range like bytes=- */
return [];
}
if (start === undefined) {
/* end-of-file range like -500 */
start = fileSize - end;
end = fileSize - BIGINT_1;
if (start < BIGINT_0) return []; /* range larger than file, return */
}
if (end === undefined) {
/* range like 0- */
end = fileSize - BIGINT_1;
}
if (start > end || end >= fileSize) {
/* return empty range to issue regular 200 */
return [];
}
size = end - start + BIGINT_1;
if (1 > maxByteRanges - ranges.length) return [];
ranges.push({
start: start,
end: end,
size: size,
});
}
return ranges;
}
function createBoundary(len: number): string {
let chars = [];
for (let i = 0; i < len; i = i + 1) {
chars[i] = BOUNDARY_CHARS.charAt(
Math.floor(Math.random() * BOUNDARY_CHARS.length),
);
}
return chars.join("");
}
class ByteRangeReadable extends Readable {
size: bigint; /* the total size in bytes */
boundary: string; /* boundary marker to use in multipart headers */
private fd: number;
private ranges: ByteRange[];
private index: number; /* index within ranges */
private position: bigint;
private end: bigint;
private contentType: string;
private fileSize: bigint;
private headers: Buffer[];
private trailer: Buffer;
static parseByteRanges(
fileSize: bigint,
maxByteRanges: number,
rangeHeaderValue?: string,
): ByteRange[] {
return extractRanges(fileSize, maxByteRanges, rangeHeaderValue);
}
private createPartHeader(range: ByteRange): Buffer {
return Buffer.from(
[
"",
`--${this.boundary}`,
`Content-Type: ${this.contentType}`,
`Content-Range: bytes ${range.start}-${range.end}/${this.fileSize}`,
"",
"",
].join("\r\n"),
);
}
constructor(
fd: number,
fileSize: bigint,
ranges: ByteRange[],
contentType: string,
opts?: ReadableOptions,
) {
super(opts);
if (ranges.length === 0) {
throw Error("this requires at least 1 byte range");
}
this.fd = fd;
this.ranges = ranges;
this.fileSize = fileSize;
this.contentType = contentType;
this.position = BIGINT_1;
this.end = BIGINT_0;
this.index = -1;
this.headers = [];
this.size = BIGINT_0;
if (this.ranges.length === 1) {
this.size = this.ranges[0].size;
} else {
this.boundary = createBoundary(BOUNDARY_SIZE);
this.ranges.forEach((r) => {
const header = this.createPartHeader(r);
this.headers.push(header);
this.size += BigInt(header.length) + r.size;
});
this.trailer = Buffer.from(`\r\n--${this.boundary}--\r\n`);
this.size += BigInt(this.trailer.length);
}
}
_read(n) {
if (this.index == this.ranges.length) {
this.push(null);
return;
}
if (this.position > this.end) {
/* move ahead to the next index */
this.index++;
if (this.index === this.ranges.length) {
if (this.trailer) {
this.push(this.trailer);
return;
}
this.push(null);
return;
}
this.position = this.ranges[this.index].start;
this.end = this.ranges[this.index].end;
if (this.ranges.length > 1) {
this.push(this.headers[this.index]);
return;
}
}
const max = this.end - this.position + BIGINT_1;
if (n > max) n = Number(max);
const buf = Buffer.alloc(n);
fs.read(this.fd, buf, 0, n, this.position, (err, bytesRead) => {
if (err) {
this.destroy(err);
return;
}
if (bytesRead == 0) {
/* something seems to have gone wrong? */
this.push(null);
return;
}
if (bytesRead > n) bytesRead = n;
this.position += BigInt(bytesRead);
this.push(buf.slice(0, bytesRead));
});
}
}
export { ByteRange, ByteRangeReadable };

View file

@ -14,6 +14,7 @@ import { detectType } from "@/misc/get-file-info.js";
import { convertToWebp } from "@/services/drive/image-processor.js";
import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
import { StatusError } from "@/misc/fetch.js";
import { ByteRangeReadable } from "./byte-range-readable.js";
import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
const _filename = fileURLToPath(import.meta.url);
@ -21,6 +22,8 @@ const _dirname = dirname(_filename);
const assets = `${_dirname}/../../server/file/assets/`;
const MAX_BYTE_RANGES = 10;
const commonReadableHandlerGenerator =
(ctx: Koa.Context) => (e: Error): void => {
serverLogger.error(e);
@ -122,31 +125,88 @@ export default async function (ctx: Koa.Context) {
return;
}
let contentType;
let filename;
let fileHandle;
if (isThumbnail || isWebpublic) {
const { mime, ext } = await detectType(InternalStorage.resolvePath(key));
const filename = rename(file.name, {
suffix: isThumbnail ? "-thumb" : "-web",
extname: ext ? `.${ext}` : undefined,
}).toString();
(contentType = FILE_TYPE_BROWSERSAFE.includes(mime)
? mime
: "application/octet-stream"),
(filename = rename(file.name, {
suffix: isThumbnail ? "-thumb" : "-web",
extname: ext ? `.${ext}` : undefined,
}).toString());
ctx.body = InternalStorage.read(key);
ctx.set(
"Content-Type",
FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : "application/octet-stream",
);
ctx.set("Cache-Control", "max-age=31536000, immutable");
ctx.set("Content-Disposition", contentDisposition("inline", filename));
fileHandle = await InternalStorage.open(key, "r");
} else {
const readable = InternalStorage.read(file.accessKey!);
readable.on("error", commonReadableHandlerGenerator(ctx));
ctx.body = readable;
ctx.set(
"Content-Type",
FILE_TYPE_BROWSERSAFE.includes(file.type)
? file.type
: "application/octet-stream",
);
ctx.set("Cache-Control", "max-age=31536000, immutable");
ctx.set("Content-Disposition", contentDisposition("inline", file.name));
(contentType = FILE_TYPE_BROWSERSAFE.includes(file.type)
? file.type
: "application/octet-stream"),
(filename = file.name);
fileHandle = await InternalStorage.open(file.accessKey!, "r");
}
// We can let Koa evaluate conditionals by setting
// the status to 200, along with the lastModified
// and etag properties, then checking ctx.fresh.
// Additionally, Range is ignored if a conditional GET would
// result in a 304 response, so we can return early here.
ctx.status = 200;
ctx.etag = file.md5;
ctx.lastModified = file.createdAt;
// When doing a conditional request, we MUST return a "Cache-Control" header
// if a normal 200 response would have included.
ctx.set("Cache-Control", "max-age=31536000, immutable");
if (ctx.fresh) {
ctx.status = 304;
return;
}
ctx.length = file.size;
ctx.set("Content-Disposition", contentDisposition("inline", filename));
ctx.set("Content-Type", contentType);
const ranges = ByteRangeReadable.parseByteRanges(
BigInt(file.size),
MAX_BYTE_RANGES,
ctx.headers["range"],
);
const readable =
ranges.length === 0
? fileHandle.createReadStream()
: new ByteRangeReadable(
fileHandle.fd,
BigInt(file.size),
ranges,
contentType,
);
readable.on("error", commonReadableHandlerGenerator(ctx));
ctx.body = readable;
if (ranges.length === 0) {
ctx.set("Accept-Ranges", "bytes");
} else {
ctx.status = 206;
ctx.length = readable.size;
readable.on("close", async () => {
await fileHandle.close();
});
if (ranges.length === 1) {
ctx.set(
"Content-Range",
`bytes ${ranges[0].start}-${ranges[0].end}/${file.size}`,
);
} else {
ctx.set(
"Content-Type",
`multipart/byteranges; boundary=${readable.boundary}`,
);
}
}
}

View file

@ -1,4 +1,5 @@
import * as fs from "node:fs";
import * as fsPromises from "node:fs/promises";
import * as Path from "node:path";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
@ -13,6 +14,10 @@ export class InternalStorage {
public static resolvePath = (key: string) =>
Path.resolve(InternalStorage.path, key);
public static open(key: string, flags: string) {
return fsPromises.open(InternalStorage.resolvePath(key), flags);
}
public static read(key: string) {
return fs.createReadStream(InternalStorage.resolvePath(key));
}

View file

@ -6,10 +6,13 @@ async function getRelMeLinks(url: string): Promise<string[]> {
try {
const html = await getHtml(url);
const dom = new JSDOM(html);
const relMeLinks = [
...dom.window.document.querySelectorAll("a[rel='me']"),
...dom.window.document.querySelectorAll("link[rel='me']"),
].map((a) => (a as HTMLAnchorElement | HTMLLinkElement).href);
const allLinks = [...dom.window.document.querySelectorAll("a, link")];
const relMeLinks = allLinks
.filter((a) => {
const relAttribute = a.getAttribute("rel");
return relAttribute ? relAttribute.split(" ").includes("me") : false;
})
.map((a) => (a as HTMLAnchorElement | HTMLLinkElement).href);
return relMeLinks;
} catch {
return [];

View file

@ -172,7 +172,7 @@ export default async (
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => {
const dontFederateInitially =
data.localOnly || data.visibility === "hidden";
data.localOnly || data.visibility?.startsWith("hidden");
// If you reply outside the channel, match the scope of the target.
// TODO (I think it's a process that could be done on the client side, but it's server side for now.)
@ -206,7 +206,8 @@ export default async (
if (data.channel != null) data.visibility = "public";
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
if (data.visibility === "hidden") data.visibility = "public";
if (data.visibility.startsWith("hidden"))
data.visibility = data.visibility.slice(6);
// enforce silent clients on server
if (

View file

@ -4,6 +4,7 @@
"scripts": {
"watch": "pnpm vite build --watch --mode development",
"build": "pnpm vite build",
"build:debug": "pnpm run build",
"lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue",
"lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"

View file

@ -132,7 +132,7 @@ export default defineComponent({
display: none;
}
> *:not(:last-child) {
&:not(.date-separated-list-nogap) > *:not(:last-child) {
margin-bottom: var(--margin);
}

View file

@ -11,6 +11,9 @@
<script lang="ts" setup>
import { ref } from "vue";
import { i18n } from "@/i18n";
import { useRouter } from "@/router";
const router = useRouter();
const props = defineProps<{
q: string;
@ -19,10 +22,7 @@ const props = defineProps<{
const query = ref(props.q);
const search = () => {
window.open(
`https://search.annoyingorange.xyz/search?q=${query.value}`,
"_blank",
);
router.push(`/search/${query.value}`);
};
</script>

View file

@ -45,14 +45,17 @@ const instance = props.instance ?? {
const commonNames = new Map<string, string>([
["birdsitelive", "BirdsiteLIVE"],
["bookwyrm", "BookWyrm"],
["bridgy-fed", "Bridgy Fed"],
["foundkey", "FoundKey"],
["gnusocial", "GNU social"],
["gnusocial", "GNU Social"],
["gotosocial", "GoToSocial"],
["microblogpub", "microblog.pub"],
["nextcloud social", "Nextcloud Social"],
["peertube", "PeerTube"],
["snac", "snac"],
["snac2", "snac2"],
["takahe", "Takahē"],
["wafrn", "WAFRN"],
["wordpress", "WordPress"],
["writefreely", "WriteFreely"],
["wxwclub", "wxwClub"],

View file

@ -53,6 +53,7 @@
:aria-label="media.comment"
preload="none"
controls
playsinline
@contextmenu.stop
>
<source :src="media.url" :type="mediaType" />

View file

@ -106,31 +106,26 @@
:text="'@namekuji@firefish.social (Backend)'"
/></FormLink>
<FormLink to="/@dev@post.naskya.net"
><Mfm :text="'@dev@post.naskya.net (Backend)'"
><Mfm :text="'@dev@post.naskya.net (Fullstack)'"
/></FormLink>
<FormLink to="/@panos@firefish.social"
><Mfm
:text="'@panos@firefish.social (Project Coordinator)'"
/></FormLink>
<FormLink
to="https://www.youtube.com/c/Henkiwashere"
external
>Henki (error images artist)</FormLink
>
<FormLink to="/@blackspike@mastodon.cloud"
><Mfm
:text="'@blackspike@mastodon.cloud (Logo Design)'"
/></FormLink>
</div>
<template #caption
><MkLink
url="https://git.joinfirefish.org/firefish/firefish/activity"
>{{
i18n.ts._aboutFirefish.allContributors
}}</MkLink
></template
<h3
style="
font-weight: 700;
margin: 1.5em 0 16px;
font-size: 1em;
"
>
</FormSection>
<FormSection>
<template #label>{{
i18n.ts._aboutFirefish.originalMisskeyContributors
}}</template>
{{ i18n.ts._aboutFirefish.misskeyContributors }}
</h3>
<div class="_formLinks">
<FormLink to="/@syuilo@misskey.io"
><Mfm :text="'@syuilo@misskey.io'"
@ -151,6 +146,12 @@
><Mfm :text="'@robflop@misskey.io'"
/></FormLink>
</div>
<h3>
<MkLink
url="https://git.joinfirefish.org/firefish/firefish/activity"
>{{ i18n.ts._aboutFirefish.allContributors }}
</MkLink>
</h3>
</FormSection>
<FormSection>
<template #label

View file

@ -218,6 +218,7 @@ definePageMetadata(
> .users {
> .inputs {
display: flex;
gap: 0.4rem;
margin-bottom: 16px;
> * {

View file

@ -5,14 +5,15 @@
/></template>
<MkSpacer :content-max="800">
<MkPagination
ref="paginationEl"
v-slot="{ items }"
:pagination="pagination"
class="ruryvtyk _content"
class="ruryvtyk _gaps_m"
>
<section
v-for="(announcement, i) in items"
v-for="announcement in items"
:key="announcement.id"
class="_card announcement"
class="announcement _panel"
>
<div class="_title">
<span v-if="$i && !announcement.isRead">🆕 </span>
@ -31,7 +32,7 @@
/>
</div>
<div v-if="$i && !announcement.isRead" class="_footer">
<MkButton primary @click="read(items, announcement, i)"
<MkButton primary @click="read(announcement.id)"
><i class="ph-check ph-bold ph-lg"></i>
{{ i18n.ts.gotIt }}</MkButton
>
@ -43,7 +44,7 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import { ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue";
import * as os from "@/os";
@ -55,13 +56,14 @@ const pagination = {
limit: 10,
};
// TODO:
function read(items, announcement, i) {
items[i] = {
...announcement,
isRead: true,
};
os.api("i/read-announcement", { announcementId: announcement.id });
const paginationEl = ref<InstanceType<typeof MkPagination>>();
function read(id: string) {
if (!paginationEl.value) return;
paginationEl.value.updateItem(id, (announcement) => {
announcement.isRead = true;
return announcement;
});
os.api("i/read-announcement", { announcementId: id });
}
const headerActions = $computed(() => []);

View file

@ -19,21 +19,21 @@ import { i18n } from "@/i18n";
const paginationForLocal = {
endpoint: "notes/featured" as const,
limit: 10,
limit: 15,
origin: "local",
offsetMode: true,
params: {
days: 14,
days: 5,
},
};
const paginationForRemote = {
endpoint: "notes/featured" as const,
limit: 20,
limit: 15,
offsetMode: true,
params: {
origin: "remote",
days: 7,
days: 5,
},
};

View file

@ -14,7 +14,7 @@
:threshold="10"
:centeredSlides="true"
:modules="[Virtual]"
:space-between="20"
:space-between="30"
:virtual="true"
:allow-touch-move="
defaultStore.state.swipeOnMobile &&

View file

@ -130,6 +130,12 @@
@update:modelValue="save()"
>{{ i18n.ts.keepCw }}</FormSwitch
>
<FormSwitch
v-model="addRe"
class="_formBlock"
@update:modelValue="save()"
>{{ i18n.ts.addRe }}
</FormSwitch>
</div>
</template>
@ -164,6 +170,7 @@ let rememberNoteVisibility = $computed(
defaultStore.makeGetterSetter("rememberNoteVisibility"),
);
let keepCw = $computed(defaultStore.makeGetterSetter("keepCw"));
let addRe = $computed(defaultStore.makeGetterSetter("addRe"));
function save() {
os.api("i/update", {

View file

@ -127,8 +127,8 @@
</FormFolder>
<template #caption>{{
i18n.t("_profile.metadataDescription", {
a: "\<code\>\<a\>\</code\>",
l: "\<code\>\<a\>\</code\>",
a: "\<a\>",
l: "\<a\>",
rel: `rel="me" href="https://${host}/@${$i.username}"`,
})
}}</template>

View file

@ -219,7 +219,7 @@
{{ i18n.ts.sendModMail }}</FormButton
>
<FormButton
v-if="$i.isAdmin"
v-if="user.host == null && $i.isAdmin"
inline
danger
@click="delete2fa"
@ -227,7 +227,7 @@
{{ i18n.ts.delete2fa }}</FormButton
>
<FormButton
v-if="$i.isAdmin"
v-if="user.host == null && $i.isAdmin"
inline
danger
@click="deletePasskeys"

View file

@ -87,17 +87,13 @@
v-if="user.isAdmin"
v-tooltip.noDelay="i18n.ts.isAdmin"
style="color: var(--badge)"
><i
class="ph-bookmark-simple ph-fill ph-lg"
></i
><i class="ph-crown ph-bold ph-lg"></i
></span>
<span
v-if="!user.isAdmin && user.isModerator"
v-tooltip.noDelay="i18n.ts.isModerator"
style="color: var(--badge)"
><i
class="ph-bookmark-simple ph-bold ph-lg"
></i
><i class="ph-gavel ph-bold ph-lg"></i
></span>
<span
v-if="user.isLocked"
@ -176,9 +172,7 @@
v-if="user.isAdmin"
v-tooltip.noDelay="i18n.ts.isAdmin"
style="color: var(--badge)"
><i
class="ph-bookmark-simple ph-fill ph-lg"
></i
><i class="ph-crown ph-bold ph-lg"></i
></span>
<span
v-if="!user.isAdmin && user.isModerator"
@ -187,9 +181,7 @@
color: var(--badge);
margin-left: 0.5rem;
"
><i
class="ph-bookmark-simple ph-bold ph-lg"
></i
><i class="ph-gavel ph-bold ph-lg"></i
></span>
<span
v-if="user.isLocked"

View file

@ -46,6 +46,8 @@ html {
tab-size: 2;
scroll-padding: 60px;
overflow-x: clip;
text-size-adjust: none;
-webkit-text-size-adjust: none;
&.useCJKFont {
font-family: "Hiragino Maru Gothic Pro", "BIZ UDGothic", Roboto,

View file

@ -6,6 +6,7 @@
"types": "./built/index.d.ts",
"scripts": {
"build": "pnpm swc src -d built -D",
"build:debug": "pnpm swc src -d built -s -D",
"render": "pnpm run build && pnpm run api && pnpm run api-prod && cp temp/firefish-js.api.json etc/ && pnpm run api-doc",
"tsd": "tsc && tsd",
"api": "pnpm api-extractor run --local --verbose",

View file

@ -5,6 +5,7 @@
"typings": "./lib/src/index.d.ts",
"scripts": {
"build": "tsc -p ./",
"build:debug": "pnpm run build",
"lint": "pnpm rome check **/*.ts --apply",
"format": "pnpm rome format --write src/**/*.ts",
"doc": "typedoc --out ../docs ./src",

View file

@ -3,6 +3,7 @@
"private": true,
"scripts": {
"build": "webpack",
"build:debug": "pnpm run build",
"watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check **/*.ts --apply",
"format": "pnpm rome format * --write"

View file

@ -1,13 +1,13 @@
{
"patrons": [
"@atomicpoet@calckey.social",
"@atomicpoet@firefish.social",
"@shoq@mastodon.social",
"@pikadude@erisly.social",
"@sage@stop.voring.me",
"@sky@therian.club",
"@panos@electricrequiem.com",
"@redhunt07@www.foxyhole.io",
"@griff@calckey.social",
"@griff@firefish.social",
"@cafkafk@ck.cafkafk.com",
"@privateger@plasmatrap.com",
"@effye@toot.thoughtworks.com",
@ -17,56 +17,56 @@
"@topher@mastodon.online",
"@hanicef@stop.voring.me",
"@nmkj@calckey.jp",
"@unattributed@calckey.social",
"@unattributed@firefish.social",
"@cody@misskey.codingneko.com",
"@kate@blahaj.zone",
"@emtk@mkkey.net",
"@jovikowi@calckey.social",
"@padraig@calckey.social",
"@jovikowi@firefish.social",
"@padraig@firefish.social",
"@pancakes@cats.city",
"@theresmiling@calckey.social",
"@kristian@calckey.social",
"@theresmiling@firefish.social",
"@kristian@firefish.social",
"@jo@blahaj.zone",
"@narF@calckey.social",
"@narF@firefish.social",
"@AlderForrest@raining.anvil.top",
"@box464@calckey.social",
"@MariaTheMartian@calckey.social",
"@nisemikol@calckey.social",
"@box464@firefish.social",
"@MariaTheMartian@firefish.social",
"@nisemikol@firefish.social",
"@smallpatatas@blahaj.zone",
"@bayra@stop.voring.me",
"@frost@wolfdo.gg",
"@joebiden@fuckgov.org",
"@nyaa@calckey.social",
"@Dan@calckey.social",
"@dana@calckey.social",
"@Jdreben@calckey.social",
"@nyaa@firefish.social",
"@Dan@firefish.social",
"@dana@firefish.social",
"@Jdreben@firefish.social",
"@natalie@prismst.one",
"@KelsonV@wandering.shop",
"@breakfastmtn@calckey.social",
"@breakfastmtn@firefish.social",
"@richardazia@mastodon.social",
"@joestone@calckey.social",
"@aj@calckey.social",
"@joestone@firefish.social",
"@aj@firefish.social",
"@zepfanman@ramblingreaders.org",
"@kimby@stop.voring.me",
"@fyrfli@fyrfli.social",
"@riversidebryan@firefish.lgbt",
"@aRubes@sloth.run",
"@andreasdotorg@calckey.social",
"@andreasdotorg@firefish.social",
"@ozzy@calckey.online",
"@leni@windycity.style",
"@mhzmodels@calckey.art",
"@ReflexVE@calckey.social",
"@mark@calckey.social",
"@ReflexVE@firefish.social",
"@mark@firefish.social",
"@skyizwhite@himagine.club",
"@Uwu@calckey.social",
"@jGoose@calckey.social",
"@Uwu@firefish.social",
"@jGoose@firefish.social",
"@kunev@blewsky.social",
"@Simoto@electricrequiem.com",
"@Evoterra@calckey.social",
"@Evoterra@firefish.social",
"@LauraLangdon@procial.tchncs.de",
"@mho@social.heise.de",
"@richardazia@calckey.social",
"@blues653@calckey.social",
"@richardazia@firefish.social",
"@blues653@firefish.social",
"@rafale_blue@calc.04.si",
"@esm@lethallava.land",
"@vmstan@vmst.io",
@ -74,33 +74,33 @@
"@renere@distance.blue",
"@theking@kitsunes.club",
"@toof@fedi.toofie.net",
"@Punko@calckey.social",
"@joesbrat67@calckey.social",
"@arth@calckey.social",
"@Punko@firefish.social",
"@joesbrat67@firefish.social",
"@arth@firefish.social",
"@octofloofy@ck.octofloofy.ink",
"@pauliehedron@infosec.town",
"@soulthunk@lethallava.land",
"@bumble@ibe.social",
"@DarrenNevares@calckey.social",
"@irfan@calckey.social",
"@DarrenNevares@firefish.social",
"@irfan@firefish.social",
"@dvd@dvd.chat",
"@charlie2alpha@electricrequiem.com",
"@arndot@layer8.space",
"@ryan@c.ryanccn.dev",
"@lapastora_deprova@calckey.social",
"@rameez@calckey.social",
"@lapastora_deprova@firefish.social",
"@rameez@firefish.social",
"@dracoling@firetribe.org",
"@Space6host@calckey.social",
"@Space6host@firefish.social",
"@zakalwe@plasmatrap.com",
"@seasicksailor@calckey.social",
"@geerue@calckey.social",
"@seasicksailor@firefish.social",
"@geerue@firefish.social",
"@WXFanatic@m.ai6yr.org",
"@Hunkabilly@calckey.world",
"@samleegray@calckey.social",
"@samleegray@firefish.social",
"@schwarzewald@kodow.net",
"@Conatusprinciple@calckey.social",
"@Conatusprinciple@firefish.social",
"@183231bcb@firefish.lgbt",
"@wiase@calckey.social",
"@wiase@firefish.social",
"@leonieke@vitaulium.nl",
"@soulfire@wackywolf.xyz",
"@elbullazul@pub.elbullazul.com",
@ -110,8 +110,8 @@
"@hryggrbyr@ibe.social"
],
"sponsors": [
"@atomicpoet@calckey.social",
"@unattributed@calckey.social",
"@atomicpoet@firefish.social",
"@unattributed@firefish.social",
"@jtbennett@noc.social",
"\nInterkosmos Link"
]