Merge branch 'develop' into refactor/antennas-in-cache
This commit is contained in:
commit
04c43ed3ef
320 changed files with 14505 additions and 9038 deletions
13
.config/LICENSE
Normal file
13
.config/LICENSE
Normal 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.
|
|
@ -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
1
.gitignore
vendored
|
@ -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
|
||||||
|
|
10
CALCKEY.md
10
CALCKEY.md
|
@ -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
|
||||||
|
|
|
@ -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
27
COPYING
|
@ -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
|
||||||
|
|
12
README.md
12
README.md
|
@ -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
13
custom/assets/LICENSE
Normal 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.
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
17
locales/gl.yml
Normal 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
|
|
@ -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: 宛先を削除
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1847,7 +1847,7 @@ customMOTDDescription: Пользовательские сообщения дл
|
||||||
разрывами строк, будут отображаться случайным образом каждый раз, когда пользователь
|
разрывами строк, будут отображаться случайным образом каждый раз, когда пользователь
|
||||||
загружает / перезагружает страницу.
|
загружает / перезагружает страницу.
|
||||||
recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк,
|
recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк,
|
||||||
должны отображаться на рекомендуемой ленте. НЕ добавляйте `https://`, ТОЛЬКО домен.
|
должны отображаться на рекомендуемой ленте.
|
||||||
caption: Автоматическая подпись
|
caption: Автоматическая подпись
|
||||||
splash: Заставка
|
splash: Заставка
|
||||||
updateAvailable: Возможно, доступно обновление!
|
updateAvailable: Возможно, доступно обновление!
|
||||||
|
|
2065
locales/tr-TR.yml
2065
locales/tr-TR.yml
File diff suppressed because it is too large
Load diff
1026
locales/uk-UA.yml
1026
locales/uk-UA.yml
File diff suppressed because it is too large
Load diff
|
@ -1839,7 +1839,7 @@ pushNotification: 推送通知
|
||||||
subscribePushNotification: 啟用推送通知
|
subscribePushNotification: 啟用推送通知
|
||||||
unsubscribePushNotification: 禁用推送通知
|
unsubscribePushNotification: 禁用推送通知
|
||||||
pushNotificationAlreadySubscribed: 推送通知已經啟用
|
pushNotificationAlreadySubscribed: 推送通知已經啟用
|
||||||
recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中。 不要添加 `https://`,只添加域名。
|
recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中。
|
||||||
searchPlaceholder: 在聯邦網路上搜尋
|
searchPlaceholder: 在聯邦網路上搜尋
|
||||||
cw: 內容警告
|
cw: 內容警告
|
||||||
selectChannel: 選擇一個頻道
|
selectChannel: 選擇一個頻道
|
||||||
|
|
26
package.json
26
package.json
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
|
}
|
15
packages/backend/migration/1689136347561-donation-link.js
Normal file
15
packages/backend/migration/1689136347561-donation-link.js
Normal 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"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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! {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -20,5 +20,9 @@ export function nyaize(text: string): string {
|
||||||
)
|
)
|
||||||
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥")
|
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥")
|
||||||
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")
|
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")
|
||||||
|
// el-GR
|
||||||
|
.replaceAll("να", "νια")
|
||||||
|
.replaceAll("ΝΑ", "ΝΙΑ")
|
||||||
|
.replaceAll("Να", "Νια")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
appId: session.appId,
|
appId: session.appId,
|
||||||
userId: user.id,
|
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 });
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
blockerId: blocker.id,
|
blockerId: blocker.id,
|
||||||
blockeeId: blockee.id,
|
blockeeId: blockee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyBlocking);
|
throw new ApiError(meta.errors.alreadyBlocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
blockerId: blocker.id,
|
blockerId: blocker.id,
|
||||||
blockeeId: blockee.id,
|
blockeeId: blockee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist == null) {
|
if (!exist) {
|
||||||
throw new ApiError(meta.errors.notBlocking);
|
throw new ApiError(meta.errors.notBlocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
clipId: clip.id,
|
clipId: clip.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyClipped);
|
throw new ApiError(meta.errors.alreadyClipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
md5: ps.md5,
|
md5: ps.md5,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return file != null;
|
return exist;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyFollowing);
|
throw new ApiError(meta.errors.alreadyFollowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist == null) {
|
if (!exist) {
|
||||||
throw new ApiError(meta.errors.notFollowing);
|
throw new ApiError(meta.errors.notFollowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist == null) {
|
if (!exist) {
|
||||||
throw new ApiError(meta.errors.notFollowing);
|
throw new ApiError(meta.errors.notFollowing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
postId: post.id,
|
postId: post.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyLiked);
|
throw new ApiError(meta.errors.alreadyLiked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
announcementId: ps.announcementId,
|
announcementId: ps.announcementId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (read != null) {
|
if (read) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
muterId: muter.id,
|
muterId: muter.id,
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyMuting);
|
throw new ApiError(meta.errors.alreadyMuting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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" },
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
blockerId: renote.userId,
|
blockerId: renote.userId,
|
||||||
blockeeId: user.id,
|
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({
|
||||||
|
where: {
|
||||||
blockerId: reply.userId,
|
blockerId: reply.userId,
|
||||||
blockeeId: user.id,
|
blockeeId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (block) {
|
if (isBlocked) {
|
||||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyFavorited);
|
throw new ApiError(meta.errors.alreadyFavorited);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
pageId: page.id,
|
pageId: page.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyLiked);
|
throw new ApiError(meta.errors.alreadyLiked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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"],
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
muterId: muter.id,
|
muterId: muter.id,
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyMuting);
|
throw new ApiError(meta.errors.alreadyMuting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
followeeId: user.id,
|
followeeId: user.id,
|
||||||
followerId: me.id,
|
followerId: me.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (following == null) {
|
if (!isFollowed) {
|
||||||
throw new ApiError(meta.errors.nullFollowers);
|
throw new ApiError(meta.errors.nullFollowers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
followeeId: user.id,
|
followeeId: user.id,
|
||||||
followerId: me.id,
|
followerId: me.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (following == null) {
|
if (!isFollowing) {
|
||||||
throw new ApiError(meta.errors.cannot_find);
|
throw new ApiError(meta.errors.cannot_find);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
userId: me.id,
|
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({
|
||||||
|
where: {
|
||||||
blockerId: user.id,
|
blockerId: user.id,
|
||||||
blockeeId: me.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({
|
||||||
|
where: {
|
||||||
userListId: userList.id,
|
userListId: userList.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userList == null) {
|
if (!exist) {
|
||||||
throw new ApiError(meta.errors.noSuchList);
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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"];
|
||||||
|
|
|
@ -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"];
|
||||||
|
|
|
@ -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({
|
||||||
|
where: {
|
||||||
id: this.listId,
|
id: this.listId,
|
||||||
userId: this.user!.id,
|
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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
7
packages/client/.eslintrc.json
Normal file
7
packages/client/.eslintrc.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
|
||||||
|
"plugins": ["file-progress", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"file-progress/activate": 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue