Merge branch 'develop' into refactor/antennas-in-cache

This commit is contained in:
naskya 2023-07-17 04:43:03 +00:00
commit 04c43ed3ef
320 changed files with 14505 additions and 9038 deletions

13
.config/LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -121,7 +121,7 @@ redis:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────
# Maximum length of a post (default 3000, max 8192) # Maximum length of a post (default 3000, max 100000)
#maxNoteLength: 3000 #maxNoteLength: 3000
# Maximum length of an image caption (default 1500, max 8192) # Maximum length of an image caption (default 1500, max 8192)

1
.gitignore vendored
View file

@ -25,6 +25,7 @@ coverage
!/.config/devenv.yml !/.config/devenv.yml
!/.config/docker_example.env !/.config/docker_example.env
!/.config/helm_values_example.yml !/.config/helm_values_example.yml
!/.config/LICENSE
#docker dev config #docker dev config
/dev/docker-compose.yml /dev/docker-compose.yml

View file

@ -6,19 +6,13 @@
## Planned ## Planned
- Stucture - Stucture
- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative
- Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes
- Rewrite backend in Rust and [Rocket](https://rocket.rs/) - Rewrite backend in Rust and [Rocket](https://rocket.rs/)
- Use [Magic RegExP](https://regexp.dev/) for RegEx 🦄
- Function - Function
- User "choices" (recommended users) and featured hashtags like Mastodon and Soapbox - User "choices" (recommended users) and featured hashtags like Mastodon and Soapbox
- Join Reason system like Mastodon/Pleroma - Join Reason system like Mastodon/Pleroma
- Option to publicize server blocks - Option to publicize server blocks
- More antenna options - More antenna options
- Groups - Groups
- Form
- Lookup/details for post/file/server
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
## Work in progress ## Work in progress
@ -30,6 +24,7 @@
- Timeline filters - Timeline filters
- Events - Events
- Fully revamp non-logged-in screen - Fully revamp non-logged-in screen
- Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes
## Implemented ## Implemented
@ -122,6 +117,7 @@
- Let moderators see moderation nodes - Let moderators see moderation nodes
- Non-mangled unicode emojis - Non-mangled unicode emojis
- Skin tone selection support - Skin tone selection support
- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative
## Implemented (remote) ## Implemented (remote)
@ -137,7 +133,7 @@
- 👍 also triggers generic like/favorite - 👍 also triggers generic like/favorite
- [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671) - [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671)
- [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549) - [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549)
- Many changes from [Foundkey](https://akkoma.dev/FoundKeyGang/Foundkey) - Many changes from [FoundKey](https://akkoma.dev/FoundKeyGang/FoundKey)
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API - https://akkoma.dev/FoundKeyGang/FoundKey/commit/0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins - https://akkoma.dev/FoundKeyGang/FoundKey/commit/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes - https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes

View file

@ -2704,7 +2704,7 @@ Co-committed-by: naskya <naskya@noreply.codeberg.org>
Passwords will be automatically re-hashed on sign-in. All new password hashes will be argon2 by default. This uses argon2id and is not configurable. In the very unlikely case someone has more specific needs, a fork is recommended. ChangeLog: Added Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net> Passwords will be automatically re-hashed on sign-in. All new password hashes will be argon2 by default. This uses argon2id and is not configurable. In the very unlikely case someone has more specific needs, a fork is recommended. ChangeLog: Added Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net>
Breaks Calckey -> Misskey migration, but fixes Foundkey -> Calckey migration Breaks Calckey -> Misskey migration, but fixes FoundKey -> Calckey migration
- Add argon - Add argon

27
COPYING
View file

@ -1,15 +1,24 @@
Unless otherwise stated this repository is Unless specified otherwise, the entirety of this repository is subject to the following:
Copyright © 2014-2022 syuilo and contributers Copyright © 2014-2023 syuilo and contributors
Copyright © 2022 thatonecalculator and contributers Copyright © 2022-2023 Kainoa Kanter and contributors
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
---
Calckey includes several third-party Open-Source softwares. These specific configuration directories:
Emoji keywords for Unicode 11 and below by Mu-An Chiou - .config/
License: MIT - custom/assets/
https://github.com/muan/emojilib/blob/master/LICENSE
and their contents are
Copyright © 2022-2023 Kainoa Kanter and contributors
And are distributed under The Apache License, Version 2.0, you should have received a copy of the license file as LICENSE in each specified directory.
---
Calckey includes several third-party open-source softwares and software libraries.
RsaSignature2017 implementation by Transmute Industries Inc RsaSignature2017 implementation by Transmute Industries Inc
License: MIT License: MIT
@ -18,3 +27,7 @@ https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
Machine learning model for sensitive images by Infinite Red, Inc. Machine learning model for sensitive images by Infinite Red, Inc.
License: MIT License: MIT
https://github.com/infinitered/nsfwjs/blob/master/LICENSE https://github.com/infinitered/nsfwjs/blob/master/LICENSE
Licenses for all softwares and software libraries installed via the Node Package Manager ("npm") can be found by running the following shell command in the root directory of this repository:
pnpm licenses list

View file

@ -72,6 +72,14 @@
# 🌠 Getting started # 🌠 Getting started
Want to just join a Calckey server? View the list here, pick one, and join:
### https://calckey.org/join
---
Want to make your own? Keep reading!
This guide will work for both **starting from scratch** and **migrating from Misskey**. This guide will work for both **starting from scratch** and **migrating from Misskey**.
## 🔰 Easy installers ## 🔰 Easy installers
@ -208,9 +216,9 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
- Edit `.config/default.yml`, making sure to fill out required fields. - Edit `.config/default.yml`, making sure to fill out required fields.
- Also copy and edit `.config/docker_example.env` to `.config/docker.env` if you're using Docker. - Also copy and edit `.config/docker_example.env` to `.config/docker.env` if you're using Docker.
## 🚚 Migrating from Misskey to Calckey ## 🚚 Migrating from Misskey/FoundKey to Calckey
For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](https://codeberg.org/calckey/calckey/src/branch/develop/docs/migrate.md). For migrating from Misskey v13, Misskey v12, and FoundKey, read [this document](https://codeberg.org/calckey/calckey/src/branch/develop/docs/migrate.md).
## 🌐 Web proxy ## 🌐 Web proxy

13
custom/assets/LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,10 +1,11 @@
# 🚚 Migrating from Misskey to Calckey # 🚚 Migrating from Misskey/FoundKey to Calckey
The following procedure may not work depending on your environment and version of Misskey. All the guides below assume you're starting in the root of the repo directory.
**Make sure you** ### Before proceeding
- **stopped all master and worker processes of Misskey.**
- **have backups of the database before performing any commands.** - **Ensure you have stopped all master and worker processes of Misskey.**
- **Ensure you have backups of the database before performing any commands.**
## Misskey v13 and above ## Misskey v13 and above
@ -77,15 +78,16 @@ NODE_ENV=production pnpm run migrate
# build using prefered method # build using prefered method
``` ```
## Foundkey ## FoundKey
```sh ```sh
cd packages/backend cd packages/backend
sed -i '12s/^/\/\//' ./migration/1663399074403-resize-comments-drive-file.js
LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n uniformThemecolor1652859567549 | cut -d ':' -f 1)" LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n uniformThemecolor1652859567549 | cut -d ':' -f 1)"
NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)" NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | wc -l)"
for i in $(seq 1 $NUM_MIGRAIONS); do for i in $(seq 1 $NUM_MIGRATIONS); do
npx typeorm migration:revert -d ormconfig.js npx typeorm migration:revert -d ormconfig.js
done done
@ -100,4 +102,4 @@ NODE_ENV=production pnpm run migrate
## Reverse ## Reverse
You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to Foundkey, though. You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to FoundKey, although this is not recommended due to FoundKey being end-of-life.

View file

@ -95,7 +95,7 @@ privacy: "Privadesa"
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació" makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
defaultNoteVisibility: "Visibilitat per defecte" defaultNoteVisibility: "Visibilitat per defecte"
follow: "Segueix" follow: "Segueix"
followRequest: "Segueix" followRequest: "Sol·licitud de Seguiment"
followRequests: "Sol·licituds de seguiment" followRequests: "Sol·licituds de seguiment"
unfollow: "Deixa de seguir" unfollow: "Deixa de seguir"
followRequestPending: "Sol·licituds de seguiment pendents" followRequestPending: "Sol·licituds de seguiment pendents"
@ -1382,7 +1382,7 @@ adminCustomCssWarn: Aquesta configuració només s'ha d'utilitzar si sabeu què
showUpdates: Mostra una finestra emergent quan Calckey s'actualitzi showUpdates: Mostra una finestra emergent quan Calckey s'actualitzi
recommendedInstances: Servidors recomanats recommendedInstances: Servidors recomanats
recommendedInstancesDescription: Servidors recomanats separats per salts de línia recommendedInstancesDescription: Servidors recomanats separats per salts de línia
que apareixen a la línia de temps recomanada. NO afegiu `https://`, NOMÉS el domini. que apareixen a la línia de temps recomanada.
caption: Descripció Automàtica caption: Descripció Automàtica
splash: Pantalla de Benvinguda splash: Pantalla de Benvinguda
swipeOnDesktop: Permet lliscar a l'estil del mòbil a l'escriptori swipeOnDesktop: Permet lliscar a l'estil del mòbil a l'escriptori
@ -1603,6 +1603,13 @@ _aboutMisskey:
patrons: Mecenes de Calckey patrons: Mecenes de Calckey
patronsList: Llistats cronològicament, no per la quantitat donada. Fes una donació patronsList: Llistats cronològicament, no per la quantitat donada. Fes una donació
amb l'enllaç de dalt per veure el teu nom aquí! amb l'enllaç de dalt per veure el teu nom aquí!
donateTitle: T'agrada Calckey?
pleaseDonateToCalckey: Penseu en fer una donació a Calckey per donar suport al seu
desenvolupament.
pleaseDonateToHost: Penseu també en fer una donació a la vostre instància, {host},
per ajudar-lo a suportar els costos de funcionament.
donateHost: Fes una donació a {host}
sponsors: Patrocinadors de Calckey
unknown: Desconegut unknown: Desconegut
pageLikesCount: Nombre de pàgines amb M'agrada pageLikesCount: Nombre de pàgines amb M'agrada
youAreRunningUpToDateClient: Estás fent servir la versió del client més nova. youAreRunningUpToDateClient: Estás fent servir la versió del client més nova.
@ -2144,3 +2151,13 @@ _skinTones:
swipeOnMobile: Permet lliscar entre pàgines swipeOnMobile: Permet lliscar entre pàgines
enableIdenticonGeneration: Habilitar la generació d'Identicon enableIdenticonGeneration: Habilitar la generació d'Identicon
enableServerMachineStats: Habilitar les estadístiques del maquinari del servidor enableServerMachineStats: Habilitar les estadístiques del maquinari del servidor
showPopup: Notificar els usuaris amb una finestra emergent
showWithSparkles: Mostra amb espurnes
youHaveUnreadAnnouncements: Tens anuncis sense llegir
xl: XL
donationLink: Enllaç a la pàgina de donacions
neverShow: No tornis a mostrar
remindMeLater: Potser després
removeMember: Elimina el membre
removeQuote: Elimina la cita
removeRecipient: Elimina el destinatari

View file

@ -105,7 +105,7 @@ privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval" makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility" defaultNoteVisibility: "Default visibility"
follow: "Follow" follow: "Follow"
followRequest: "Follow" followRequest: "Follow Request"
followRequests: "Follow requests" followRequests: "Follow requests"
unfollow: "Unfollow" unfollow: "Unfollow"
followRequestPending: "Follow request pending" followRequestPending: "Follow request pending"
@ -644,6 +644,7 @@ useBlurEffectForModal: "Use blur effect for modals"
useFullReactionPicker: "Use full-size reaction picker" useFullReactionPicker: "Use full-size reaction picker"
width: "Width" width: "Width"
height: "Height" height: "Height"
xl: "XL"
large: "Big" large: "Big"
medium: "Medium" medium: "Medium"
small: "Small" small: "Small"
@ -1049,7 +1050,7 @@ customSplashIconsDescription: "URLs for custom splash screen icons separated by
showUpdates: "Show a popup when Calckey updates" showUpdates: "Show a popup when Calckey updates"
recommendedInstances: "Recommended servers" recommendedInstances: "Recommended servers"
recommendedInstancesDescription: "Recommended servers separated by line breaks to recommendedInstancesDescription: "Recommended servers separated by line breaks to
appear in the recommended timeline. Do NOT add `https://`, ONLY the domain." appear in the recommended timeline."
caption: "Auto Caption" caption: "Auto Caption"
splash: "Splash Screen" splash: "Splash Screen"
updateAvailable: "There might be an update available!" updateAvailable: "There might be an update available!"
@ -1117,6 +1118,12 @@ enableIdenticonGeneration: "Enable Identicon generation"
showPopup: "Notify users with popup" showPopup: "Notify users with popup"
showWithSparkles: "Show with sparkles" showWithSparkles: "Show with sparkles"
youHaveUnreadAnnouncements: "You have unread announcements" youHaveUnreadAnnouncements: "You have unread announcements"
donationLink: "Link to donation page"
neverShow: "Don't show again"
remindMeLater: "Maybe later"
removeQuote: "Remove quote"
removeRecipient: "Remove recipient"
removeMember: "Remove member"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing
@ -1215,8 +1222,13 @@ _aboutMisskey:
source: "Source code" source: "Source code"
translation: "Translate Calckey" translation: "Translate Calckey"
donate: "Donate to Calckey" donate: "Donate to Calckey"
donateTitle: "Enjoying Calckey?"
pleaseDonateToCalckey: "Please consider donating to Calckey to support its development."
pleaseDonateToHost: "Please also consider donating to your home server, {host}, to help support its operation costs."
donateHost: "Donate to {host}"
morePatrons: "We also appreciate the support of many other helpers not listed here. morePatrons: "We also appreciate the support of many other helpers not listed here.
Thank you! 🥰" Thank you! 🥰"
sponsors: "Calckey sponsors"
patrons: "Calckey patrons" patrons: "Calckey patrons"
patronsList: "Listed chronologically, not by donation size. Donate with the link above to get your name on here!" patronsList: "Listed chronologically, not by donation size. Donate with the link above to get your name on here!"
_nsfw: _nsfw:

View file

@ -642,7 +642,7 @@ wordMute: "Silenciar palabras"
regexpError: "Error de la expresión regular" regexpError: "Error de la expresión regular"
regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line} regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line}
de las palabras muteadas {tab}" de las palabras muteadas {tab}"
instanceMute: "Instancias silenciadas" instanceMute: "Servidores silenciados"
userSaysSomething: "{name} dijo algo" userSaysSomething: "{name} dijo algo"
makeActive: "Activar" makeActive: "Activar"
display: "Apariencia" display: "Apariencia"
@ -671,14 +671,14 @@ sample: "Muestra"
abuseReports: "Reportes" abuseReports: "Reportes"
reportAbuse: "Reportar" reportAbuse: "Reportar"
reportAbuseOf: "Reportar a {name}" reportAbuseOf: "Reportar a {name}"
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una publicación
particular, ingrese la URL de esta." en particular, ingrese la URL de esta."
abuseReported: "Se ha enviado el reporte. Muchas gracias." abuseReported: "Se ha enviado el reporte. Muchas gracias."
reporter: "Reportador" reporter: "Reportador"
reporteeOrigin: "Reportar a" reporteeOrigin: "Reportar a"
reporterOrigin: "Origen del reporte" reporterOrigin: "Origen del reporte"
forwardReport: "Transferir un informe a una instancia remota" forwardReport: "Transferir reporte a un servidor remoto"
forwardReportIsAnonymous: "No puede ver su información de la instancia remota y aparecerá forwardReportIsAnonymous: "No puede ver su información del servidor remoto y aparecerá
como una cuenta anónima del sistema" como una cuenta anónima del sistema"
send: "Enviar" send: "Enviar"
abuseMarkAsResolved: "Marcar reporte como resuelto" abuseMarkAsResolved: "Marcar reporte como resuelto"
@ -686,7 +686,7 @@ openInNewTab: "Abrir en una Nueva Pestaña"
openInSideView: "Abrir en una vista al costado" openInSideView: "Abrir en una vista al costado"
defaultNavigationBehaviour: "Navegación por defecto" defaultNavigationBehaviour: "Navegación por defecto"
editTheseSettingsMayBreakAccount: "Editar estas configuraciones puede dañar su cuenta." editTheseSettingsMayBreakAccount: "Editar estas configuraciones puede dañar su cuenta."
instanceTicker: "Información de notas de la instancia" instanceTicker: "Información de publicaciones de el servidor"
waitingFor: "Esperando a {x}" waitingFor: "Esperando a {x}"
random: "Aleatorio" random: "Aleatorio"
system: "Sistema" system: "Sistema"
@ -697,14 +697,14 @@ createNew: "Crear"
optional: "Opcional" optional: "Opcional"
createNewClip: "Crear clip nuevo" createNewClip: "Crear clip nuevo"
unclip: "Quitar clip" unclip: "Quitar clip"
confirmToUnclipAlreadyClippedNote: "Esta nota ya está incluida en el clip \"{name}\"\ confirmToUnclipAlreadyClippedNote: "Esta publicación ya está incluida en el clip \"\
. ¿Quiere quitar la nota del clip?" {name}\". ¿Quiere quitar la nota del clip?"
public: "Público" public: "Público"
i18nInfo: "Calckey está siendo traducido a varios idiomas gracias a voluntarios. Se i18nInfo: "Calckey está siendo traducido a varios idiomas gracias a voluntarios. Se
puede colaborar traduciendo en {link}" puede colaborar traduciendo en {link}"
manageAccessTokens: "Administrar tokens de acceso" manageAccessTokens: "Administrar tokens de acceso"
accountInfo: "Información de la Cuenta" accountInfo: "Información de la Cuenta"
notesCount: "Cantidad de notas" notesCount: "Cantidad de publicaciones"
repliesCount: "Cantidad de respuestas hechas" repliesCount: "Cantidad de respuestas hechas"
renotesCount: "Cantidad de renotas hechas" renotesCount: "Cantidad de renotas hechas"
repliedCount: "Cantidad de respuestas recibidas" repliedCount: "Cantidad de respuestas recibidas"
@ -720,7 +720,7 @@ no: "No"
driveFilesCount: "Cantidad de archivos en el drive" driveFilesCount: "Cantidad de archivos en el drive"
driveUsage: "Uso del drive" driveUsage: "Uso del drive"
noCrawle: "Rechazar indexación del crawler" noCrawle: "Rechazar indexación del crawler"
noCrawleDescription: "Pedir a los motores de búsqueda que no indexen tu perfil, notas, noCrawleDescription: "Pedir a los motores de búsqueda que no indexen tu perfil, publicaciones,
páginas, etc." páginas, etc."
lockedAccountInfo: "A menos que configures la visibilidad de tus notas como \"Sólo 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 seguidores\", tus notas serán visibles para cualquiera, incluso si requieres que
@ -734,7 +734,7 @@ verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación
configuración." configuración."
notSet: "Sin especificar" notSet: "Sin especificar"
emailVerified: "Su dirección de correo electrónico ha sido verificada." emailVerified: "Su dirección de correo electrónico ha sido verificada."
noteFavoritesCount: "Número de notas favoritas" noteFavoritesCount: "Número de publicaciones favoritas"
pageLikesCount: "Número de favoritos en la página" pageLikesCount: "Número de favoritos en la página"
pageLikedCount: "Número de favoritos de su página" pageLikedCount: "Número de favoritos de su página"
contact: "Contacto" contact: "Contacto"
@ -975,7 +975,7 @@ shuffle: "Aleatorio"
account: "Cuentas" account: "Cuentas"
move: "Mover" move: "Mover"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduce el esfuerzo de la moderación el el servidor a través del reconocimiento description: "Reduce el esfuerzo de la moderación de el servidor a través del reconocimiento
automático de contenido NSFW usando 'Machine Learning'. Esto puede incrementar automático de contenido NSFW usando 'Machine Learning'. Esto puede incrementar
ligeramente la carga en el servidor." ligeramente la carga en el servidor."
sensitivity: "Sensibilidad de detección" sensitivity: "Sensibilidad de detección"
@ -1295,7 +1295,7 @@ _time:
_tutorial: _tutorial:
title: "Cómo usar Calckey" title: "Cómo usar Calckey"
step1_1: "¡Bienvenido!" step1_1: "¡Bienvenido!"
step1_2: "Vamos a configurarte. Estarás listo y funcionando en poco tiempo" step1_2: "Vamos a configurarte. ¡Estarás listo y funcionando en poco tiempo!"
step2_1: "En primer lugar, rellena tu perfil" 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 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 notas o seguirte."
@ -1789,7 +1789,7 @@ _pages:
splitStrByLine: "Separar texto en lineas" splitStrByLine: "Separar texto en lineas"
_splitStrByLine: _splitStrByLine:
arg1: "Texto" arg1: "Texto"
ref: "Variables" ref: "Variable"
aiScriptVar: "Variable de AiScript" aiScriptVar: "Variable de AiScript"
fn: "funciones" fn: "funciones"
_fn: _fn:
@ -1800,8 +1800,8 @@ _pages:
_for: _for:
arg1: "Cantidad de repeticiones" arg1: "Cantidad de repeticiones"
arg2: "Acción" arg2: "Acción"
typeError: "El slot {slot} acepta el tipo {expect} pero fue ingresado el tipo typeError: "El slot {slot} acepta el tipo \"{expect}\" pero fue ingresado el tipo
{actual}" \"{actual}\""
thereIsEmptySlot: "El slot {slot} está vacío" thereIsEmptySlot: "El slot {slot} está vacío"
types: types:
string: "Texto" string: "Texto"

