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

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

13
.config/LICENSE Normal file
View File

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

View File

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

1
.gitignore vendored
View File

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

View File

@ -6,19 +6,13 @@
## Planned
- 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/)
- Use [Magic RegExP](https://regexp.dev/) for RegEx 🦄
- Function
- User "choices" (recommended users) and featured hashtags like Mastodon and Soapbox
- Join Reason system like Mastodon/Pleroma
- Option to publicize server blocks
- More antenna options
- Groups
- Form
- Lookup/details for post/file/server
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
## Work in progress
@ -30,6 +24,7 @@
- Timeline filters
- Events
- Fully revamp non-logged-in screen
- Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes
## Implemented
@ -122,6 +117,7 @@
- Let moderators see moderation nodes
- Non-mangled unicode emojis
- Skin tone selection support
- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative
## Implemented (remote)
@ -137,7 +133,7 @@
- 👍 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 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/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes

View File

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

27
COPYING
View File

@ -1,15 +1,24 @@
Unless otherwise stated this repository is
Copyright © 2014-2022 syuilo and contributers
Copyright © 2022 thatonecalculator and contributers
Unless specified otherwise, the entirety of this repository is subject to the following:
Copyright © 2014-2023 syuilo and contributors
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.
---
Calckey includes several third-party Open-Source softwares.
These specific configuration directories:
Emoji keywords for Unicode 11 and below by Mu-An Chiou
License: MIT
https://github.com/muan/emojilib/blob/master/LICENSE
- .config/
- custom/assets/
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
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.
License: MIT
https://github.com/infinitered/nsfwjs/blob/master/LICENSE
Licenses for all softwares and software libraries installed via the Node Package Manager ("npm") can be found by running the following shell command in the root directory of this repository:
pnpm licenses list

View File

@ -72,6 +72,14 @@
# 🌠 Getting started
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**.
## 🔰 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.
- 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

13
custom/assets/LICENSE Normal file
View File

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

View File

@ -1,10 +1,11 @@
# 🚚 Migrating from Misskey to Calckey
# 🚚 Migrating from Misskey/FoundKey to Calckey
The following procedure may not work depending on your environment and version of Misskey.
All the guides below assume you're starting in the root of the repo directory.
**Make sure you**
- **stopped all master and worker processes of Misskey.**
- **have backups of the database before performing any commands.**
### Before proceeding
- **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
@ -77,15 +78,16 @@ NODE_ENV=production pnpm run migrate
# build using prefered method
```
## Foundkey
## FoundKey
```sh
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)"
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
done
@ -100,4 +102,4 @@ NODE_ENV=production pnpm run migrate
## Reverse
You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to Foundkey, though.
You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to FoundKey, although this is not recommended due to FoundKey being end-of-life.

View File

@ -95,7 +95,7 @@ privacy: "Privadesa"
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
defaultNoteVisibility: "Visibilitat per defecte"
follow: "Segueix"
followRequest: "Segueix"
followRequest: "Sol·licitud de Seguiment"
followRequests: "Sol·licituds de seguiment"
unfollow: "Deixa de seguir"
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
recommendedInstances: Servidors recomanats
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
splash: Pantalla de Benvinguda
swipeOnDesktop: Permet lliscar a l'estil del mòbil a l'escriptori
@ -1603,6 +1603,13 @@ _aboutMisskey:
patrons: Mecenes de Calckey
patronsList: Llistats cronològicament, no per la quantitat donada. Fes una donació
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
pageLikesCount: Nombre de pàgines amb M'agrada
youAreRunningUpToDateClient: Estás fent servir la versió del client més nova.
@ -2144,3 +2151,13 @@ _skinTones:
swipeOnMobile: Permet lliscar entre pàgines
enableIdenticonGeneration: Habilitar la generació d'Identicon
enableServerMachineStats: Habilitar les estadístiques del maquinari del servidor
showPopup: Notificar els usuaris amb una finestra emergent
showWithSparkles: Mostra amb espurnes
youHaveUnreadAnnouncements: Tens anuncis sense llegir
xl: XL
donationLink: Enllaç a la pàgina de donacions
neverShow: No tornis a mostrar
remindMeLater: Potser després
removeMember: Elimina el membre
removeQuote: Elimina la cita
removeRecipient: Elimina el destinatari