View file

@ -1952,8 +1952,7 @@ antennaInstancesDescription: Lister un hôte d'instance par ligne
userSaysSomethingReason: '{name} a dit {reason}' userSaysSomethingReason: '{name} a dit {reason}'
breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ? breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ?
recommendedInstancesDescription: Instances recommandées séparées par une nouvelle recommendedInstancesDescription: Instances recommandées séparées par une nouvelle
ligne pour apparaître dans la timeline recommandée. Ne PAS ajouter `https://`, SEULEMENT ligne pour apparaître dans la timeline recommandée.
le domaine.
sendPushNotificationReadMessage: Supprimer les notifications push une fois que les sendPushNotificationReadMessage: Supprimer les notifications push une fois que les
notifications ou messages concernés ont été lus notifications ou messages concernés ont été lus
sendPushNotificationReadMessageCaption: Une notification contenant le texte "{emptyPushNotificationMessage}" sendPushNotificationReadMessageCaption: Une notification contenant le texte "{emptyPushNotificationMessage}"

17
locales/gl.yml Normal file
View file

@ -0,0 +1,17 @@
_lang_: Inglés
introMisskey: Benvida! Calckey é unha plataforma de medios sociais de código aberto,
descentralizada e gratuíta para sempre!🚀
monthAndDay: '{day}/{month}'
notifications: Notificacións
password: Contrasinal
forgotPassword: Esquecín o contrasinal
gotIt: Vale!
cancel: Cancelar
noThankYou: Non, grazas
headlineMisskey: Plataforma de medios sociais de código aberto e descentralizada,
gratuíta para sempre!🚀
search: Buscar
searchPlaceholder: Buscar en Calckey
username: Identificador
fetchingAsApObject: Descargando desde o Fediverso
ok: OK

View file

@ -946,7 +946,7 @@ customSplashIconsDescription: "ユーザがページをロード/リロードす
URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。" URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。"
showUpdates: "Calckeyの更新時にポップアップを表示する" showUpdates: "Calckeyの更新時にポップアップを表示する"
recommendedInstances: "おすすめサーバー" recommendedInstances: "おすすめサーバー"
recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。`https://`は書かず、ドメインのみを入力してください。" recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。"
caption: "自動キャプション" caption: "自動キャプション"
splash: "スプラッシュスクリーン" splash: "スプラッシュスクリーン"
updateAvailable: "アップデートがありますよ!" updateAvailable: "アップデートがありますよ!"
@ -983,6 +983,8 @@ enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にす
showPopup: "ポップアップを表示してユーザーに知らせる" showPopup: "ポップアップを表示してユーザーに知らせる"
showWithSparkles: "タイトルをキラキラさせる" showWithSparkles: "タイトルをキラキラさせる"
youHaveUnreadAnnouncements: "未読のお知らせがあります" youHaveUnreadAnnouncements: "未読のお知らせがあります"
neverShow: "今後表示しない"
remindMeLater: "また後で"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
@ -1068,6 +1070,10 @@ _aboutMisskey:
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰" morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰"
patrons: "支援者" patrons: "支援者"
patronsList: 寄付額ではなく時系列順に並んでいます。上記のリンクから寄付を行ってここにあなたのIDを載せましょう patronsList: 寄付額ではなく時系列順に並んでいます。上記のリンクから寄付を行ってここにあなたのIDを載せましょう
pleaseDonateToCalckey: Calckey開発への寄付をご検討ください。
pleaseDonateToHost: また、このサーバー {host} の運営者への寄付もご検討ください。
donateHost: '{host} に寄付する'
donateTitle: Calckeyを気に入りましたか
_nsfw: _nsfw:
respect: "閲覧注意のメディアは隠す" respect: "閲覧注意のメディアは隠す"
ignore: "閲覧注意のメディアを隠さない" ignore: "閲覧注意のメディアを隠さない"
@ -1948,3 +1954,8 @@ removeReaction: リアクションを取り消す
alt: 代替テキスト alt: 代替テキスト
swipeOnMobile: ページ間のスワイプを有効にする swipeOnMobile: ページ間のスワイプを有効にする
reactionPickerSkinTone: 優先する絵文字のスキン色 reactionPickerSkinTone: 優先する絵文字のスキン色
xl: 特大
donationLink: 寄付ページへのリンク
removeMember: メンバーを削除
removeQuote: 引用を削除
removeRecipient: 宛先を削除

View file

@ -1,2 +1,83 @@
---
_lang_: "Norsk Bokmål" _lang_: "Norsk Bokmål"
search: Søk
monthAndDay: '{day}/{month}'
fetchingAsApObject: Henter fra fediverset
ok: OK
gotIt: Jeg forstår!
profile: Profil
timeline: Tidslinje
save: Lagre
addToList: Legg til liste
searchPlaceholder: Søk Calckey
username: Brukernavn
password: Passord
notifications: Meldinger
forgotPassword: Glemt passord
cancel: Avbryt
noNotes: Ingen poster
instance: Server
settings: Innstillinger
noAccountDescription: Denne brukeren har ikke fylt ut bio'en sin ennå.
login: Logg inn
loggingIn: Logger inn
signup: Oppretter bruker
uploading: Laster opp..
enterUsername: Skriv inn brukernavn
noNotifications: Ingen meldinger
users: Brukere
addUser: Legg til en bruker
favorite: Legg til i bokmerker
cantFavorite: Kunne ikke legges til i bokmerker.
pin: Fest til profilen
copyContent: Kopier innhold
deleteAndEdit: Slett og rediger
sendMessage: Send en melding
copyUsername: Kopier brukernavn
reply: Svar
loadMore: Last mer
showLess: Lukk
receiveFollowRequest: Følgeforespørsel mottatt
directNotes: Direktemelding
importAndExport: Importer/eksporter data
importRequested: Du har bedt om en importering. Dette vil ta litt tid.
lists: Lister
listsDesc: Lister lar deg lage tidslinjer med utvalgte brukere. De kan hentes frem
fra tidslinje-siden.
deleted: Slettet
editNote: Rediger notat
followsYou: Følger deg
createList: Lag liste
newer: nyere
older: eldre
download: Last ned
unfollowConfirm: Er du sikker på at du ikke lenger vil følge {name}?
noLists: Du har ingen lister
following: Følger
files: Filer
note: Post
notes: Poster
followers: Følgere
otherSettings: Andre innstillinger
addInstance: Legg til en server
alreadyFavorited: Allerede lagt til i bokmerker.
delete: Slett
openInWindow: Åpne i vindu
basicSettings: Grunnleggende innstillinger
headlineMisskey: En desentralisert sosialt media-plattform, basert på åpen kildekode,
som alltid vil være gratis! 🚀
introMisskey: Velkommen! Calckey er en desentralisert sosialt media-plattform, basert
på åpen kildekode, som alltid vil være gratis! 🚀
exportRequested: Du har bedt om en eksportering. Dette vil ta litt tid. Den vil bli
lagt til på disken din når den er ferdig.
noThankYou: Nei takk
favorites: Bokmerker
unfavorite: Fjern fra bokmerker
favorited: Lagt til i bokmerker.
copyLink: Kopier lenke
searchUser: Søk etter en bruker
jumpToPrevious: Gå til foregående
showMore: Vis mer
followRequestAccepted: Følgeforespørsel godtatt
import: Importer
export: Eksporter
logout: Logger ut

View file

@ -85,3 +85,28 @@ noLists: Você não possui nenhuma lista
following: Seguindo following: Seguindo
followers: Seguidores followers: Seguidores
followsYou: Segue você followsYou: Segue você
fetchingAsApObject: Buscando do Fediverse
timeline: Linha do tempo
favorite: Adicionar aos marcadores
favorites: Marcadores
unfavorite: Remover dos marcadores
favorited: Adicionado aos marcadores.
alreadyFavorited: Já foi adicionado aos marcadores.
download: Download
pageLoadError: Ocorreu um erro ao carregar a página.
pageLoadErrorDescription: Isso normalmente é causado por erros de rede ou pelo cache
do navegador. Tente limpar o cache e, depois de esperar um pouquinho, tente novamente.
serverIsDead: Esse servidos não está respondendo. Por favor espere um pouco e tente
novamente.
youShouldUpgradeClient: Para visualizar essa página, favor reiniciar para atualizar
seu cliente.
enterListName: Insira um nome para a lista
privacy: Privacidade
defaultNoteVisibility: Visibilidade padrão
makeFollowManuallyApprove: Pedidos de seguimento precisam de aprovação
follow: Seguir
followRequest: Seguir
followRequests: Pedidos de seguimento
unfollow: Parar de seguir
followRequestPending: Pedido de seguimento pendente
enterEmoji: Insira um emoji

View file

@ -1847,7 +1847,7 @@ customMOTDDescription: Пользовательские сообщения дл
разрывами строк, будут отображаться случайным образом каждый раз, когда пользователь разрывами строк, будут отображаться случайным образом каждый раз, когда пользователь
загружает / перезагружает страницу. загружает / перезагружает страницу.
recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк, recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк,
должны отображаться на рекомендуемой ленте. НЕ добавляйте `https://`, ТОЛЬКО домен. должны отображаться на рекомендуемой ленте.
caption: Автоматическая подпись caption: Автоматическая подпись
splash: Заставка splash: Заставка
updateAvailable: Возможно, доступно обновление! updateAvailable: Возможно, доступно обновление!

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1839,7 +1839,7 @@ pushNotification: 推送通知
subscribePushNotification: 啟用推送通知 subscribePushNotification: 啟用推送通知
unsubscribePushNotification: 禁用推送通知 unsubscribePushNotification: 禁用推送通知
pushNotificationAlreadySubscribed: 推送通知已經啟用 pushNotificationAlreadySubscribed: 推送通知已經啟用
recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中。 不要添加 `https://`,只添加域名。 recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中。
searchPlaceholder: 在聯邦網路上搜尋 searchPlaceholder: 在聯邦網路上搜尋
cw: 內容警告 cw: 內容警告
selectChannel: 選擇一個頻道 selectChannel: 選擇一個頻道

View file

@ -1,16 +1,16 @@
{ {
"name": "calckey", "name": "calckey",
"version": "14.0.0-rc3", "version": "14.0.0-dev79",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://codeberg.org/calckey/calckey.git" "url": "https://codeberg.org/calckey/calckey.git"
}, },
"packageManager": "pnpm@8.6.6", "packageManager": "pnpm@8.6.7",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp", "rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"build": "pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp", "build": "pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start", "start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test", "start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate", "init": "pnpm run migrate",
@ -21,13 +21,13 @@
"watch": "pnpm run dev", "watch": "pnpm run dev",
"dev": "pnpm node ./scripts/dev.js", "dev": "pnpm node ./scripts/dev.js",
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start", "dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
"lint": "pnpm -r run lint", "lint": "pnpm -r --parallel run lint",
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run", "cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha", "mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha", "test": "pnpm run mocha",
"format": "pnpm -r run format", "format": "pnpm -r --parallel run format",
"clean": "pnpm node ./scripts/clean.js", "clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js", "clean-all": "pnpm node ./scripts/clean-all.js",
"cleanall": "pnpm run clean-all" "cleanall": "pnpm run clean-all"
@ -36,17 +36,17 @@
"chokidar": "^3.3.1" "chokidar": "^3.3.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.2.0", "@bull-board/api": "5.6.0",
"@bull-board/ui": "5.2.0", "@bull-board/ui": "5.6.0",
"@napi-rs/cli": "^2.16.1", "@napi-rs/cli": "^2.16.1",
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^3.21.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.11.18", "@types/gulp": "4.0.13",
"@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.2",
"@types/gulp-rename": "2.0.1", "@types/node": "20.4.1",
"chalk": "4.1.2", "chalk": "4.1.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
@ -57,8 +57,8 @@
"gulp-replace": "1.1.4", "gulp-replace": "1.1.4",
"gulp-terser": "2.1.0", "gulp-terser": "2.1.0",
"install-peers": "^1.0.4", "install-peers": "^1.0.4",
"rome": "^12.1.3", "rome": "^v12.1.3-nightly.f65b0d9",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"typescript": "4.9.4" "typescript": "5.1.6"
} }
} }

View file