View File

@ -105,7 +105,7 @@ privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility"
follow: "Follow"
followRequest: "Follow"
followRequest: "Follow Request"
followRequests: "Follow requests"
unfollow: "Unfollow"
followRequestPending: "Follow request pending"
@ -644,6 +644,7 @@ useBlurEffectForModal: "Use blur effect for modals"
useFullReactionPicker: "Use full-size reaction picker"
width: "Width"
height: "Height"
xl: "XL"
large: "Big"
medium: "Medium"
small: "Small"
@ -1049,7 +1050,7 @@ customSplashIconsDescription: "URLs for custom splash screen icons separated by
showUpdates: "Show a popup when Calckey updates"
recommendedInstances: "Recommended servers"
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"
splash: "Splash Screen"
updateAvailable: "There might be an update available!"
@ -1117,6 +1118,12 @@ enableIdenticonGeneration: "Enable Identicon generation"
showPopup: "Notify users with popup"
showWithSparkles: "Show with sparkles"
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:
description: "Reduces the effort of server moderation through automatically recognizing
@ -1215,8 +1222,13 @@ _aboutMisskey:
source: "Source code"
translation: "Translate 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.
Thank you! 🥰"
sponsors: "Calckey sponsors"
patrons: "Calckey patrons"
patronsList: "Listed chronologically, not by donation size. Donate with the link above to get your name on here!"
_nsfw:

View File

@ -642,7 +642,7 @@ wordMute: "Silenciar palabras"
regexpError: "Error de la expresión regular"
regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line}
de las palabras muteadas {tab}"
instanceMute: "Instancias silenciadas"
instanceMute: "Servidores silenciados"
userSaysSomething: "{name} dijo algo"
makeActive: "Activar"
display: "Apariencia"
@ -671,14 +671,14 @@ sample: "Muestra"
abuseReports: "Reportes"
reportAbuse: "Reportar"
reportAbuseOf: "Reportar a {name}"
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en
particular, ingrese la URL de esta."
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una publicación
en particular, ingrese la URL de esta."
abuseReported: "Se ha enviado el reporte. Muchas gracias."
reporter: "Reportador"
reporteeOrigin: "Reportar a"
reporterOrigin: "Origen del reporte"
forwardReport: "Transferir un informe a una instancia remota"
forwardReportIsAnonymous: "No puede ver su información de la instancia remota y aparecerá
forwardReport: "Transferir reporte a un servidor remoto"
forwardReportIsAnonymous: "No puede ver su información del servidor remoto y aparecerá
como una cuenta anónima del sistema"
send: "Enviar"
abuseMarkAsResolved: "Marcar reporte como resuelto"
@ -686,7 +686,7 @@ openInNewTab: "Abrir en una Nueva Pestaña"
openInSideView: "Abrir en una vista al costado"
defaultNavigationBehaviour: "Navegación por defecto"
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}"
random: "Aleatorio"
system: "Sistema"
@ -697,14 +697,14 @@ createNew: "Crear"
optional: "Opcional"
createNewClip: "Crear clip nuevo"
unclip: "Quitar clip"
confirmToUnclipAlreadyClippedNote: "Esta nota ya está incluida en el clip \"{name}\"\
. ¿Quiere quitar la nota del clip?"
confirmToUnclipAlreadyClippedNote: "Esta publicación ya está incluida en el clip \"\
{name}\". ¿Quiere quitar la nota del clip?"
public: "Público"
i18nInfo: "Calckey está siendo traducido a varios idiomas gracias a voluntarios. Se
puede colaborar traduciendo en {link}"
manageAccessTokens: "Administrar tokens de acceso"
accountInfo: "Información de la Cuenta"
notesCount: "Cantidad de notas"
notesCount: "Cantidad de publicaciones"
repliesCount: "Cantidad de respuestas hechas"
renotesCount: "Cantidad de renotas hechas"
repliedCount: "Cantidad de respuestas recibidas"
@ -720,7 +720,7 @@ no: "No"
driveFilesCount: "Cantidad de archivos en el drive"
driveUsage: "Uso del drive"
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."
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
@ -734,7 +734,7 @@ verificationEmailSent: "Se le ha enviado un correo electrónico de confirmación
configuración."
notSet: "Sin especificar"
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"
pageLikedCount: "Número de favoritos de su página"
contact: "Contacto"
@ -975,7 +975,7 @@ shuffle: "Aleatorio"
account: "Cuentas"
move: "Mover"
_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
ligeramente la carga en el servidor."
sensitivity: "Sensibilidad de detección"
@ -1295,7 +1295,7 @@ _time:
_tutorial:
title: "Cómo usar Calckey"
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_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."
@ -1789,7 +1789,7 @@ _pages:
splitStrByLine: "Separar texto en lineas"
_splitStrByLine:
arg1: "Texto"
ref: "Variables"
ref: "Variable"
aiScriptVar: "Variable de AiScript"
fn: "funciones"
_fn:
@ -1800,8 +1800,8 @@ _pages:
_for:
arg1: "Cantidad de repeticiones"
arg2: "Acción"
typeError: "El slot {slot} acepta el tipo {expect} pero fue ingresado el tipo
{actual}"
typeError: "El slot {slot} acepta el tipo \"{expect}\" pero fue ingresado el tipo
\"{actual}\""
thereIsEmptySlot: "El slot {slot} está vacío"
types:
string: "Texto"