@ -0,0 +1,16 @@
export class tweakVarcharLength1678426061773 {
name = "tweakVarcharLength1678426061773";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`,
undefined,
);
await queryRunner.query(
`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`,
undefined,
);
}
async down(queryRunner) {}
}

View file

@ -0,0 +1,15 @@
export class DonationLink1689136347561 {
name = "DonationLink1689136347561";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ADD "donationLink" character varying(256)`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "DonationLink1689136347561"`,
);
}
}

View file

@ -43,6 +43,7 @@
"universal": "napi universal", "universal": "napi universal",
"version": "napi version", "version": "napi version",
"format": "cargo fmt --all", "format": "cargo fmt --all",
"lint": "cargo clippy --fix",
"cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration", "cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration",
"cargo:unit": "cargo test unit_test && cargo test -F napi unit_test", "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
"cargo:integration": "cargo test -F noarray int_test -- --test-threads=1" "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"

View file

@ -40,7 +40,7 @@ impl Repository<Antenna> for antenna::Model {
src: self.src.try_into()?, src: self.src.try_into()?,
user_list_id: self.user_list_id, user_list_id: self.user_list_id,
user_group_id, user_group_id,
users: self.users.into(), users: self.users,
instances: self.instances.into(), instances: self.instances.into(),
case_sensitive: self.case_sensitive, case_sensitive: self.case_sensitive,
notify: self.notify, notify: self.notify,

View file

@ -58,7 +58,7 @@ impl TryFrom<AntennaSrcEnum> for super::AntennaSrc {
// ---- TODO: could be macro // ---- TODO: could be macro
impl Schema<Self> for super::Antenna {} impl Schema<Self> for super::Antenna {}
pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| super::Antenna::validator()); pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(super::Antenna::validator);
// ---- // ----
cfg_if! { cfg_if! {

View file

@ -91,7 +91,7 @@ pub enum AppPermission {
impl Schema<Self> for App {} impl Schema<Self> for App {}
pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| App::validator()); pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(App::validator);
#[cfg(test)] #[cfg(test)]
mod unit_test { mod unit_test {

View file

@ -148,8 +148,8 @@ async fn setup_model(db: &DbConn) {
let user_model = entity::user::Model { let user_model = entity::user::Model {
id: user_id.to_owned(), id: user_id.to_owned(),
created_at: Utc::now().into(), created_at: Utc::now().into(),
username: name.to_lowercase().to_string(), username: name.to_lowercase(),
username_lower: name.to_lowercase().to_string(), username_lower: name.to_lowercase(),
name: Some(name.to_string()), name: Some(name.to_string()),
token: Some(gen_string(16)), token: Some(gen_string(16)),
is_admin: true, is_admin: true,

View file

@ -43,18 +43,16 @@ mod int_test {
keywords: vec![ keywords: vec![
vec!["foo".to_string(), "bar".to_string()], vec!["foo".to_string(), "bar".to_string()],
vec!["foobar".to_string()], vec!["foobar".to_string()],
] ],
.into(),
exclude_keywords: vec![ exclude_keywords: vec![
vec!["abc".to_string()], vec!["abc".to_string()],
vec!["def".to_string(), "ghi".to_string()], vec!["def".to_string(), "ghi".to_string()],
] ],
.into(),
src: schema::AntennaSrc::All, src: schema::AntennaSrc::All,
user_list_id: None, user_list_id: None,
user_group_id: None, user_group_id: None,
users: vec![].into(), users: vec![],
instances: vec![].into(), instances: vec![],
case_sensitive: true, case_sensitive: true,
notify: true, notify: true,
with_replies: false, with_replies: false,

View file

@ -25,16 +25,16 @@
"@tensorflow/tfjs-node": "3.21.1" "@tensorflow/tfjs-node": "3.21.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.2.0", "@bull-board/api": "5.6.0",
"@bull-board/koa": "5.2.0", "@bull-board/koa": "5.6.0",
"@bull-board/ui": "5.2.0", "@bull-board/ui": "5.6.0",
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0", "@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3", "@koa/cors": "3.4.3",
"@koa/multer": "3.0.2", "@koa/multer": "3.0.2",
"@koa/router": "9.0.1", "@koa/router": "9.0.1",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.0.0-beta.120", "@redocly/openapi-core": "1.0.0-beta.131",
"@sinonjs/fake-timers": "9.1.2", "@sinonjs/fake-timers": "9.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0", "@tensorflow/tfjs": "^4.2.0",
@ -42,20 +42,19 @@
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"autobind-decorator": "2.4.0",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1277.0", "aws-sdk": "2.1413.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.5", "blurhash": "2.0.5",
"bull": "4.10.4", "bull": "4.10.4",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"cbor": "8.1.0", "cbor": "8.1.0",
"chalk": "5.2.0", "chalk": "5.3.0",
"chalk-template": "0.4.0", "chalk-template": "0.4.0",
"chokidar": "3.5.3", "chokidar": "^3.5.3",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
"color-convert": "2.0.1", "color-convert": "2.0.1",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
@ -68,15 +67,16 @@
"got": "12.5.3", "got": "12.5.3",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.0.11", "ip-cidr": "3.1.0",
"is-svg": "4.3.2", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "20.0.3", "jsdom": "20.0.3",
"json5": "2.2.3",
"jsonld": "8.2.0", "jsonld": "8.2.0",
"jsrsasign": "10.6.1", "jsrsasign": "10.8.6",
"koa": "2.13.4", "koa": "2.14.2",
"koa-body": "^6.0.1", "koa-body": "^6.0.1",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.4.1",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
"koa-json-body": "5.3.0", "koa-json-body": "5.3.0",
"koa-logger": "3.2.1", "koa-logger": "3.2.1",
@ -98,9 +98,9 @@
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "^0.10.0", "oauth": "^0.10.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "^9.1.2", "otpauth": "^9.1.3",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.11.0", "pg": "8.11.1",
"private-ip": "2.3.4", "private-ip": "2.3.4",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@ -110,7 +110,7 @@
"qs": "6.11.2", "qs": "6.11.2",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.19.0", "re2": "1.19.1",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"redis-semaphore": "5.3.1", "redis-semaphore": "5.3.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@ -119,7 +119,7 @@
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"semver": "7.5.1", "semver": "7.5.4",
"sharp": "0.32.1", "sharp": "0.32.1",
"sonic-channel": "^1.3.1", "sonic-channel": "^1.3.1",
"stringz": "2.1.0", "stringz": "2.1.0",
@ -130,27 +130,26 @@
"tinycolor2": "1.5.2", "tinycolor2": "1.5.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.17",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"web-push": "3.6.1", "web-push": "3.6.3",
"websocket": "1.0.34", "websocket": "1.0.34",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62", "@swc/core": "^1.3.68",
"@types/adm-zip": "^0.5.0", "@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.9",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.20", "@types/fluent-ffmpeg": "2.1.21",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "20.0.1", "@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.4", "@types/jsrsasign": "10.5.8",
"@types/koa": "2.13.5", "@types/koa": "2.13.6",
"@types/koa-bodyparser": "4.3.10", "@types/koa-bodyparser": "4.3.10",
"@types/koa-cors": "0.0.2", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
@ -169,7 +168,7 @@
"@types/probe-image-size": "^7.2.0", "@types/probe-image-size": "^7.2.0",
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.1",
"@types/qs": "6.9.7", "@types/qs": "6.9.7",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4", "@types/ratelimiter": "3.4.4",
@ -177,29 +176,26 @@
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"@types/uuid": "8.3.4", "@types/uuid": "9.0.2",
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.5",
"autobind-decorator": "2.4.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "^8.42.0", "eslint": "^8.44.0",
"execa": "6.1.0", "execa": "6.1.0",
"json5": "2.2.3",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"mocha": "10.2.0", "mocha": "10.2.0",
"pug": "3.0.2", "pug": "3.0.2",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"swc-loader": "^0.2.3", "swc-loader": "^0.2.3",
"ts-loader": "9.4.3", "ts-loader": "9.4.4",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.1.3", "typescript": "5.1.6",
"webpack": "^5.85.1", "webpack": "^5.88.1",
"ws": "8.13.0" "ws": "8.13.0"
} }
} }

View file

@ -1,8 +1,13 @@
import config from "@/config/index.js"; import config from "@/config/index.js";
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; import {
DB_MAX_NOTE_TEXT_LENGTH,
DB_MAX_IMAGE_COMMENT_LENGTH,
} from "@/misc/hard-limits.js";
export const MAX_NOTE_TEXT_LENGTH = export const MAX_NOTE_TEXT_LENGTH = Math.min(
config.maxNoteLength != null ? config.maxNoteLength : 3000; // <- should we increase this? config.maxNoteLength ?? 3000,
DB_MAX_NOTE_TEXT_LENGTH,
);
export const MAX_CAPTION_TEXT_LENGTH = Math.min( export const MAX_CAPTION_TEXT_LENGTH = Math.min(
config.maxCaptionLength ?? 1500, config.maxCaptionLength ?? 1500,
DB_MAX_IMAGE_COMMENT_LENGTH, DB_MAX_IMAGE_COMMENT_LENGTH,

View file

@ -4,7 +4,7 @@ export type Acct = {
}; };
export function parse(acct: string): Acct { export function parse(acct: string): Acct {
if (acct.startsWith("@")) acct = acct.substr(1); if (acct.startsWith("@")) acct = acct.slice(1);
const split = acct.split("@", 2); const split = acct.split("@", 2);
return { username: split[0], host: split[1] || null }; return { username: split[0], host: split[1] || null };
} }

View file

@ -24,6 +24,7 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
.stream(url, { .stream(url, {
headers: { headers: {
"User-Agent": config.userAgent, "User-Agent": config.userAgent,
Host: new URL(url).hostname,
}, },
timeout: { timeout: {
lookup: timeout, lookup: timeout,

View file

@ -3,8 +3,13 @@
/** /**
* Maximum note text length that can be stored in DB. * Maximum note text length that can be stored in DB.
* Surrogate pairs count as one * Surrogate pairs count as one
*
* NOTE: this can hypothetically be pushed further
* (up to 250000000), but will likely cause truncations
* and incompatibilities with other servers,
* as well as potential performance issues.
*/ */
export const DB_MAX_NOTE_TEXT_LENGTH = 8192; export const DB_MAX_NOTE_TEXT_LENGTH = 100000;
/** /**
* Maximum image description length that can be stored in DB. * Maximum image description length that can be stored in DB.

View file

@ -20,5 +20,9 @@ export function nyaize(text: string): string {
) )
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥") .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥")
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥") .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")
// el-GR
.replaceAll("να", "νια")
.replaceAll("ΝΑ", "ΝΙΑ")
.replaceAll("Να", "Νια")
); );
} }

View file

@ -326,13 +326,13 @@ export class Meta {
public smtpPort: number | null; public smtpPort: number | null;
@Column("varchar", { @Column("varchar", {
length: 128, length: 1024,
nullable: true, nullable: true,
}) })
public smtpUser: string | null; public smtpUser: string | null;
@Column("varchar", { @Column("varchar", {
length: 128, length: 1024,
nullable: true, nullable: true,
}) })
public smtpPass: string | null; public smtpPass: string | null;
@ -556,4 +556,10 @@ export class Meta {
default: true, default: true,
}) })
public enableIdenticonGeneration: boolean; public enableIdenticonGeneration: boolean;
@Column("varchar", {
length: 256,
nullable: true,
})
public donationLink: string | null;
} }

View file

@ -453,6 +453,7 @@ export const UserRepository = db.getRepository(User).extend({
isAdmin: user.isAdmin || falsy, isAdmin: user.isAdmin || falsy,
isModerator: user.isModerator || falsy, isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy, isBot: user.isBot || falsy,
isLocked: user.isLocked,
isCat: user.isCat || falsy, isCat: user.isCat || falsy,
speakAsCat: user.speakAsCat || falsy, speakAsCat: user.speakAsCat || falsy,
instance: user.host instance: user.host
@ -497,7 +498,6 @@ export const UserRepository = db.getRepository(User).extend({
: null, : null,
bannerBlurhash: user.banner?.blurhash || null, bannerBlurhash: user.banner?.blurhash || null,
bannerColor: null, // 後方互換性のため bannerColor: null, // 後方互換性のため
isLocked: user.isLocked,
isSilenced: user.isSilenced || falsy, isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy, isSuspended: user.isSuspended || falsy,
description: profile!.description, description: profile!.description,

View file

@ -1,4 +1,5 @@
import type Bull from "bull"; import type Bull from "bull";
import type { DoneCallback } from "bull";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "../../logger.js";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
@ -11,7 +12,7 @@ const logger = queueLogger.createSubLogger("index-all-notes");
export default async function indexAllNotes( export default async function indexAllNotes(
job: Bull.Job<Record<string, unknown>>, job: Bull.Job<Record<string, unknown>>,
done: () => void, done: DoneCallback,
): Promise<void> { ): Promise<void> {
logger.info("Indexing all notes..."); logger.info("Indexing all notes...");
@ -20,7 +21,7 @@ export default async function indexAllNotes(
let total: number = (job.data.total as number) ?? 0; let total: number = (job.data.total as number) ?? 0;
let running = true; let running = true;
const take = 100000; const take = 10000;
const batch = 100; const batch = 100;
while (running) { while (running) {
logger.info( logger.info(
@ -41,13 +42,14 @@ export default async function indexAllNotes(
}, },
relations: ["user"], relations: ["user"],
}); });
} catch (e) { } catch (e: any) {
logger.error(`Failed to query notes ${e}`); logger.error(`Failed to query notes ${e}`);
continue; done(e);
break;
} }
if (notes.length === 0) { if (notes.length === 0) {
job.progress(100); await job.progress(100);
running = false; running = false;
break; break;
} }
@ -55,7 +57,7 @@ export default async function indexAllNotes(
try { try {
const count = await Notes.count(); const count = await Notes.count();
total = count; total = count;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
} catch (e) {} } catch (e) {}
for (let i = 0; i < notes.length; i += batch) { for (let i = 0; i < notes.length; i += batch) {
@ -69,12 +71,12 @@ export default async function indexAllNotes(
indexedCount += chunk.length; indexedCount += chunk.length;
const pct = (indexedCount / total) * 100; const pct = (indexedCount / total) * 100;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
job.progress(+pct.toFixed(1)); await job.progress(+pct.toFixed(1));
logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`); logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
} }
cursor = notes[notes.length - 1].id; cursor = notes[notes.length - 1].id;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
if (notes.length < take) { if (notes.length < take) {
running = false; running = false;

View file

@ -4,7 +4,7 @@ import * as fs from "node:fs";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "../../logger.js";
import { addFile } from "@/services/drive/add-file.js"; import { addFile } from "@/services/drive/add-file.js";
import { format as dateFormat } from "date-fns"; import { format as dateFormat } from "date-fns";
import { Users, Notes, Polls } from "@/models/index.js"; import { Users, Notes, Polls, DriveFiles } from "@/models/index.js";
import { MoreThan } from "typeorm"; import { MoreThan } from "typeorm";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import type { Poll } from "@/models/entities/poll.js"; import type { Poll } from "@/models/entities/poll.js";
@ -75,7 +75,7 @@ export async function exportNotes(
if (note.hasPoll) { if (note.hasPoll) {
poll = await Polls.findOneByOrFail({ noteId: note.id }); poll = await Polls.findOneByOrFail({ noteId: note.id });
} }
const content = JSON.stringify(serialize(note, poll)); const content = JSON.stringify(await serialize(note, poll));
const isFirst = exportedNotesCount === 0; const isFirst = exportedNotesCount === 0;
await write(isFirst ? content : ",\n" + content); await write(isFirst ? content : ",\n" + content);
exportedNotesCount++; exportedNotesCount++;
@ -112,15 +112,16 @@ export async function exportNotes(
done(); done();
} }
function serialize( async function serialize(
note: Note, note: Note,
poll: Poll | null = null, poll: Poll | null = null,
): Record<string, unknown> { ): Promise<Record<string, unknown>> {
return { return {
id: note.id, id: note.id,
text: note.text, text: note.text,
createdAt: note.createdAt, createdAt: note.createdAt,
fileIds: note.fileIds, fileIds: note.fileIds,
files: await DriveFiles.packMany(note.fileIds),
replyId: note.replyId, replyId: note.replyId,
renoteId: note.renoteId, renoteId: note.renoteId,
poll: poll, poll: poll,

View file

@ -3,6 +3,8 @@ import create from "@/services/note/create.js";
import { Users } from "@/models/index.js"; import { Users } from "@/models/index.js";
import type { DbUserImportMastoPostJobData } from "@/queue/types.js"; import type { DbUserImportMastoPostJobData } from "@/queue/types.js";
import { queueLogger } from "../../logger.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 type Bull from "bull";
const logger = queueLogger.createSubLogger("import-calckey-post"); const logger = queueLogger.createSubLogger("import-calckey-post");
@ -29,10 +31,25 @@ export async function importCkPost(
done(); done();
return; return;
} }
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 } = Post.parse(post); const { text, cw, localOnly, createdAt } = Post.parse(post);
const note = await create(user, { const note = await create(user, {
createdAt: createdAt, createdAt: createdAt,
files: undefined, files: files.length == 0 ? undefined : files,
poll: undefined, poll: undefined,
text: text || undefined, text: text || undefined,
reply: null, reply: null,

View file

@ -6,6 +6,8 @@ import type Bull from "bull";
import { htmlToMfm } from "@/remote/activitypub/misc/html-to-mfm.js"; import { htmlToMfm } from "@/remote/activitypub/misc/html-to-mfm.js";
import { resolveNote } from "@/remote/activitypub/models/note.js"; import { resolveNote } from "@/remote/activitypub/models/note.js";
import { Note } from "@/models/entities/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";
const logger = queueLogger.createSubLogger("import-masto-post"); const logger = queueLogger.createSubLogger("import-masto-post");
@ -43,14 +45,30 @@ export async function importMastoPost(
throw e; throw e;
} }
job.progress(80); job.progress(80);
const urls = post.object.attachment
.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 note = await create(user, { const note = await create(user, {
createdAt: new Date(post.object.published), createdAt: new Date(post.object.published),
files: undefined, files: files.length == 0 ? undefined : files,
poll: undefined, poll: undefined,
text: text || undefined, text: text || undefined,
reply, reply,
renote: null, renote: null,
cw: post.sensitive, cw: post.object.sensitive ? post.object.summary : undefined,
localOnly: false, localOnly: false,
visibility: "hidden", visibility: "hidden",
visibleUsers: [], visibleUsers: [],

View file

@ -147,11 +147,11 @@ export async function fetchPerson(
} }
//#region Returns if already registered with this server //#region Returns if already registered with this server
const exist = await Users.findOneBy({ uri }); const user = await Users.findOneBy({ uri });
if (exist) { if (user != null) {
await uriPersonCache.set(uri, exist); await uriPersonCache.set(uri, user);
return exist; return user;
} }
//#endregion //#endregion
@ -396,9 +396,9 @@ export async function updatePerson(
} }
//#region Already registered on this server? //#region Already registered on this server?
const exist = (await Users.findOneBy({ uri })) as IRemoteUser; const user = (await Users.findOneBy({ uri })) as IRemoteUser;
if (exist == null) { if (user == null) {
return; return;
} }
//#endregion //#endregion
@ -416,17 +416,15 @@ export async function updatePerson(
[person.icon, person.image].map((img) => [person.icon, person.image].map((img) =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(exist, img).catch(() => null), : resolveImage(user, img).catch(() => null),
), ),
); );
// Custom pictogram acquisition // Custom pictogram acquisition
const emojis = await extractEmojis(person.tag || [], exist.host).catch( const emojis = await extractEmojis(person.tag || [], user.host).catch((e) => {
(e) => { logger.info(`extractEmojis: ${e}`);
logger.info(`extractEmojis: ${e}`); return [] as Emoji[];
return [] as Emoji[]; });
},
);
const emojiNames = emojis.map((emoji) => emoji.name); const emojiNames = emojis.map((emoji) => emoji.name);
@ -518,11 +516,11 @@ export async function updatePerson(
} }
// Update user // Update user
await Users.update(exist.id, updates); await Users.update(user.id, updates);
if (person.publicKey) { if (person.publicKey) {
await UserPublickeys.update( await UserPublickeys.update(
{ userId: exist.id }, { userId: user.id },
{ {
keyId: person.publicKey.id, keyId: person.publicKey.id,
keyPem: person.publicKey.publicKeyPem, keyPem: person.publicKey.publicKeyPem,
@ -531,7 +529,7 @@ export async function updatePerson(
} }
await UserProfiles.update( await UserProfiles.update(
{ userId: exist.id }, { userId: user.id },
{ {
url: url, url: url,
fields, fields,
@ -543,15 +541,15 @@ export async function updatePerson(
}, },
); );
publishInternalEvent("remoteUserUpdated", { id: exist.id }); publishInternalEvent("remoteUserUpdated", { id: user.id });
// Hashtag Update // Hashtag Update
updateUsertags(exist, tags); updateUsertags(user, tags);
// If the user in question is a follower, followers will also be updated. // If the user in question is a follower, followers will also be updated.
await Followings.update( await Followings.update(
{ {
followerId: exist.id, followerId: user.id,
}, },
{ {
followerSharedInbox: followerSharedInbox:
@ -560,7 +558,7 @@ export async function updatePerson(
}, },
); );
await updateFeatured(exist.id, resolver).catch((err) => logger.error(err)); await updateFeatured(user.id, resolver).catch((err) => logger.error(err));
} }
/** /**
@ -576,10 +574,10 @@ export async function resolvePerson(
if (typeof uri !== "string") throw new Error("uri is not string"); if (typeof uri !== "string") throw new Error("uri is not string");
//#region If already registered on this server, return it. //#region If already registered on this server, return it.
const exist = await fetchPerson(uri); const user = await fetchPerson(uri);
if (exist) { if (user != null) {
return exist; return user;
} }
//#endregion //#endregion

View file

@ -491,6 +491,11 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
donationLink: {
type: "string",
optional: true,
nullable: true,
},
}, },
}, },
} as const; } as const;
@ -604,5 +609,6 @@ export default define(meta, paramDef, async (ps, me) => {
experimentalFeatures: instance.experimentalFeatures, experimentalFeatures: instance.experimentalFeatures,
enableServerMachineStats: instance.enableServerMachineStats, enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration, enableIdenticonGeneration: instance.enableIdenticonGeneration,
donationLink: instance.donationLink,
}; };
}); });

View file

@ -40,9 +40,9 @@ export default define(meta, paramDef, async (ps, user) => {
throw err; throw err;
}); });
const exist = await PromoNotes.findOneBy({ noteId: note.id }); const exist = await PromoNotes.exist({ where: { noteId: note.id } });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyPromoted); throw new ApiError(meta.errors.alreadyPromoted);
} }

View file

@ -1,6 +1,5 @@
import { Meta } from "@/models/entities/meta.js"; import { Meta } from "@/models/entities/meta.js";
import { insertModerationLog } from "@/services/insert-moderation-log.js"; import { insertModerationLog } from "@/services/insert-moderation-log.js";
import { DB_MAX_NOTE_TEXT_LENGTH } from "@/misc/hard-limits.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import define from "../../define.js"; import define from "../../define.js";
@ -177,6 +176,9 @@ export const paramDef = {
postImports: { type: "boolean" }, postImports: { type: "boolean" },
}, },
}, },
enableServerMachineStats: { type: "boolean" },
enableIdenticonGeneration: { type: "boolean" },
donationLink: { type: "string", nullable: true },
}, },
required: [], required: [],
} as const; } as const;
@ -218,6 +220,15 @@ export default define(meta, paramDef, async (ps, me) => {
if (Array.isArray(ps.recommendedInstances)) { if (Array.isArray(ps.recommendedInstances)) {
set.recommendedInstances = ps.recommendedInstances.filter(Boolean); set.recommendedInstances = ps.recommendedInstances.filter(Boolean);
if (set.recommendedInstances?.length > 0) {
set.recommendedInstances.forEach((instance, index) => {
if (/^https?:\/\//i.test(instance)) {
set.recommendedInstances![index] = instance
.replace(/^https?:\/\//i, "")
.replace(/\/$/, "");
}
});
}
} }
if (Array.isArray(ps.hiddenTags)) { if (Array.isArray(ps.hiddenTags)) {
@ -568,6 +579,21 @@ export default define(meta, paramDef, async (ps, me) => {
set.experimentalFeatures = ps.experimentalFeatures || undefined; set.experimentalFeatures = ps.experimentalFeatures || undefined;
} }
if (ps.enableServerMachineStats !== undefined) {
set.enableServerMachineStats = ps.enableServerMachineStats;
}
if (ps.enableIdenticonGeneration !== undefined) {
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
}
if (ps.donationLink !== undefined) {
set.donationLink = ps.donationLink;
if (set.donationLink && !/^https?:\/\//i.test(set.donationLink)) {
set.donationLink = `https://${set.donationLink}`;
}
}
await db.transaction(async (transactionalEntityManager) => { await db.transaction(async (transactionalEntityManager) => {
const metas = await transactionalEntityManager.find(Meta, { const metas = await transactionalEntityManager.find(Meta, {
order: { order: {

View file

@ -41,12 +41,14 @@ export default define(meta, paramDef, async (ps, user) => {
const accessToken = secureRndstr(32, true); const accessToken = secureRndstr(32, true);
// Fetch exist access token // Fetch exist access token
const exist = await AccessTokens.findOneBy({ const exist = await AccessTokens.exist({
appId: session.appId, where: {
userId: user.id, appId: session.appId,
userId: user.id,
},
}); });
if (exist == null) { if (!exist) {
// Lookup app // Lookup app
const app = await Apps.findOneByOrFail({ id: session.appId }); const app = await Apps.findOneByOrFail({ id: session.appId });

View file

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already blocking // Check if already blocking
const exist = await Blockings.findOneBy({ const exist = await Blockings.exist({
blockerId: blocker.id, where: {
blockeeId: blockee.id, blockerId: blocker.id,
blockeeId: blockee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyBlocking); throw new ApiError(meta.errors.alreadyBlocking);
} }

View file

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not blocking // Check not blocking
const exist = await Blockings.findOneBy({ const exist = await Blockings.exist({
blockerId: blocker.id, where: {
blockeeId: blockee.id, blockerId: blocker.id,
blockeeId: blockee.id,
},
}); });
if (exist == null) { if (!exist) {
throw new ApiError(meta.errors.notBlocking); throw new ApiError(meta.errors.notBlocking);
} }

View file

@ -57,12 +57,14 @@ export default define(meta, paramDef, async (ps, user) => {
throw err; throw err;
}); });
const exist = await ClipNotes.findOneBy({ const exist = await ClipNotes.exist({
noteId: note.id, where: {
clipId: clip.id, noteId: note.id,
clipId: clip.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyClipped); throw new ApiError(meta.errors.alreadyClipped);
} }

View file

@ -26,10 +26,12 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const file = await DriveFiles.findOneBy({ const exist = await DriveFiles.exist({
md5: ps.md5, where: {
userId: user.id, md5: ps.md5,
userId: user.id,
},
}); });
return file != null; return exist;
}); });

View file

@ -82,12 +82,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already following // Check if already following
const exist = await Followings.findOneBy({ const exist = await Followings.exist({
followerId: follower.id, where: {
followeeId: followee.id, followerId: follower.id,
followeeId: followee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyFollowing); throw new ApiError(meta.errors.alreadyFollowing);
} }

View file

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not following // Check not following
const exist = await Followings.findOneBy({ const exist = await Followings.exist({
followerId: follower.id, where: {
followeeId: followee.id, followerId: follower.id,
followeeId: followee.id,
},
}); });
if (exist == null) { if (!exist) {
throw new ApiError(meta.errors.notFollowing); throw new ApiError(meta.errors.notFollowing);
} }

View file

@ -69,12 +69,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not following // Check not following
const exist = await Followings.findOneBy({ const exist = await Followings.exist({
followerId: follower.id, where: {
followeeId: followee.id, followerId: follower.id,
followeeId: followee.id,
},
}); });
if (exist == null) { if (!exist) {
throw new ApiError(meta.errors.notFollowing); throw new ApiError(meta.errors.notFollowing);
} }

View file

@ -40,12 +40,14 @@ export default define(meta, paramDef, async (ps, user) => {
} }
// if already liked // if already liked
const exist = await GalleryLikes.findOneBy({ const exist = await GalleryLikes.exist({
postId: post.id, where: {
userId: user.id, postId: post.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyLiked); throw new ApiError(meta.errors.alreadyLiked);
} }

View file

@ -38,17 +38,17 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchPost); throw new ApiError(meta.errors.noSuchPost);
} }
const exist = await GalleryLikes.findOneBy({ const like = await GalleryLikes.findOneBy({
postId: post.id, postId: post.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { if (like == null) {
throw new ApiError(meta.errors.notLiked); throw new ApiError(meta.errors.notLiked);
} }
// Delete like // Delete like
await GalleryLikes.delete(exist.id); await GalleryLikes.delete(like.id);
GalleryPosts.decrement({ id: post.id }, "likedCount", 1); GalleryPosts.decrement({ id: post.id }, "likedCount", 1);
}); });

View file

@ -30,19 +30,23 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
// Check if announcement exists // Check if announcement exists
const announcement = await Announcements.findOneBy({ id: ps.announcementId }); const exist = await Announcements.exist({
where: { id: ps.announcementId },
});
if (announcement == null) { if (!exist) {
throw new ApiError(meta.errors.noSuchAnnouncement); throw new ApiError(meta.errors.noSuchAnnouncement);
} }
// Check if already read // Check if already read
const read = await AnnouncementReads.findOneBy({ const read = await AnnouncementReads.exist({
announcementId: ps.announcementId, where: {
userId: user.id, announcementId: ps.announcementId,
userId: user.id,
},
}); });
if (read != null) { if (read) {
return; return;
} }

View file

@ -17,9 +17,9 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const token = await AccessTokens.findOneBy({ id: ps.tokenId }); const exist = await AccessTokens.exist({ where: { id: ps.tokenId } });
if (token) { if (exist) {
await AccessTokens.delete({ await AccessTokens.delete({
id: ps.tokenId, id: ps.tokenId,
userId: user.id, userId: user.id,

View file

@ -389,6 +389,11 @@ export const meta = {
nullable: false, nullable: false,
default: "⭐", default: "⭐",
}, },
donationLink: {
type: "string",
optional: "true",
nullable: true,
},
}, },
}, },
} as const; } as const;
@ -491,6 +496,7 @@ export default define(meta, paramDef, async (ps, me) => {
translatorAvailable: translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
defaultReaction: instance.defaultReaction, defaultReaction: instance.defaultReaction,
donationLink: instance.donationLink,
...(ps.detail ...(ps.detail
? { ? {

View file

@ -64,12 +64,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already muting // Check if already muting
const exist = await Mutings.findOneBy({ const exist = await Mutings.exist({
muterId: muter.id, where: {
muteeId: mutee.id, muterId: muter.id,
muteeId: mutee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyMuting); throw new ApiError(meta.errors.alreadyMuting);
} }

View file

@ -56,18 +56,18 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not muting // Check not muting
const exist = await Mutings.findOneBy({ const muting = await Mutings.findOneBy({
muterId: muter.id, muterId: muter.id,
muteeId: mutee.id, muteeId: mutee.id,
}); });
if (exist == null) { if (muting == null) {
throw new ApiError(meta.errors.notMuting); throw new ApiError(meta.errors.notMuting);
} }
// Delete mute // Delete mute
await Mutings.delete({ await Mutings.delete({
id: exist.id, id: muting.id,
}); });
publishUserEvent(user.id, "unmute", mutee); publishUserEvent(user.id, "unmute", mutee);

View file

@ -1,4 +1,3 @@
import { Brackets } from "typeorm";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
import define from "../../define.js"; import define from "../../define.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js";
@ -11,6 +10,7 @@ export const meta = {
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: true, requireCredentialPrivateMode: true,
description: "Get threaded/chained replies to a note",
res: { res: {
type: "array", type: "array",
@ -23,13 +23,14 @@ export const meta = {
ref: "Note", ref: "Note",
}, },
}, },
}; } as const;
export const paramDef = { export const paramDef = {
type: "object", type: "object",
properties: { properties: {
noteId: { type: "string", format: "misskey:id" }, noteId: { type: "string", format: "misskey:id" },
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
depth: { type: "integer", minimum: 1, maximum: 100, default: 12 },
sinceId: { type: "string", format: "misskey:id" }, sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" }, untilId: { type: "string", format: "misskey:id" },
}, },

View file

@ -9,6 +9,7 @@ export const meta = {
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: true, requireCredentialPrivateMode: true,
description: "Get conversation of a note thread/chain by a reply",
res: { res: {
type: "array", type: "array",
@ -34,7 +35,11 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: "object", type: "object",
properties: { properties: {
noteId: { type: "string", format: "misskey:id" }, noteId: {
type: "string",
format: "misskey:id",
description: "Should be a reply",
},
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
offset: { type: "integer", default: 0 }, offset: { type: "integer", default: 0 },
}, },
@ -51,7 +56,7 @@ export default define(meta, paramDef, async (ps, user) => {
const conversation: Note[] = []; const conversation: Note[] = [];
let i = 0; let i = 0;
async function get(id: any) { async function get(id: string) {
i++; i++;
const p = await getNote(id, user).catch((e) => { const p = await getNote(id, user).catch((e) => {
if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null; if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null;
@ -60,7 +65,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (p == null) return; if (p == null) return;
if (i > ps.offset!) { if (i > ps.offset) {
conversation.push(p); conversation.push(p);
} }

View file

@ -224,11 +224,13 @@ export default define(meta, paramDef, async (ps, user) => {
// Check blocking // Check blocking
if (renote.userId !== user.id) { if (renote.userId !== user.id) {
const block = await Blockings.findOneBy({ const isBlocked = await Blockings.exist({
blockerId: renote.userId, where: {
blockeeId: user.id, blockerId: renote.userId,
blockeeId: user.id,
},
}); });
if (block) { if (isBlocked) {
throw new ApiError(meta.errors.youHaveBeenBlocked); throw new ApiError(meta.errors.youHaveBeenBlocked);
} }
} }
@ -249,11 +251,13 @@ export default define(meta, paramDef, async (ps, user) => {
// Check blocking // Check blocking
if (reply.userId !== user.id) { if (reply.userId !== user.id) {
const block = await Blockings.findOneBy({ const isBlocked = await Blockings.exist({
blockerId: reply.userId, where: {
blockeeId: user.id, blockerId: reply.userId,
blockeeId: user.id,
},
}); });
if (block) { if (isBlocked) {
throw new ApiError(meta.errors.youHaveBeenBlocked); throw new ApiError(meta.errors.youHaveBeenBlocked);
} }
} }

View file

@ -43,12 +43,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// if already favorited // if already favorited
const exist = await NoteFavorites.findOneBy({ const exist = await NoteFavorites.exist({
noteId: note.id, where: {
userId: user.id, noteId: note.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyFavorited); throw new ApiError(meta.errors.alreadyFavorited);
} }

View file

@ -42,15 +42,15 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// if already favorited // if already favorited
const exist = await NoteFavorites.findOneBy({ const favorite = await NoteFavorites.findOneBy({
noteId: note.id, noteId: note.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { if (favorite == null) {
throw new ApiError(meta.errors.notFavorited); throw new ApiError(meta.errors.notFavorited);
} }
// Delete favorite // Delete favorite
await NoteFavorites.delete(exist.id); await NoteFavorites.delete(favorite.id);
}); });

View file

@ -21,6 +21,7 @@ export const meta = {
message: "No such note.", message: "No such note.",
code: "NO_SUCH_NOTE", code: "NO_SUCH_NOTE",
id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d", id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d",
httpStatusCode: 404,
}, },
}, },
} as const; } as const;

View file

@ -40,12 +40,14 @@ export default define(meta, paramDef, async (ps, user) => {
} }
// if already liked // if already liked
const exist = await PageLikes.findOneBy({ const exist = await PageLikes.exist({
pageId: page.id, where: {
userId: user.id, pageId: page.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyLiked); throw new ApiError(meta.errors.alreadyLiked);
} }

View file

@ -38,17 +38,17 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchPage); throw new ApiError(meta.errors.noSuchPage);
} }
const exist = await PageLikes.findOneBy({ const like = await PageLikes.findOneBy({
pageId: page.id, pageId: page.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { if (like == null) {
throw new ApiError(meta.errors.notLiked); throw new ApiError(meta.errors.notLiked);
} }
// Delete like // Delete like
await PageLikes.delete(exist.id); await PageLikes.delete(like.id);
Pages.decrement({ id: page.id }, "likedCount", 1); Pages.decrement({ id: page.id }, "likedCount", 1);
}); });

View file

@ -9,7 +9,7 @@ const _dirname = dirname(_filename);
export const meta = { export const meta = {
tags: ["meta"], tags: ["meta"],
description: "Get list of Calckey patrons from Codeberg", description: "Get Calckey patrons",
requireCredential: false, requireCredential: false,
requireCredentialPrivateMode: false, requireCredentialPrivateMode: false,
@ -51,6 +51,8 @@ export default define(meta, paramDef, async (ps) => {
}); });
await redisClient.set("patrons", JSON.stringify(patrons), "EX", 3600); await redisClient.set("patrons", JSON.stringify(patrons), "EX", 3600);
} }
return {
return patrons["patrons"]; patrons: patrons["patrons"],
sponsors: patrons["sponsors"],
};
}); });

View file

@ -33,12 +33,14 @@ export default define(meta, paramDef, async (ps, user) => {
throw err; throw err;
}); });
const exist = await PromoReads.findOneBy({ const exist = await PromoReads.exist({
noteId: note.id, where: {
userId: user.id, noteId: note.id,
userId: user.id,
},
}); });
if (exist != null) { if (exist) {
return; return;
} }

View file

@ -47,12 +47,14 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already muting // Check if already muting
const exist = await RenoteMutings.findOneBy({ const exist = await RenoteMutings.exist({
muterId: muter.id, where: {
muteeId: mutee.id, muterId: muter.id,
muteeId: mutee.id,
},
}); });
if (exist != null) { if (exist) {
throw new ApiError(meta.errors.alreadyMuting); throw new ApiError(meta.errors.alreadyMuting);
} }

View file

@ -45,18 +45,18 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not muting // Check not muting
const exist = await RenoteMutings.findOneBy({ const muting = await RenoteMutings.findOneBy({
muterId: muter.id, muterId: muter.id,
muteeId: mutee.id, muteeId: mutee.id,
}); });
if (exist == null) { if (muting == null) {
throw new ApiError(meta.errors.notMuting); throw new ApiError(meta.errors.notMuting);
} }
// Delete mute // Delete mute
await RenoteMutings.delete({ await RenoteMutings.delete({
id: exist.id, id: muting.id,
}); });
// publishUserEvent(user.id, "unmute", mutee); // publishUserEvent(user.id, "unmute", mutee);

View file

@ -57,8 +57,7 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
// if already subscribed const subscription = await SwSubscriptions.findOneBy({
const exist = await SwSubscriptions.findOneBy({
userId: me.id, userId: me.id,
endpoint: ps.endpoint, endpoint: ps.endpoint,
auth: ps.auth, auth: ps.auth,
@ -67,13 +66,14 @@ export default define(meta, paramDef, async (ps, me) => {
const instance = await fetchMeta(true); const instance = await fetchMeta(true);
if (exist != null) { // if already subscribed
if (subscription != null) {
return { return {
state: "already-subscribed" as const, state: "already-subscribed" as const,
key: instance.swPublicKey, key: instance.swPublicKey,
userId: me.id, userId: me.id,
endpoint: exist.endpoint, endpoint: subscription.endpoint,
sendReadMessage: exist.sendReadMessage, sendReadMessage: subscription.sendReadMessage,
}; };
} }

View file

@ -42,16 +42,16 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
const exist = await SwSubscriptions.findOneBy({ const subscription = await SwSubscriptions.findOneBy({
userId: me.id, userId: me.id,
endpoint: ps.endpoint, endpoint: ps.endpoint,
}); });
if (exist != null) { if (subscription != null) {
return { return {
userId: exist.userId, userId: subscription.userId,
endpoint: exist.endpoint, endpoint: subscription.endpoint,
sendReadMessage: exist.sendReadMessage, sendReadMessage: subscription.sendReadMessage,
}; };
} }

View file

@ -98,11 +98,13 @@ export default define(meta, paramDef, async (ps, me) => {
if (me == null) { if (me == null) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) { } else if (me.id !== user.id) {
const following = await Followings.findOneBy({ const isFollowed = await Followings.exist({
followeeId: user.id, where: {
followerId: me.id, followeeId: user.id,
followerId: me.id,
},
}); });
if (following == null) { if (!isFollowed) {
throw new ApiError(meta.errors.nullFollowers); throw new ApiError(meta.errors.nullFollowers);
} }
} }

View file

@ -97,11 +97,13 @@ export default define(meta, paramDef, async (ps, me) => {
if (me == null) { if (me == null) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) { } else if (me.id !== user.id) {
const following = await Followings.findOneBy({ const isFollowing = await Followings.exist({
followeeId: user.id, where: {
followerId: me.id, followeeId: user.id,
followerId: me.id,
},
}); });
if (following == null) { if (!isFollowing) {
throw new ApiError(meta.errors.cannot_find); throw new ApiError(meta.errors.cannot_find);
} }
} }

View file

@ -52,12 +52,14 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
// Fetch the list // Fetch the list
const userList = await UserLists.findOneBy({ const listExists = await UserLists.exist({
id: ps.listId, where: {
userId: me.id, id: ps.listId,
userId: me.id,
},
}); });
if (userList == null) { if (!listExists) {
throw new ApiError(meta.errors.noSuchList); throw new ApiError(meta.errors.noSuchList);
} }
@ -70,18 +72,22 @@ export default define(meta, paramDef, async (ps, me) => {
// Check blocking // Check blocking
if (user.id !== me.id) { if (user.id !== me.id) {
const block = await Blockings.findOneBy({ const isBlocked = await Blockings.exist({
blockerId: user.id, where: {
blockeeId: me.id, blockerId: user.id,
blockeeId: me.id,
},
}); });
if (block) { if (isBlocked) {
throw new ApiError(meta.errors.youHaveBeenBlocked); throw new ApiError(meta.errors.youHaveBeenBlocked);
} }
} }
const exist = await UserListJoinings.findOneBy({ const exist = await UserListJoinings.exist({
userListId: userList.id, where: {
userId: user.id, userListId: userList.id,
userId: user.id,
},
}); });
if (exist) { if (exist) {

View file

@ -37,12 +37,14 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
// Fetch the list // Fetch the list
const userList = await UserLists.findOneBy({ const exist = await UserLists.exist({
id: ps.listId, where: {
userId: me.id, id: ps.listId,
userId: me.id,
},
}); });
if (userList == null) { if (!exist) {
throw new ApiError(meta.errors.noSuchList); throw new ApiError(meta.errors.noSuchList);
} }

View file

@ -49,7 +49,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId }); const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId });
if (me == null || (me.id !== ps.userId && !profile.publicReactions)) { if (me.id !== ps.userId && !profile.publicReactions) {
throw new ApiError(meta.errors.reactionsNotPublic); throw new ApiError(meta.errors.reactionsNotPublic);
} }

View file

@ -1,4 +1,5 @@
import { Entity } from "megalodon"; import { Entity } from "megalodon";
import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Users, Notes } from "@/models/index.js"; import { Users, Notes } from "@/models/index.js";
import { IsNull, MoreThan } from "typeorm"; import { IsNull, MoreThan } from "typeorm";
@ -17,7 +18,7 @@ export async function getInstance(response: Entity.Instance) {
response.description || response.description ||
"This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)", "This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)",
email: response.email || "", email: response.email || "",
version: "3.0.0 compatible (3.5+ Calckey)", //I hope this version string is correct, we will need to test it. version: `3.0.0 (compatible; Calckey ${config.version})`,
urls: response.urls, urls: response.urls,
stats: { stats: {
user_count: await totalUsers, user_count: await totalUsers,

View file

@ -67,6 +67,25 @@ export function apiStatusMastodon(router: Router): void {
const { sensitive } = body; const { sensitive } = body;
body.sensitive = body.sensitive =
typeof sensitive === "string" ? sensitive === "true" : sensitive; typeof sensitive === "string" ? sensitive === "true" : sensitive;
if (body.poll) {
if (
body.poll.expires_in != null &&
typeof body.poll.expires_in === "string"
)
body.poll.expires_in = parseInt(body.poll.expires_in);
if (
body.poll.multiple != null &&
typeof body.poll.multiple === "string"
)
body.poll.multiple = body.poll.multiple == "true";
if (
body.poll.hide_totals != null &&
typeof body.poll.hide_totals === "string"
)
body.poll.hide_totals = body.poll.hide_totals == "true";
}
const data = await client.postStatus(text, body); const data = await client.postStatus(text, body);
ctx.body = convertStatus(data.data); ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
@ -86,7 +105,7 @@ export function apiStatusMastodon(router: Router): void {
ctx.body = convertStatus(data.data); ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = ctx.status == 404 ? 404 : 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
}); });

View file

@ -18,7 +18,7 @@ function getUserToken(ctx: Koa.BaseContext): string | null {
function compareOrigin(ctx: Koa.BaseContext): boolean { function compareOrigin(ctx: Koa.BaseContext): boolean {
function normalizeUrl(url?: string): string { function normalizeUrl(url?: string): string {
return url ? (url.endsWith("/") ? url.substr(0, url.length - 1) : url) : ""; return url ? (url.endsWith("/") ? url.slice(0, url.length - 1) : url) : "";
} }
const referer = ctx.headers["referer"]; const referer = ctx.headers["referer"];

View file

@ -18,7 +18,7 @@ function getUserToken(ctx: Koa.BaseContext): string | null {
function compareOrigin(ctx: Koa.BaseContext): boolean { function compareOrigin(ctx: Koa.BaseContext): boolean {
function normalizeUrl(url?: string): string { function normalizeUrl(url?: string): string {
return url ? (url.endsWith("/") ? url.substr(0, url.length - 1) : url) : ""; return url ? (url.endsWith("/") ? url.slice(0, url.length - 1) : url) : "";
} }
const referer = ctx.headers["referer"]; const referer = ctx.headers["referer"];

View file

@ -22,11 +22,13 @@ export default class extends Channel {
this.listId = params.listId as string; this.listId = params.listId as string;
// Check existence and owner // Check existence and owner
const list = await UserLists.findOneBy({ const exist = await UserLists.exist({
id: this.listId, where: {
userId: this.user!.id, id: this.listId,
userId: this.user!.id,
},
}); });
if (!list) return; if (!exist) return;
// Subscribe stream // Subscribe stream
this.subscriber.on(`userListStream:${this.listId}`, this.send); this.subscriber.on(`userListStream:${this.listId}`, this.send);

View file

@ -247,7 +247,7 @@ export default class Connection {
for (const obj of objs) { for (const obj of objs) {
const { type, body } = obj; const { type, body } = obj;
console.log(type, body); // console.log(type, body);
switch (type) { switch (type) {
case "readNotification": case "readNotification":
this.onReadNotification(body); this.onReadNotification(body);

View file

@ -1,4 +1,6 @@
import * as fs from "node:fs"; import * as fs from "node:fs";
import net from "node:net";
import { promises } from "node:dns";
import type Koa from "koa"; import type Koa from "koa";
import sharp from "sharp"; import sharp from "sharp";
import type { IImage } from "@/services/drive/image-processor.js"; import type { IImage } from "@/services/drive/image-processor.js";
@ -19,6 +21,40 @@ export async function proxyMedia(ctx: Koa.Context) {
return; return;
} }
const { hostname } = new URL(url);
let resolvedIps;
try {
resolvedIps = await promises.resolve(hostname);
} catch (error) {
ctx.status = 400;
ctx.body = { message: "Invalid URL" };
return;
}
const isSSRF = resolvedIps.some((ip) => {
if (net.isIPv4(ip)) {
const parts = ip.split(".").map(Number);
return (
parts[0] === 10 ||
(parts[0] === 172 && parts[1] >= 16 && parts[1] < 32) ||
(parts[0] === 192 && parts[1] === 168) ||
parts[0] === 127 ||
parts[0] === 0
);
} else if (net.isIPv6(ip)) {
return (
ip.startsWith("::") || ip.startsWith("fc00:") || ip.startsWith("fe80:")
);
}
return false;
});
if (isSSRF) {
ctx.status = 400;
ctx.body = { message: "Access to this URL is not allowed" };
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await createTemp(); const [path, cleanup] = await createTemp();

View file

@ -102,7 +102,11 @@
localStorage.setItem("fontSize", null); localStorage.setItem("fontSize", null);
fontSize = localStorage.getItem("fontSize"); fontSize = localStorage.getItem("fontSize");
} }
document.documentElement.style.fontSize = fontSize + "px"; document.documentElement.style.fontSize = `${fontSize}px`;
}
if (["ja-JP", "ja-KS", "ko-KR", "zh-CN", "zh-TW"].includes(lang)) {
document.documentElement.classList.add("useCJKFont");
} }
const useSystemFont = localStorage.getItem("useSystemFont"); const useSystemFont = localStorage.getItem("useSystemFont");
@ -123,7 +127,7 @@
} }
async function addStyle(styleText) { async function addStyle(styleText) {
let css = document.createElement("style"); const css = document.createElement("style");
css.appendChild(document.createTextNode(styleText)); css.appendChild(document.createTextNode(styleText));
document.head.appendChild(css); document.head.appendChild(css);
} }

View file

@ -13,13 +13,13 @@ export default async (
user: { id: User["id"]; host: User["host"] }, user: { id: User["id"]; host: User["host"] },
note: Note, note: Note,
) => { ) => {
// if already unreacted const reaction = await NoteReactions.findOneBy({
const exist = await NoteReactions.findOneBy({
noteId: note.id, noteId: note.id,
userId: user.id, userId: user.id,
}); });
if (exist == null) { // if already unreacted
if (reaction == null) {
throw new IdentifiableError( throw new IdentifiableError(
"60527ec9-b4cb-4a88-a6bd-32d3ad26817d", "60527ec9-b4cb-4a88-a6bd-32d3ad26817d",
"not reacted", "not reacted",
@ -27,7 +27,7 @@ export default async (
} }
// Delete reaction // Delete reaction
const result = await NoteReactions.delete(exist.id); const result = await NoteReactions.delete(reaction.id);
if (result.affected !== 1) { if (result.affected !== 1) {
throw new IdentifiableError( throw new IdentifiableError(
@ -37,7 +37,7 @@ export default async (
} }
// Decrement reactions count // Decrement reactions count
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`; const sql = `jsonb_set("reactions", '{${reaction.reaction}}', (COALESCE("reactions"->>'${reaction.reaction}', '0')::int - 1)::text::jsonb)`;
await Notes.createQueryBuilder() await Notes.createQueryBuilder()
.update() .update()
.set({ .set({
@ -49,14 +49,14 @@ export default async (
Notes.decrement({ id: note.id }, "score", 1); Notes.decrement({ id: note.id }, "score", 1);
publishNoteStream(note.id, "unreacted", { publishNoteStream(note.id, "unreacted", {
reaction: decodeReaction(exist.reaction).reaction, reaction: decodeReaction(reaction.reaction).reaction,
userId: user.id, userId: user.id,
}); });
//#region 配信 //#region 配信
if (Users.isLocalUser(user) && !note.localOnly) { if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity( const content = renderActivity(
renderUndo(await renderLike(exist, note), user), renderUndo(await renderLike(reaction, note), user),
); );
const dm = new DeliverManager(user, content); const dm = new DeliverManager(user, content);
if (note.userHost !== null) { if (note.userHost !== null) {

View file

@ -42,9 +42,9 @@ export async function insertNoteUnread(
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する // 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
setTimeout(async () => { setTimeout(async () => {
const exist = await NoteUnreads.findOneBy({ id: unread.id }); const exist = await NoteUnreads.exist({ where: { id: unread.id } });
if (exist == null) return; if (!exist) return;
if (params.isMentioned) { if (params.isMentioned) {
publishMainStream(userId, "unreadMention", note.id); publishMainStream(userId, "unreadMention", note.id);

View file

@ -4,7 +4,7 @@ export type Acct = {
}; };
export function parse(acct: string): Acct { export function parse(acct: string): Acct {
if (acct.startsWith("@")) acct = acct.substr(1); if (acct.startsWith("@")) acct = acct.slice(1);
const split = acct.split("@", 2); const split = acct.split("@", 2);
return { username: split[0], host: split[1] || null }; return { username: split[0], host: split[1] || null };
} }

View file

@ -0,0 +1,7 @@
{
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
"plugins": ["file-progress", "prettier"],
"rules": {
"file-progress/activate": 1
}
}

View file

@ -4,11 +4,14 @@
"scripts": { "scripts": {
"watch": "pnpm vite build --watch --mode development", "watch": "pnpm vite build --watch --mode development",
"build": "pnpm vite build", "build": "pnpm vite build",
"lint": "pnpm rome check \"src/**/*.{ts,vue}\"", "lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}'" "lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
}, },
"devDependencies": { "devDependencies": {
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@eslint-sets/eslint-config-vue3": "^5.6.1",
"@eslint-sets/eslint-config-vue3-ts": "^3.3.0",
"@phosphor-icons/web": "^2.0.3", "@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "3.1.9", "@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0", "@rollup/plugin-json": "4.1.0",
@ -16,7 +19,7 @@
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
"@types/gulp": "4.0.11", "@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2", "@types/gulp-rename": "2.0.2",
"@types/katex": "0.16.0", "@types/katex": "0.16.0",
"@types/matter-js": "0.18.2", "@types/matter-js": "0.18.2",
@ -29,8 +32,8 @@
"@vue/compiler-sfc": "3.3.4", "@vue/compiler-sfc": "3.3.4",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "5.0.2", "autosize": "5.0.2",
"blurhash": "1.1.5", "blurhash": "2.0.5",
"broadcast-channel": "4.19.1", "broadcast-channel": "5.1.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer", "browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"chart.js": "4.3.0", "chart.js": "4.3.0",
@ -39,56 +42,60 @@
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.0.1",
"city-timezones": "^1.2.1", "city-timezones": "^1.2.1",
"compare-versions": "5.0.3", "compare-versions": "6.0.0",
"cropperjs": "2.0.0-beta.2", "cropperjs": "2.0.0-beta.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"emojilib": "github:thatonecalculator/emojilib", "emojilib": "github:thatonecalculator/emojilib",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eventemitter3": "4.0.7", "eslint-config-prettier": "^8.6.0",
"focus-trap": "^7.4.3", "eslint-plugin-file-progress": "^1.3.0",
"eventemitter3": "5.0.1",
"fast-blurhash": "^1.1.2",
"focus-trap": "^7.5.2",
"focus-trap-vue": "^4.0.2", "focus-trap-vue": "^4.0.2",
"gsap": "^3.11.5", "gsap": "^3.12.2",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"json5": "2.2.3", "json5": "2.2.3",
"katex": "0.16.7", "katex": "0.16.8",
"matter-js": "0.18.0", "matter-js": "0.18.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"photoswipe": "5.3.7", "paralint": "^1.2.1",
"photoswipe": "5.3.8",
"prettier": "3.0.0", "prettier": "3.0.0",
"prettier-plugin-vue": "1.1.6", "prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0", "prismjs": "1.29.0",
"punycode": "2.1.1", "punycode": "2.3.0",
"querystring": "0.2.1", "querystring": "0.2.1",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.23.1", "rollup": "3.26.2",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.62.1", "sass": "1.63.6",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"swiper": "9.3.2", "swiper": "10.0.4",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.146.0", "three": "0.146.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.5.2", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.6", "tsc-alias": "1.8.7",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "5.1.3", "typescript": "5.1.6",
"unicode-emoji-json": "^0.4.0", "unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.0", "vanilla-tilt": "1.8.0",
"vite": "4.3.9", "vite": "4.4.2",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vue": "3.3.4", "vue": "3.3.4",
"vue-draggable-plus": "^0.2.2",
"vue-isyourpasswordsafe": "^2.0.0", "vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0", "vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2"
"vuedraggable": "4.1.0"
} }
} }

View file

@ -80,11 +80,11 @@ const emit = defineEmits<{
(ev: "resolved", reportId: string): void; (ev: "resolved", reportId: string): void;
}>(); }>();
let forward = $ref(props.report.forwarded); const forward = $ref(props.report.forwarded);
function resolve() { function resolve() {
os.apiWithDialog("admin/resolve-abuse-user-report", { os.apiWithDialog("admin/resolve-abuse-user-report", {
forward: forward, forward,
reportId: props.report.id, reportId: props.report.id,
}).then(() => { }).then(() => {
emit("resolved", props.report.id); emit("resolved", props.report.id);

View file

@ -41,7 +41,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import * as Misskey from "calckey-js"; import type * as Misskey from "calckey-js";
import XWindow from "@/components/MkWindow.vue"; import XWindow from "@/components/MkWindow.vue";
import MkTextarea from "@/components/form/textarea.vue"; import MkTextarea from "@/components/form/textarea.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";

View file

@ -109,12 +109,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
ref,
computed, computed,
onMounted,
onBeforeUnmount,
shallowRef,
nextTick, nextTick,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
} from "vue"; } from "vue";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { globalEvents } from "@/events.js"; import { globalEvents } from "@/events.js";
@ -173,21 +173,21 @@ const texts = computed(() => {
return angles; return angles;
}); });
let enabled = true; let enabled = true,
let majorGraduationColor = $ref<string>(); majorGraduationColor = $ref<string>(),
//let minorGraduationColor = $ref<string>(); // let minorGraduationColor = $ref<string>();
let sHandColor = $ref<string>(); sHandColor = $ref<string>(),
let mHandColor = $ref<string>(); mHandColor = $ref<string>(),
let hHandColor = $ref<string>(); hHandColor = $ref<string>(),
let nowColor = $ref<string>(); nowColor = $ref<string>(),
let h = $ref<number>(0); h = $ref<number>(0),
let m = $ref<number>(0); m = $ref<number>(0),
let s = $ref<number>(0); s = $ref<number>(0),
let hAngle = $ref<number>(0); hAngle = $ref<number>(0),
let mAngle = $ref<number>(0); mAngle = $ref<number>(0),
let sAngle = $ref<number>(0); sAngle = $ref<number>(0),
let disableSAnimate = $ref(false); disableSAnimate = $ref(false),
let sOneRound = false; sOneRound = false;
function tick() { function tick() {
const now = new Date(); const now = new Date();
@ -230,7 +230,7 @@ function calcColors() {
majorGraduationColor = dark majorGraduationColor = dark
? "rgba(255, 255, 255, 0.3)" ? "rgba(255, 255, 255, 0.3)"
: "rgba(0, 0, 0, 0.3)"; : "rgba(0, 0, 0, 0.3)";
//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; // minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
sHandColor = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)"; sHandColor = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)";
mHandColor = tinycolor( mHandColor = tinycolor(
computedStyle.getPropertyValue("--fg"), computedStyle.getPropertyValue("--fg"),

View file

@ -5,6 +5,13 @@
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle> <MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
<p v-else>{{ title }}</p> <p v-else>{{ title }}</p>
</div> </div>
<div :class="$style.time">
<MkTime :time="announcement.createdAt" />
<div v-if="announcement.updatedAt">
{{ i18n.ts.updatedAt }}:
<MkTime :time="announcement.createdAt" />
</div>
</div>
<Mfm :text="text" /> <Mfm :text="text" />
<img <img
v-if="imageUrl != null" v-if="imageUrl != null"
@ -68,6 +75,10 @@ const gotIt = () => {
} }
} }
.time {
font-size: 0.8rem;
}
.gotIt { .gotIt {
margin: 8px 0 0 0; margin: 8px 0 0 0;
} }

View file

@ -85,11 +85,11 @@
<script lang="ts"> <script lang="ts">
import { import {
markRaw, markRaw,
ref,
onUpdated,
onMounted,
onBeforeUnmount,
nextTick, nextTick,
onBeforeUnmount,
onMounted,
onUpdated,
ref,
watch, watch,
} from "vue"; } from "vue";
import contains from "@/scripts/contains"; import contains from "@/scripts/contains";
@ -99,17 +99,17 @@ import { acct } from "@/filters/user";
import * as os from "@/os"; import * as os from "@/os";
import { MFM_TAGS } from "@/scripts/mfm-tags"; import { MFM_TAGS } from "@/scripts/mfm-tags";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { emojilist, addSkinTone } from "@/scripts/emojilist"; import { addSkinTone, emojilist } from "@/scripts/emojilist";
import { instance } from "@/instance"; import { instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
type EmojiDef = { interface EmojiDef {
emoji: string; emoji: string;
name: string; name: string;
aliasOf?: string; aliasOf?: string;
url?: string; url?: string;
isCustomEmoji?: boolean; isCustomEmoji?: boolean;
}; }
const lib = emojilist.filter((x) => x.category !== "flags"); const lib = emojilist.filter((x) => x.category !== "flags");
@ -140,7 +140,7 @@ for (const x of lib) {
emjdb.sort((a, b) => a.name.length - b.name.length); emjdb.sort((a, b) => a.name.length - b.name.length);
//#region Construct Emoji DB // #region Construct Emoji DB
const customEmojis = instance.emojis; const customEmojis = instance.emojis;
const emojiDefinitions: EmojiDef[] = []; const emojiDefinitions: EmojiDef[] = [];
@ -168,7 +168,7 @@ for (const x of customEmojis) {
emojiDefinitions.sort((a, b) => a.name.length - b.name.length); emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); const emojiDb = markRaw(emojiDefinitions.concat(emjdb));
//#endregion // #endregion
export default { export default {
emojiDb, emojiDb,

View file

@ -49,8 +49,8 @@ const emit = defineEmits<{
(ev: "click", payload: MouseEvent): void; (ev: "click", payload: MouseEvent): void;
}>(); }>();
let el = $ref<HTMLElement | null>(null); const el = $ref<HTMLElement | null>(null);
let ripples = $ref<HTMLElement | null>(null); const ripples = $ref<HTMLElement | null>(null);
onMounted(() => { onMounted(() => {
if (props.autofocus) { if (props.autofocus) {

View file

@ -6,11 +6,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue"; import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
type Captcha = { interface Captcha {
render( render(
container: string | Node, container: string | Node,
options: { options: {
@ -31,7 +31,7 @@ type Captcha = {
execute(id: string): void; execute(id: string): void;
reset(id?: string): void; reset(id?: string): void;
getResponse(id: string): string; getResponse(id: string): string;
}; }
type CaptchaProvider = "hcaptcha" | "recaptcha"; type CaptchaProvider = "hcaptcha" | "recaptcha";
@ -105,7 +105,7 @@ function requestRender() {
captcha.value.render(captchaEl.value, { captcha.value.render(captchaEl.value, {
sitekey: props.sitekey, sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? "dark" : "light", theme: defaultStore.state.darkMode ? "dark" : "light",
callback: callback, callback,
"expired-callback": callback, "expired-callback": callback,
"error-callback": callback, "error-callback": callback,
}); });

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