View File

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

17
locales/gl.yml Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -58,7 +58,7 @@ impl TryFrom<AntennaSrcEnum> for super::AntennaSrc {
// ---- TODO: could be macro
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! {

View File

@ -91,7 +91,7 @@ pub enum AppPermission {
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)]
mod unit_test {

View File

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

View File

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

View File

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

View File

@ -1,8 +1,13 @@
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 =
config.maxNoteLength != null ? config.maxNoteLength : 3000; // <- should we increase this?
export const MAX_NOTE_TEXT_LENGTH = Math.min(
config.maxNoteLength ?? 3000,
DB_MAX_NOTE_TEXT_LENGTH,
);
export const MAX_CAPTION_TEXT_LENGTH = Math.min(
config.maxCaptionLength ?? 1500,
DB_MAX_IMAGE_COMMENT_LENGTH,

View File

@ -4,7 +4,7 @@ export type 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);
return { username: split[0], host: split[1] || null };
}

View File

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

View File

@ -3,8 +3,13 @@
/**
* Maximum note text length that can be stored in DB.
* 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.

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import * as fs from "node:fs";
import { queueLogger } from "../../logger.js";
import { addFile } from "@/services/drive/add-file.js";
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 type { Note } from "@/models/entities/note.js";
import type { Poll } from "@/models/entities/poll.js";
@ -75,7 +75,7 @@ export async function exportNotes(
if (note.hasPoll) {
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;
await write(isFirst ? content : ",\n" + content);
exportedNotesCount++;
@ -112,15 +112,16 @@ export async function exportNotes(
done();
}
function serialize(
async function serialize(
note: Note,
poll: Poll | null = null,
): Record<string, unknown> {
): Promise<Record<string, unknown>> {
return {
id: note.id,
text: note.text,
createdAt: note.createdAt,
fileIds: note.fileIds,
files: await DriveFiles.packMany(note.fileIds),
replyId: note.replyId,
renoteId: note.renoteId,
poll: poll,

View File

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

View File

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

View File

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

View File

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

View File

@ -40,9 +40,9 @@ export default define(meta, paramDef, async (ps, user) => {
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);
}

View File

@ -1,6 +1,5 @@
import { Meta } from "@/models/entities/meta.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 define from "../../define.js";
@ -177,6 +176,9 @@ export const paramDef = {
postImports: { type: "boolean" },
},
},
enableServerMachineStats: { type: "boolean" },
enableIdenticonGeneration: { type: "boolean" },
donationLink: { type: "string", nullable: true },
},
required: [],
} as const;
@ -218,6 +220,15 @@ export default define(meta, paramDef, async (ps, me) => {
if (Array.isArray(ps.recommendedInstances)) {
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)) {
@ -568,6 +579,21 @@ export default define(meta, paramDef, async (ps, me) => {
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) => {
const metas = await transactionalEntityManager.find(Meta, {
order: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,19 +30,23 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, user) => {
// 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);
}
// Check if already read
const read = await AnnouncementReads.findOneBy({
announcementId: ps.announcementId,
userId: user.id,
const read = await AnnouncementReads.exist({
where: {
announcementId: ps.announcementId,
userId: user.id,
},
});
if (read != null) {
if (read) {
return;
}

View File

@ -17,9 +17,9 @@ export const paramDef = {
} as const;
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({
id: ps.tokenId,
userId: user.id,

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ export const meta = {
requireCredential: false,
requireCredentialPrivateMode: true,
description: "Get conversation of a note thread/chain by a reply",
res: {
type: "array",
@ -34,7 +35,11 @@ export const meta = {
export const paramDef = {
type: "object",
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 },
offset: { type: "integer", default: 0 },
},
@ -51,7 +56,7 @@ export default define(meta, paramDef, async (ps, user) => {
const conversation: Note[] = [];
let i = 0;
async function get(id: any) {
async function get(id: string) {
i++;
const p = await getNote(id, user).catch((e) => {
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 (i > ps.offset!) {
if (i > ps.offset) {
conversation.push(p);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => {
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);
}

View File

@ -1,4 +1,5 @@
import { Entity } from "megalodon";
import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { Users, Notes } from "@/models/index.js";
import { IsNull, MoreThan } from "typeorm";
@ -17,7 +18,7 @@ export async function getInstance(response: Entity.Instance) {
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 :)",
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,
stats: {
user_count: await totalUsers,

View File

@ -67,6 +67,25 @@ export function apiStatusMastodon(router: Router): void {
const { sensitive } = body;
body.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);
ctx.body = convertStatus(data.data);
} catch (e: any) {
@ -86,7 +105,7 @@ export function apiStatusMastodon(router: Router): void {
ctx.body = convertStatus(data.data);
} catch (e: any) {
console.error(e);
ctx.status = 401;
ctx.status = ctx.status == 404 ? 404 : 401;
ctx.body = e.response.data;
}
});

View File

@ -18,7 +18,7 @@ function getUserToken(ctx: Koa.BaseContext): string | null {
function compareOrigin(ctx: Koa.BaseContext): boolean {
function 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"];

View File

@ -18,7 +18,7 @@ function getUserToken(ctx: Koa.BaseContext): string | null {
function compareOrigin(ctx: Koa.BaseContext): boolean {
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"];

View File

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

View File

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

View File

@ -1,4 +1,6 @@
import * as fs from "node:fs";
import net from "node:net";
import { promises } from "node:dns";
import type Koa from "koa";
import sharp from "sharp";
import type { IImage } from "@/services/drive/image-processor.js";
@ -19,6 +21,40 @@ export async function proxyMedia(ctx: Koa.Context) {
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
const [path, cleanup] = await createTemp();

View File

@ -102,7 +102,11 @@
localStorage.setItem("fontSize", null);
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");
@ -123,7 +127,7 @@
}
async function addStyle(styleText) {
let css = document.createElement("style");
const css = document.createElement("style");
css.appendChild(document.createTextNode(styleText));
document.head.appendChild(css);
}

View File

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

View File

@ -42,9 +42,9 @@ export async function insertNoteUnread(
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
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) {
publishMainStream(userId, "unreadMention", note.id);

View File

@ -4,7 +4,7 @@ export type 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);
return { username: split[0], host: split[1] || null };
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,13 @@
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
<p v-else>{{ title }}</p>
</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" />
<img
v-if="imageUrl != null"
@ -68,6 +75,10 @@ const gotIt = () => {
}
}
.time {
font-size: 0.8rem;
}
.gotIt {
margin: 8px 0 0 0;
}

View File

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

View File

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

View File

@ -6,11 +6,11 @@
</template>
<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 { i18n } from "@/i18n";
type Captcha = {
interface Captcha {
render(
container: string | Node,
options: {
@ -31,7 +31,7 @@ type Captcha = {
execute(id: string): void;
reset(id?: string): void;
getResponse(id: string): string;
};
}
type CaptchaProvider = "hcaptcha" | "recaptcha";
@ -105,7 +105,7 @@ function requestRender() {
captcha.value.render(captchaEl.value, {
sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? "dark" : "light",
callback: callback,
callback,
"expired-callback": callback,
"error-callback": callback,
});

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