Merge branch 'develop' into feat/antenna_limit

This commit is contained in:
naskya 2024-04-22 05:51:22 +09:00
commit 4c91e8e37f
No known key found for this signature in database
GPG Key ID: 712D413B3A9FED5C
341 changed files with 4405 additions and 3244 deletions

View File

@ -178,19 +178,13 @@ logLevel: [
# Media Proxy
#mediaProxy: https://example.com/proxy
# Proxy remote files (default: false)
# Proxy remote files (default: true)
#proxyRemoteFiles: true
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]
# TWA
#twa:
# nameSpace: android_app
# packageName: tld.domain.twa
# sha256CertFingerprints: ['AB:CD:EF']
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View File

@ -3,30 +3,47 @@
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
🤝 By submitting this issue, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
**What happened?** _(Please give us a brief description of what happened.)_
## What happened? <!-- Please give us a brief description of what happened. -->
**What did you expect to happen?** _(Please give us a brief description of what you expected to happen.)_
**Version** _(What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information.)_
## What did you expect to happen? <!-- Please give us a brief description of what you expected to happen. -->
**Instance** _(What instance of firefish are you using?)_
**What type of issue is this?** _(If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side.)_
## Version <!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
**What browser are you using? (Client-side issues only)**
**What operating system are you using? (Client-side issues only)**
## What type of issue is this? <!-- If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side. -->
**How do you deploy Firefish on your server? (Server-side issues only)**
- [ ] server-side
- [ ] client-side
- [ ] not sure
**What operating system are you using? (Server-side issues only)**
<details>
**Relevant log output** _(Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks.)_
### Instance <!-- What instance of firefish are you using? -->
**Contribution Guidelines**
### What browser are you using? (client-side issues only)
### What operating system are you using? (client-side issues only)
### How do you deploy Firefish on your server? (server-side issues only)
### What operating system are you using? (Server-side issues only)
### Relevant log output <!-- Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. -->
</details>
## Contribution Guidelines
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines
- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate.
**Are you willing to fix this bug?** (optional)
## Are you willing to fix this bug? (optional)
- [ ] Yes. I will fix this bug and open a merge request if the change is agreed upon.

View File

@ -3,18 +3,22 @@
🔒 Found a security vulnerability? [Please disclose it responsibly.](https://firefish.dev/firefish/firefish/-/blob/develop/SECURITY.md)
🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) -->
**What feature would you like implemented?** _(Please give us a brief description of what you'd like.)_
## What feature would you like implemented? <!-- Please give us a brief description of what you'd like. -->
**Why should we add this feature?** _(Please give us a brief description of why your feature is important.)_
**Version** _(What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information.)_
## Why should we add this feature? <!-- Please give us a brief description of why your feature is important. -->
**Instance** _(What instance of firefish are you using?)_
**Contribution Guidelines**
## Version <!-- What version of firefish is your instance running? You can find this by clicking your instance's logo at the bottom left and then clicking instance information. -->
## Instance <!-- What instance of firefish are you using? -->
## Contribution Guidelines
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines
- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.
**Are you willing to implement this feature?** (optional)
## Are you willing to implement this feature? (optional)
- [ ] Yes. I will implement this feature and open a merge request if the change is agreed upon.

View File

@ -1,8 +1,9 @@
<!-- Thanks for taking the time to make Firefish better! It's not required, but please consider using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) when making your commits. If you use VSCode, please use the [Conventional Commits extension](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits). -->
**What does this PR do?** _(Please give us a brief description of what this PR does.)_
## What does this PR do? <!-- Please give us a brief description of what this PR does. -->
**Contribution Guidelines**
## Contribution Guidelines
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] This change is reviewed in an issue / This is a minor bug fix
- [ ] I agree to follow this project's Contribution Guidelines

View File

@ -12,6 +12,7 @@
"esbenp.prettier-vscode",
"redhat.vscode-yaml",
"yoavbls.pretty-ts-errors",
"biomejs.biome"
"biomejs.biome",
"rust-lang.rust-analyzer"
]
}

207
Cargo.lock generated
View File

@ -59,9 +59,9 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "allocator-api2"
version = "0.2.16"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "android-tzdata"
@ -132,6 +132,18 @@ version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -190,11 +202,14 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
name = "backend-rs"
version = "0.0.0"
dependencies = [
"argon2",
"async-trait",
"basen",
"bcrypt",
"cfg-if",
"chrono",
"cuid2",
"emojis",
"idna",
"jsonschema",
"macro_rs",
@ -238,6 +253,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
[[package]]
name = "base64ct"
version = "1.6.0"
@ -250,6 +271,19 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbe4bb73fd931c4d1aaf53b35d1286c8a948ad00ec92c8e3c856f15fd027f43"
[[package]]
name = "bcrypt"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7"
dependencies = [
"base64 0.22.0",
"blowfish",
"getrandom",
"subtle",
"zeroize",
]
[[package]]
name = "bigdecimal"
version = "0.3.1"
@ -303,6 +337,15 @@ dependencies = [
"wyz",
]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -312,6 +355,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blowfish"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "borsh"
version = "1.4.0"
@ -384,9 +437,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.92"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
[[package]]
name = "cfg-if"
@ -412,7 +465,17 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.4",
"windows-targets 0.52.5",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
@ -633,13 +696,22 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
dependencies = [
"serde",
]
[[package]]
name = "emojis"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee61eb945bff65ee7d19d157d39c67c33290ff0742907413fd5eefd29edc979"
dependencies = [
"phf",
]
[[package]]
name = "encoding_rs"
version = "0.8.34"
@ -1075,6 +1147,15 @@ dependencies = [
"syn 2.0.58",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "ipnet"
version = "2.9.0"
@ -1122,7 +1203,7 @@ checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978"
dependencies = [
"ahash 0.8.11",
"anyhow",
"base64",
"base64 0.21.7",
"bytecount",
"clap",
"fancy-regex",
@ -1175,7 +1256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
dependencies = [
"cfg-if",
"windows-targets 0.52.4",
"windows-targets 0.52.5",
]
[[package]]
@ -1296,9 +1377,9 @@ dependencies = [
[[package]]
name = "napi-build"
version = "2.1.2"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f9130fccc5f763cf2069b34a089a18f0d0883c66aceb81f2fad541a3d823c43"
checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a"
[[package]]
name = "napi-derive"
@ -1350,9 +1431,9 @@ dependencies = [
[[package]]
name = "num"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41"
dependencies = [
"num-bigint",
"num-complex",
@ -1559,6 +1640,17 @@ dependencies = [
"syn 2.0.58",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.14"
@ -1580,6 +1672,24 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@ -1801,7 +1911,7 @@ version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64",
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
@ -1947,7 +2057,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
"base64 0.21.7",
]
[[package]]
@ -2231,6 +2341,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "slab"
version = "0.4.9"
@ -2398,7 +2514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
dependencies = [
"atoi",
"base64",
"base64 0.21.7",
"bigdecimal",
"bitflags 2.5.0",
"byteorder",
@ -2445,7 +2561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
dependencies = [
"atoi",
"base64",
"base64 0.21.7",
"bigdecimal",
"bitflags 2.5.0",
"byteorder",
@ -3041,7 +3157,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.4",
"windows-targets 0.52.5",
]
[[package]]
@ -3059,7 +3175,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.4",
"windows-targets 0.52.5",
]
[[package]]
@ -3079,17 +3195,18 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
]
[[package]]
@ -3100,9 +3217,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
@ -3112,9 +3229,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
@ -3124,9 +3241,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
@ -3136,9 +3259,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
@ -3148,9 +3271,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
@ -3160,9 +3283,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
@ -3172,9 +3295,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"

View File

@ -7,14 +7,17 @@ macro_rs = { path = "packages/macro-rs" }
napi = { version = "2.16.2", default-features = false }
napi-derive = "2.16.2"
napi-build = "2.1.2"
napi-build = "2.1.3"
argon2 = "0.5.3"
async-trait = "0.1.80"
basen = "0.1.0"
bcrypt = "0.15.1"
cfg-if = "1.0.0"
chrono = "0.4.37"
convert_case = "0.6.0"
cuid2 = "0.1.2"
emojis = "0.6.1"
idna = "0.5.0"
jsonschema = "0.17.1"
once_cell = "1.19.0"

View File

@ -1,7 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
"organizeImports": {
"enabled": true
"enabled": false
},
"linter": {
"enabled": true,
@ -21,7 +21,8 @@
"useImportType": "warn",
"useShorthandFunctionType": "warn",
"useTemplate": "warn",
"noNonNullAssertion": "off"
"noNonNullAssertion": "off",
"useNodejsImportProtocol": "off"
}
}
}

View File

@ -17,6 +17,7 @@ services:
# - web
environment:
NODE_ENV: production
NODE_OPTIONS: --max-old-space-size=3072
volumes:
- ./custom:/firefish/custom:ro
- ./files:/firefish/files

View File

@ -2,6 +2,10 @@
Breaking changes are indicated by the :warning: icon.
## v20240413
- :warning: Removed `patrons` endpoint.
## v20240405
- Added `notes/history` endpoint.

View File

@ -5,9 +5,16 @@ Critical security updates are indicated by the :warning: icon.
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
## Unreleased
## [v20240421](https://firefish.dev/firefish/firefish/-/merge_requests/10756/commits)
- Fix bugs
## [v20240413](https://firefish.dev/firefish/firefish/-/merge_requests/10741/commits)
- Add "Media" tab to user page
- Improve federation and rendering of mathematical expressions
- Remove donor information from the web client
- See also: https://info.firefish.dev/notes/9s1n283sb10rh869
- Fix bugs
## [v20240405](https://firefish.dev/firefish/firefish/-/merge_requests/10733/commits)

View File

@ -1,6 +1,8 @@
BEGIN;
DELETE FROM "migrations" WHERE name IN (
'AddDriveFileUsage1713451569342',
'ConvertCwVarcharToText1713225866247',
'FixChatFileConstraint1712855579316',
'DropTimeZone1712425488543',
'ExpandNoteEdit1711936358554',
@ -22,6 +24,15 @@ DELETE FROM "migrations" WHERE name IN (
'RemoveNativeUtilsMigration1705877093218'
);
-- AddDriveFileUsage
ALTER TABLE "drive_file" DROP COLUMN "usageHint";
DROP TYPE "drive_file_usage_hint_enum";
-- convert-cw-varchar-to-text
DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f";
ALTER TABLE "note" ALTER COLUMN "cw" TYPE character varying(512);
CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2);
-- fix-chat-file-constraint
ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_535def119223ac05ad3fa9ef64b";
ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_535def119223ac05ad3fa9ef64b" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION;

View File

@ -154,7 +154,7 @@ sudo apt install ffmpeg
1. Build
```sh
pnpm install --frozen-lockfile
NODE_ENV=production pnpm run build
NODE_ENV=production NODE_OPTIONS='--max-old-space-size=3072' pnpm run build
```
1. Execute database migrations
```sh
@ -242,6 +242,7 @@ In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefi
WorkingDirectory=/home/firefish/firefish
Environment="NODE_ENV=production"
Environment="npm_config_cache=/tmp"
Environment="NODE_OPTIONS=--max-old-space-size=3072"
# uncomment the following line if you use jemalloc (note that the path varies on different environments)
# Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
StandardOutput=journal

View File

@ -2,7 +2,7 @@
You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md).
## Unreleased
## v20240413
### For all users
@ -21,8 +21,8 @@ The number of posts stored on your database can be found at `https://yourserver.
- Please remove `packages/backend-rs/target` before building Firefish.
```sh
rm --recursive --force packages/backend-rs/target
```
rm --recursive --force packages/backend-rs/target
```
- Please do not terminate `pnpm run migrate` even if it appears to be frozen.
### For Docker/Podman users

View File

@ -893,9 +893,6 @@ _aboutFirefish:
source: "الشفرة المصدرية"
translation: "ترجم ميسكي"
donate: "تبرع لميسكي"
morePatrons: "نحن نقدر الدعم الذي قدمه العديد من الأشخاص الذين لم نذكرهم. شكرًا
لكم 🥰"
patrons: "الداعمون"
_nsfw:
respect: "اخف الوسائط ذات المحتوى الحساس"
ignore: "اعرض الوسائط ذات المحتوى الحساس"

View File

@ -975,8 +975,6 @@ _aboutFirefish:
source: "সোর্স কোড"
translation: "Firefish অনুবাদ করুন"
donate: "Firefish তে দান করুন"
morePatrons: "আরও অনেকে আমাদের সাহায্য করছেন। তাদের সবাইকে ধন্যবাদ 🥰"
patrons: "সমর্থনকারী"
_nsfw:
respect: "স্পর্শকাতর মিডিয়া লুকান"
ignore: "স্পর্শকাতর মিডিয়া লুকাবেন না"

View File

@ -1586,18 +1586,12 @@ _aboutFirefish:
translation: Tradueix Firefish
about: Firefish és una bifurcació de Misskey feta per ThatOneCalculator, que està
en desenvolupament des del 2022.
morePatrons: També agraïm el suport de molts altres ajudants que no figuren aquí.
Gràcies! 🥰
patrons: Mecenes de Firefish
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 Firefish?
pleaseDonateToFirefish: Penseu en fer una donació a Firefish 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
misskeyContributors: Col·laboradors de Misskey
unknown: Desconegut
pageLikesCount: Nombre de pàgines amb M'agrada
@ -2084,9 +2078,9 @@ _experiments:
release: Publicà
title: Experiments
enablePostImports: Activar l'importació de publicacions
postImportsCaption: Permet els usuaris importar publicacions desde comptes a Firefish,
postImportsCaption: Permet als usuaris importar publicacions des de comptes de Firefish,
Misskey, Mastodon, Akkoma i Pleroma. Pot fer que el servidor vagi més lent durant
la càrrega si tens un coll d'ampolla a la cua.
la importació si la teva cua de feina és saturada.
noGraze: Si us plau, desactiva l'extensió del navegador "Graze for Mastodon", ja que
interfereix amb Firefish.
accessibility: Accessibilitat

View File

@ -1092,17 +1092,11 @@ _aboutFirefish:
source: "Quellcode"
translation: "Firefish übersetzen"
donate: "An Firefish spenden"
morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter
Personen sehr. Danke! 🥰"
patrons: "UnterstützerInnen"
patronsList: Auflistung chonologisch, nicht nach Spenden-Größe. Spende über den
Link oben, um hier aufgeführt zu werden!
donateTitle: Gefällt dir Firefish?
pleaseDonateToFirefish: Bitte erwäge eine Spende an Firefish, um dessen Entwicklung
zu unterstützen.
pleaseDonateToHost: Bitte erwäge auch, an deinen Heimatserver {host} zu spenden,
um bei der Deckung der Betriebskosten zu helfen.
sponsors: Firefish-Sponsoren
donateHost: Spende an {host}
misskeyContributors: Misskey-Mitwirkende
_nsfw:

View File

@ -1337,12 +1337,6 @@ _aboutFirefish:
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: "Firefish sponsors"
patrons: "Firefish patrons"
patronsList: "Listed chronologically, not by donation size. Donate with the link
above to get your name on here!"
_nsfw:
respect: "Hide NSFW media"
ignore: "Don't hide NSFW media"

View File

@ -1073,17 +1073,11 @@ _aboutFirefish:
source: "Código fuente"
translation: "Traducir Firefish"
donate: "Donar a Firefish"
morePatrons: "También apreciamos el apoyo de muchos más que no están enlistados
aquí. ¡Gracias! 🥰"
patrons: "Mecenas de Firefish"
pleaseDonateToFirefish: Por favor considera donar a Firefish para apollar su desarrollo.
donateHost: Dona a {host}
patronsList: Listados cronológicamente no por monto de la donación. ¡Dona con el
vínculo de arriba para que tu nombre aparezca aquí!
donateTitle: ¿Te gusta Firefish?
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
ayudar con los costos de operación.
sponsors: Patrocinadores de Firefish
misskeyContributors: Contribuidores de Misskey
_nsfw:
respect: "Ocultar medios NSFW"

View File

@ -996,18 +996,12 @@ _aboutFirefish:
source: "Code source"
translation: "Traduire Firefish"
donate: "Soutenir Firefish"
morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes
non mentionnées ici. Merci à toutes et à tous ! 🥰"
patrons: "Contributeurs"
pleaseDonateToFirefish: Merci de considérer de faire un don pour soutenir le développement
de Firefish.
sponsors: Sponsors Firefish
donateTitle: Firefish vous plaît?
pleaseDonateToHost: Également, veuillez envisager de faire un don à votre serveur
d'accueil, {host}, pour contribuer à couvrir ses frais de fonctionnement.
donateHost: Faire un don à {host}
patronsList: Listé chronologiquement, pas par taille de donation. Faite un don avec
le lien ci-dessus pour avoir votre nom affiché ici !
misskeyContributors: Contributeurs Misskey
_nsfw:
respect: "Cacher les médias marqués comme contenu sensible (NSFW)"
@ -2324,3 +2318,4 @@ markLocalFilesNsfwByDefaultDescription: Indépendamment de ce réglage, les util
peuvent supprimer le drapeau « sensible » (NSFW) eux-mêmes. Les fichiers existants
ne sont pas affectés.
noteEditHistory: Historique des publications
media: Multimédia

View File

@ -985,12 +985,6 @@ _aboutFirefish:
source: "Sumber kode"
translation: "Terjemahkan Firefish"
donate: "Donasi ke Firefish"
morePatrons: "Kami sangat mengapresiasi dukungan dari banyak penolong lain yang
tidak tercantum disini. Terima kasih! 🥰"
patrons: "Pendukung"
patronsList: Diurutkan secara kronologis, bukan berdasarkan jumlah donasi. Berdonasilah
dengan tautan di atas supaya nama kamu ada di sini!
sponsors: Sponsor Firefish
donateTitle: Suka Firefish?
pleaseDonateToFirefish: Silakan pertimbangkan berdonasi ke Firefish untuk mendukung
pengembangannya.

View File

@ -934,18 +934,12 @@ _aboutFirefish:
source: "Codice sorgente"
translation: "Traduzione di Firefish"
donate: "Sostieni Firefish"
morePatrons: "Apprezziamo sinceramente l'aiuto di tante altre persone non elencate
qui. Grazie mille! 🥰"
patrons: "Sostenitori"
sponsors: Gli sponsor di Firefish
misskeyContributors: Contributori di Misskey
donateTitle: Ti piace Firefish?
pleaseDonateToFirefish: Con una donazione puoi supportare lo sviluppo di Firefish.
pleaseDonateToHost: Considera anche una donazione al server che ti ospita, {host},
per contribuire ai costi che sostiene.
donateHost: Dona a {host}
patronsList: Elencati in ordine cronologico, non per importo. Dona con il link sopra
per apparire in questa lista!
_nsfw:
respect: "Nascondi i media sensibli (NSFW)"
ignore: "Mostra i media sensibili (NSFW)"

View File

@ -1109,14 +1109,10 @@ _aboutFirefish:
source: "ソースコード"
translation: "Firefishを翻訳"
donate: "Firefishに寄付"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰"
patrons: "支援者"
patronsList: 寄付額ではなく時系列順に並んでいます。上記のリンクから寄付を行ってここにあなたのIDを載せましょう
pleaseDonateToFirefish: Firefish開発への寄付をご検討ください。
pleaseDonateToHost: また、このサーバー {host} の運営者への寄付もご検討ください。
donateHost: '{host} に寄付する'
donateTitle: Firefishを気に入りましたか
sponsors: Firefish の支援者
_nsfw:
respect: "閲覧注意のメディアは隠す"
ignore: "閲覧注意のメディアを隠さない"

View File

@ -874,8 +874,6 @@ _aboutFirefish:
source: "ソースコード"
translation: "Firefishを翻訳"
donate: "Firefishに寄付"
morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰"
patrons: "支援者"
misskeyContributors: フォーク元のMisskeyを作らはった人ら
_mfm:
cheatSheet: "MFMチートシート"

View File

@ -992,10 +992,6 @@ _aboutFirefish:
source: "소스 코드"
translation: "Firefish를 번역하기"
donate: "Firefish에 기부하기"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자"
patronsList: 기부 금액이 아닌 시간 순서로 정렬합니다. 위 링크를 통해 후원하여 당신의 이름을 새겨 보세요!
sponsors: Firefish 스폰서
pleaseDonateToHost: 또한, 이 서버 {host} 의 운영자에게 기부하는 것도 검토하여 주십시오.
pleaseDonateToFirefish: Firefish의 개발에 후원하는 것을 검토하여 주십시오.
donateHost: '{host} 에게 기부하기'

View File

@ -987,8 +987,6 @@ _aboutFirefish:
pleaseDonateToFirefish: Du kan vurdere å donere en slant til Firefish for å støtte
videre utvikling og feilretting.
donateHost: Donér til {host}
morePatrons: Vi er også takknemlige for bidragene fra mange andre som ikke er listet
her. Takk til dere alle! 🥰
contributors: Hovedutviklere
source: Kildekode
allContributors: Alle bidragsytere
@ -996,10 +994,6 @@ _aboutFirefish:
pleaseDonateToHost: Du kan også vurdere å donere til hjemme-tjeneren din, {host},
for å hjelpe dem med driftskostnadene for tjenesten.
about: Firefish ble opprettet av ThatOneCalculator i 2022, basert på Misskey.
sponsors: Firefishs sponsorer
patrons: Firefishs patroner
patronsList: Listen er kronologisk, ikke etter donert beløp. Doner med lenken over
for å få navnet ditt her!
isBot: Denne kontoen er en bot
_nsfw:
respect: Skjul NSFW-merket media

View File

@ -990,9 +990,6 @@ _aboutFirefish:
source: "Kod źródłowy"
translation: "Tłumacz Firefish"
donate: "Przekaż darowiznę na Firefish"
morePatrons: "Naprawdę doceniam wsparcie ze strony wielu niewymienionych tu osób.
Dziękuję! 🥰"
patrons: "Wspierający"
_nsfw:
respect: "Ukrywaj media NSFW"
ignore: "Nie ukrywaj mediów NSFW"

View File

@ -986,12 +986,6 @@ _aboutFirefish:
source: "Исходный код"
translation: "Перевод Firefish"
donate: "Пожертвование на Firefish"
morePatrons: "Большое спасибо и многим другим, кто принял участие в этом проекте!
🥰"
patrons: "Материальная поддержка"
patronsList: Перечислены в хронологическом порядке, а не по размеру пожертвования.
Сделайте взнос по ссылке выше, чтобы ваше имя было здесь!
sponsors: Спонсоры Firefish
donateTitle: Понравился Firefish?
pleaseDonateToFirefish: Пожалуйста, поддержите разработку Firefish.
pleaseDonateToHost: Также не забудьте поддержать ваш домашний сервер {host}, чтобы

View File

@ -1036,9 +1036,6 @@ _aboutFirefish:
source: "Zdrojový kód"
translation: "Preložiť Firefish"
donate: "Podporiť Firefish"
morePatrons: "Takisto oceňujeme podporu mnoých ďalších, ktorí tu nie sú uvedení.
Ďakujeme! 🥰"
patrons: "Prispievatelia"
_nsfw:
respect: "Skryť NSFW médiá"
ignore: "Neskrývať NSFW médiá"

View File

@ -1022,9 +1022,6 @@ _aboutFirefish:
source: "ซอร์สโค้ด"
translation: "รับแปลภาษา Firefish"
donate: "บริจาคให้กับ Firefish"
morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ
ขอขอบคุณ! 🥰"
patrons: "สมาชิกพันธมิตร"
_nsfw:
respect: "ซ่อนสื่อ NSFW"
ignore: "อย่าซ่อนสื่อ NSFW"

View File

@ -1910,14 +1910,9 @@ _preferencesBackups:
updatedAt: 'Güncelleme tarihi: {date} {time}'
cannotLoad: Yüklenemedi
_aboutFirefish:
patronsList: Bağış büyüklüğüne göre değil, kronolojik olarak listelenmiştir. Adınızı
buraya almak için yukarıdaki bağlantıyla bağış yapın!
about: Firefish, 2022'den beri geliştirilmekte olan ThatOneCalculator tarafından
yapılan bir Misskey çatalıdır.
allContributors: Tüm katkıda bulunanlar
patrons: Firefish patronları
morePatrons: Burada listelenmeyen diğer birçok yardımcının desteğini de takdir ediyoruz.
Teşekkür ederim! 🥰
donate: Firefish'e bağışta bulunun
contributors: Ana katkıda bulunanlar
source: Kaynak Kodu
@ -1928,7 +1923,6 @@ _aboutFirefish:
pleaseDonateToHost: İşletme maliyetlerini desteklemek için lütfen ev sunucunuz {host}'a
bağış yapmayı da düşünün.
donateHost: '{ev sahibi} için bağış yapın'
sponsors: Firefish sponsorları
misskeyContributors: Misskey'e katkıda bulunanlar
_weekday:
saturday: Cumartesi

View File

@ -825,17 +825,11 @@ _aboutFirefish:
source: "Вихідний код"
translation: "Перекладати Firefish"
donate: "Пожертвувати Firefish"
morePatrons: "Ми дуже цінуємо підтримку багатьох інших помічників, не перелічених
тут. Дякуємо! 🥰"
patrons: "Підтримали"
patronsList: Перераховані в хронологічному порядку, а не за розміром пожертви. Зробіть
внесок за посиланням вище, щоб ваше ім'я було тут!
donateTitle: Сподобався Firefish?
pleaseDonateToFirefish: Будь ласка, підтримайте розробку Firefish.
pleaseDonateToHost: Також не забудьте підтримати ваш домашній сервер {host}, щоб
допомогти з його операційними витратами.
donateHost: Зробити внесок на рахунок {host}
sponsors: Спонсори Firefish
misskeyContributors: Контрибутори Misskey
_nsfw:
respect: "Приховувати NSFW медіа"

View File

@ -1050,15 +1050,10 @@ _aboutFirefish:
source: "Mã nguồn"
translation: "Dịch Firefish"
donate: "Ủng hộ Firefish"
morePatrons: "Chúng tôi cũng trân trọng sự hỗ trợ của nhiều người đóng góp khác
không được liệt kê ở đây. Cảm ơn! 🥰"
patrons: "Người ủng hộ"
patronsList: Liệt kê theo thứ tự, không theo số tiền ủng hộ. Hãy để tên bạn ở đây!
donateTitle: Thích Firefish?
pleaseDonateToFirefish: Hãy cân nhắc ủng hộ Firefish phát triển.
donateHost: Ủng hộ {host}
pleaseDonateToHost: Cũng như ủng hộ chi phí vận hành máy chủ {host} của bạn.
sponsors: Nhà tài trợ Firefish
misskeyContributors: Người đóng góp Misskey
_nsfw:
respect: "Ẩn nội dung NSFW"

View File

@ -996,10 +996,6 @@ _aboutFirefish:
source: "源代码"
translation: "翻译 Firefish"
donate: "赞助 Firefish"
morePatrons: "还有很多其它的人也在支持我们,非常感谢🥰"
patrons: "Firefish 赞助者"
patronsList: 按时间顺序而不是捐赠金额排列。通过上面的链接捐款,让您的名字出现在这里!
sponsors: Firefish 赞助者们
donateTitle: 喜欢 Firefish 吗?
pleaseDonateToFirefish: 请考虑赞助 Firefish 以支持其开发。
pleaseDonateToHost: 也请考虑赞助您的主服务器 {host},以帮助支持其运营成本。

View File

@ -991,10 +991,6 @@ _aboutFirefish:
source: "原始碼"
translation: "翻譯Firefish"
donate: "贊助Firefish"
morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰"
patrons: "贊助者"
patronsList: 按時間順序列出,而不是按贊助規模列出。使用上面的連結贊助,在這裡獲得顯示您名字的機會!
sponsors: Firefish 贊助者們
donateTitle: 覺得 Firefish 棒嗎?
pleaseDonateToFirefish: 請考慮向 Firefish 贊助以支持其發展。
pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。

View File

@ -1,11 +1,11 @@
{
"name": "firefish",
"version": "20240405",
"version": "20240421",
"repository": {
"type": "git",
"url": "https://firefish.dev/firefish/firefish.git"
},
"packageManager": "pnpm@8.15.6",
"packageManager": "pnpm@8.15.7",
"private": true,
"scripts": {
"rebuild": "pnpm run clean && pnpm run build",
@ -26,7 +26,9 @@
"debug": "pnpm run build:debug && pnpm run start",
"build:debug": "pnpm run clean && pnpm node ./scripts/dev-build.mjs && pnpm run gulp",
"mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha",
"test": "pnpm run test:ts && pnpm run test:rs",
"test:ts": "pnpm run mocha",
"test:rs": "cargo test",
"format": "pnpm run format:ts; pnpm run format:rs",
"format:ts": "pnpm -r --parallel run format",
"format:rs": "cargo fmt --all --",
@ -36,11 +38,11 @@
"clean-all": "pnpm run clean && pnpm run clean-cargo && pnpm run clean-npm"
},
"dependencies": {
"js-yaml": "4.1.0",
"gulp": "4.0.2",
"gulp-cssnano": "2.1.3",
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0"
"gulp-terser": "2.1.0",
"js-yaml": "4.1.0"
},
"devDependencies": {
"@biomejs/biome": "1.6.4",
@ -50,7 +52,7 @@
"@biomejs/cli-linux-x64": "^1.6.4",
"@types/node": "20.12.7",
"execa": "8.0.1",
"pnpm": "8.15.6",
"pnpm": "8.15.7",
"typescript": "5.4.5"
}
}

View File

@ -4,6 +4,7 @@ This directory contains all of the packages Firefish uses.
- `backend`: Main backend code written in TypeScript for NodeJS
- `backend-rs`: Backend code written in Rust, bound to NodeJS by [NAPI-RS](https://napi.rs/)
- `macro-rs`: Procedural macros for backend-rs
- `client`: Web interface written in Vue3 and TypeScript
- `sw`: Web [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) written in TypeScript
- `firefish-js`: TypeScript SDK for both backend and client

View File

@ -17,11 +17,14 @@ macro_rs = { workspace = true }
napi = { workspace = true, optional = true, default-features = false, features = ["napi9", "tokio_rt", "chrono_date", "serde-json"] }
napi-derive = { workspace = true, optional = true }
argon2 = { workspace = true, features = ["std"] }
async-trait = { workspace = true }
basen = { workspace = true }
bcrypt = { workspace = true }
cfg-if = { workspace = true }
chrono = { workspace = true }
cuid2 = { workspace = true }
emojis = { workspace = true }
idna = { workspace = true }
jsonschema = { workspace = true }
once_cell = { workspace = true }

View File

@ -7,9 +7,9 @@ SRC += $(call recursive_wildcard, src, *)
.PHONY: regenerate-entities
regenerate-entities:
sea-orm-cli generate entity \
--output-dir='src/model/entity' \
--database-url='postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:25432/$(POSTGRES_DB)' \
--date-time-crate='chrono' \
--output-dir='src/model/entity' \
--database-url='postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@localhost:25432/$(POSTGRES_DB)' \
--date-time-crate='chrono' \
--model-extra-attributes='NAPI_EXTRA_ATTR_PLACEHOLDER' && \
for file in src/model/entity/*; do \
base=$$(basename -- "$${file}"); \
@ -17,7 +17,7 @@ regenerate-entities:
attribute=$$(printf 'cfg_attr(feature = "napi", napi_derive::napi(object, js_name = "%s", use_nullable = true))' "$${jsname}"); \
sed -i "s/NAPI_EXTRA_ATTR_PLACEHOLDER/$${attribute}/" "$${file}"; \
done
sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi)]/' \
sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]/' \
src/model/entity/sea_orm_active_enums.rs
cargo fmt --all --

View File

@ -3,6 +3,16 @@
/* auto-generated by NAPI-RS */
export interface EnvConfig {
onlyQueue: boolean
onlyServer: boolean
noDaemons: boolean
disableClustering: boolean
verbose: boolean
withLogTime: boolean
slow: boolean
}
export function readEnvironmentConfig(): EnvConfig
export interface ServerConfig {
url: string
port: number
@ -118,7 +128,8 @@ export interface Acct {
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
export interface NoteLike {
/** TODO: handle name collisions better */
export interface NoteLikeForCheckWordMute {
fileIds: Array<string>
userId: string | null
text: string | null
@ -126,15 +137,52 @@ export interface NoteLike {
renoteId: string | null
replyId: string | null
}
export function checkWordMute(note: NoteLike, mutedWordLists: Array<Array<string>>, mutedPatterns: Array<string>): Promise<boolean>
export function checkWordMute(note: NoteLikeForCheckWordMute, mutedWordLists: Array<Array<string>>, mutedPatterns: Array<string>): Promise<boolean>
export function getFullApAccount(username: string, host?: string | undefined | null): string
export function isSelfHost(host?: string | undefined | null): boolean
export function isSameOrigin(uri: string): boolean
export function extractHost(uri: string): string
export function toPuny(host: string): string
export function isUnicodeEmoji(s: string): boolean
export function sqlLikeEscape(src: string): string
export function safeForSql(src: string): boolean
/** Convert milliseconds to a human readable string */
export function formatMilliseconds(milliseconds: number): string
/** TODO: handle name collisions better */
export interface NoteLikeForGetNoteSummary {
fileIds: Array<string>
text: string | null
cw: string | null
hasPoll: boolean
}
export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
export function toMastodonId(firefishId: string): string | null
export function fromMastodonId(mastodonId: string): string | null
export function fetchMeta(useCache: boolean): Promise<Meta>
export interface PugArgs {
img: string | null
title: string
instanceName: string
desc: string | null
icon: string | null
splashIcon: string | null
themeColor: string | null
randomMotd: string
privateMode: boolean | null
}
export function metaToPugArgs(meta: Meta): PugArgs
export function nyaify(text: string, lang?: string | undefined | null): string
export function hashPassword(password: string): string
export function verifyPassword(password: string, hash: string): boolean
export function isOldPasswordAlgorithm(hash: string): boolean
export interface DecodedReaction {
reaction: string
name: string | null
host: string | null
}
export function decodeReaction(reaction: string): DecodedReaction
export function countReactions(reactions: Record<string, number>): Record<string, number>
export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise<string>
export interface AbuseUserReport {
id: string
createdAt: Date
@ -300,6 +348,7 @@ export interface DriveFile {
webpublicType: string | null
requestHeaders: Json | null
requestIp: string | null
usageHint: DriveFileUsageHintEnum | null
}
export interface DriveFolder {
id: string
@ -725,81 +774,85 @@ export interface ReplyMuting {
muteeId: string
muterId: string
}
export const enum AntennaSrcEnum {
All = 0,
Group = 1,
Home = 2,
Instances = 3,
List = 4,
Users = 5
export enum AntennaSrcEnum {
All = 'all',
Group = 'group',
Home = 'home',
Instances = 'instances',
List = 'list',
Users = 'users'
}
export const enum MutedNoteReasonEnum {
Manual = 0,
Other = 1,
Spam = 2,
Word = 3
export enum DriveFileUsageHintEnum {
UserAvatar = 'userAvatar',
UserBanner = 'userBanner'
}
export const enum NoteVisibilityEnum {
Followers = 0,
Hidden = 1,
Home = 2,
Public = 3,
Specified = 4
export enum MutedNoteReasonEnum {
Manual = 'manual',
Other = 'other',
Spam = 'spam',
Word = 'word'
}
export const enum NotificationTypeEnum {
App = 0,
Follow = 1,
FollowRequestAccepted = 2,
GroupInvited = 3,
Mention = 4,
PollEnded = 5,
PollVote = 6,
Quote = 7,
Reaction = 8,
ReceiveFollowRequest = 9,
Renote = 10,
Reply = 11
export enum NoteVisibilityEnum {
Followers = 'followers',
Hidden = 'hidden',
Home = 'home',
Public = 'public',
Specified = 'specified'
}
export const enum PageVisibilityEnum {
Followers = 0,
Public = 1,
Specified = 2
export enum NotificationTypeEnum {
App = 'app',
Follow = 'follow',
FollowRequestAccepted = 'followRequestAccepted',
GroupInvited = 'groupInvited',
Mention = 'mention',
PollEnded = 'pollEnded',
PollVote = 'pollVote',
Quote = 'quote',
Reaction = 'reaction',
ReceiveFollowRequest = 'receiveFollowRequest',
Renote = 'renote',
Reply = 'reply'
}
export const enum PollNotevisibilityEnum {
Followers = 0,
Home = 1,
Public = 2,
Specified = 3
export enum PageVisibilityEnum {
Followers = 'followers',
Public = 'public',
Specified = 'specified'
}
export const enum RelayStatusEnum {
Accepted = 0,
Rejected = 1,
Requesting = 2
export enum PollNotevisibilityEnum {
Followers = 'followers',
Home = 'home',
Public = 'public',
Specified = 'specified'
}
export const enum UserEmojimodpermEnum {
Add = 0,
Full = 1,
Mod = 2,
Unauthorized = 3
export enum RelayStatusEnum {
Accepted = 'accepted',
Rejected = 'rejected',
Requesting = 'requesting'
}
export const enum UserProfileFfvisibilityEnum {
Followers = 0,
Private = 1,
Public = 2
export enum UserEmojimodpermEnum {
Add = 'add',
Full = 'full',
Mod = 'mod',
Unauthorized = 'unauthorized'
}
export const enum UserProfileMutingnotificationtypesEnum {
App = 0,
Follow = 1,
FollowRequestAccepted = 2,
GroupInvited = 3,
Mention = 4,
PollEnded = 5,
PollVote = 6,
Quote = 7,
Reaction = 8,
ReceiveFollowRequest = 9,
Renote = 10,
Reply = 11
export enum UserProfileFfvisibilityEnum {
Followers = 'followers',
Private = 'private',
Public = 'public'
}
export enum UserProfileMutingnotificationtypesEnum {
App = 'app',
Follow = 'follow',
FollowRequestAccepted = 'followRequestAccepted',
GroupInvited = 'groupInvited',
Mention = 'mention',
PollEnded = 'pollEnded',
PollVote = 'pollVote',
Quote = 'quote',
Reaction = 'reaction',
ReceiveFollowRequest = 'receiveFollowRequest',
Renote = 'renote',
Reply = 'reply'
}
export interface Signin {
id: string

View File

@ -310,8 +310,9 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, toMastodonId, fromMastodonId, nyaify, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
module.exports.readEnvironmentConfig = readEnvironmentConfig
module.exports.readServerConfig = readServerConfig
module.exports.stringToAcct = stringToAcct
module.exports.acctToString = acctToString
@ -321,10 +322,24 @@ module.exports.isSelfHost = isSelfHost
module.exports.isSameOrigin = isSameOrigin
module.exports.extractHost = extractHost
module.exports.toPuny = toPuny
module.exports.isUnicodeEmoji = isUnicodeEmoji
module.exports.sqlLikeEscape = sqlLikeEscape
module.exports.safeForSql = safeForSql
module.exports.formatMilliseconds = formatMilliseconds
module.exports.getNoteSummary = getNoteSummary
module.exports.toMastodonId = toMastodonId
module.exports.fromMastodonId = fromMastodonId
module.exports.fetchMeta = fetchMeta
module.exports.metaToPugArgs = metaToPugArgs
module.exports.nyaify = nyaify
module.exports.hashPassword = hashPassword
module.exports.verifyPassword = verifyPassword
module.exports.isOldPasswordAlgorithm = isOldPasswordAlgorithm
module.exports.decodeReaction = decodeReaction
module.exports.countReactions = countReactions
module.exports.toDbReaction = toDbReaction
module.exports.AntennaSrcEnum = AntennaSrcEnum
module.exports.DriveFileUsageHintEnum = DriveFileUsageHintEnum
module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum
module.exports.NoteVisibilityEnum = NoteVisibilityEnum
module.exports.NotificationTypeEnum = NotificationTypeEnum

View File

@ -33,8 +33,8 @@
},
"scripts": {
"artifacts": "napi artifacts",
"build": "napi build --features napi --platform --release ./built/",
"build:debug": "napi build --features napi --platform ./built/",
"build": "napi build --features napi --no-const-enum --platform --release ./built/",
"build:debug": "napi build --features napi --no-const-enum --platform ./built/",
"prepublishOnly": "napi prepublish -t npm",
"test": "pnpm run cargo:test && pnpm run build:debug && ava",
"universal": "napi universal",

View File

@ -0,0 +1,27 @@
// FIXME: Are these options used?
#[crate::export(object)]
pub struct EnvConfig {
pub only_queue: bool,
pub only_server: bool,
pub no_daemons: bool,
pub disable_clustering: bool,
pub verbose: bool,
pub with_log_time: bool,
pub slow: bool,
}
#[crate::export]
pub fn read_environment_config() -> EnvConfig {
let node_env = std::env::var("NODE_ENV").unwrap_or_default().to_lowercase();
let is_testing = node_env == "test";
EnvConfig {
only_queue: std::env::var("MK_ONLY_QUEUE").is_ok(),
only_server: std::env::var("MK_ONLY_SERVER").is_ok(),
no_daemons: is_testing || std::env::var("MK_NO_DAEMONS").is_ok(),
disable_clustering: is_testing || std::env::var("MK_DISABLE_CLUSTERING").is_ok(),
verbose: std::env::var("MK_VERBOSE").is_ok(),
with_log_time: std::env::var("MK_WITH_LOG_TIME").is_ok(),
slow: std::env::var("MK_SLOW").is_ok(),
}
}

View File

@ -1 +1,2 @@
pub mod environment;
pub mod server;

View File

@ -4,7 +4,8 @@ use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::{prelude::*, QuerySelect};
#[crate::export(object)]
/// TODO: handle name collisions better
#[crate::export(object, js_name = "NoteLikeForCheckWordMute")]
pub struct NoteLike {
pub file_ids: Vec<String>,
pub user_id: Option<String>,

View File

@ -0,0 +1,31 @@
#[inline]
#[crate::export]
pub fn is_unicode_emoji(s: &str) -> bool {
emojis::get(s).is_some()
}
#[cfg(test)]
mod unit_test {
use super::is_unicode_emoji;
#[test]
fn test_unicode_emoji_check() {
assert!(is_unicode_emoji(""));
assert!(is_unicode_emoji("👍"));
assert!(is_unicode_emoji(""));
assert!(is_unicode_emoji("♥️"));
assert!(is_unicode_emoji("❤️"));
assert!(is_unicode_emoji("💙"));
assert!(is_unicode_emoji("🩷"));
assert!(is_unicode_emoji("🖖🏿"));
assert!(is_unicode_emoji("🏃‍➡️"));
assert!(is_unicode_emoji("👩‍❤️‍👨"));
assert!(is_unicode_emoji("👩‍👦‍👦"));
assert!(is_unicode_emoji("🏳️‍🌈"));
assert!(!is_unicode_emoji("⭐⭐"));
assert!(!is_unicode_emoji("x"));
assert!(!is_unicode_emoji("\t"));
assert!(!is_unicode_emoji(":meow_aww:"));
}
}

View File

@ -0,0 +1,36 @@
#[crate::export]
pub fn sql_like_escape(src: &str) -> String {
src.replace('%', r"\%").replace('_', r"\_")
}
#[crate::export]
pub fn safe_for_sql(src: &str) -> bool {
!src.contains([
'\0', '\x08', '\x09', '\x1a', '\n', '\r', '"', '\'', '\\', '%',
])
}
#[cfg(test)]
mod unit_test {
use super::{safe_for_sql, sql_like_escape};
use pretty_assertions::assert_eq;
#[test]
fn sql_like_escape_test() {
assert_eq!(sql_like_escape(""), "");
assert_eq!(sql_like_escape("abc"), "abc");
assert_eq!(sql_like_escape("a%bc"), r"a\%bc");
assert_eq!(sql_like_escape("a呼%吸bc"), r"a呼\%吸bc");
assert_eq!(sql_like_escape("a呼%吸b%_c"), r"a呼\%吸b\%\_c");
assert_eq!(sql_like_escape("_اللغة العربية"), r"\_اللغة العربية");
}
#[test]
fn safe_for_sql_test() {
assert!(safe_for_sql("123"));
assert!(safe_for_sql("人間"));
assert!(!safe_for_sql("人間\x09"));
assert!(!safe_for_sql("abc\ndef"));
assert!(!safe_for_sql("%something%"));
}
}

View File

@ -0,0 +1,46 @@
/// Convert milliseconds to a human readable string
#[crate::export]
pub fn format_milliseconds(milliseconds: u32) -> String {
let mut seconds = milliseconds / 1000;
let mut minutes = seconds / 60;
let mut hours = minutes / 60;
let days = hours / 24;
seconds %= 60;
minutes %= 60;
hours %= 24;
let mut buf: Vec<String> = vec![];
if days > 0 {
buf.push(format!("{} day(s)", days));
}
if hours > 0 {
buf.push(format!("{} hour(s)", hours));
}
if minutes > 0 {
buf.push(format!("{} minute(s)", minutes));
}
if seconds > 0 {
buf.push(format!("{} second(s)", seconds));
}
buf.join(", ")
}
#[cfg(test)]
mod unit_test {
use super::format_milliseconds;
use pretty_assertions::assert_eq;
#[test]
fn format_milliseconds_test() {
assert_eq!(format_milliseconds(1000), "1 second(s)");
assert_eq!(format_milliseconds(1387938), "23 minute(s), 7 second(s)");
assert_eq!(format_milliseconds(34200457), "9 hour(s), 30 minute(s)");
assert_eq!(
format_milliseconds(998244353),
"11 day(s), 13 hour(s), 17 minute(s), 24 second(s)"
);
}
}

View File

@ -0,0 +1,90 @@
/// TODO: handle name collisions better
#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")]
pub struct NoteLike {
pub file_ids: Vec<String>,
pub text: Option<String>,
pub cw: Option<String>,
pub has_poll: bool,
}
#[crate::export]
pub fn get_note_summary(note: NoteLike) -> String {
let mut buf: Vec<String> = vec![];
if let Some(cw) = note.cw {
buf.push(cw)
} else if let Some(text) = note.text {
buf.push(text)
}
match note.file_ids.len() {
0 => (),
1 => buf.push("📎".to_string()),
n => buf.push(format!("📎 ({})", n)),
};
if note.has_poll {
buf.push("📊".to_string())
}
buf.join(" ")
}
#[cfg(test)]
mod unit_test {
use super::{get_note_summary, NoteLike};
use pretty_assertions::assert_eq;
#[test]
fn test_note_summary() {
let note = NoteLike {
file_ids: vec![],
text: Some("Hello world!".to_string()),
cw: None,
has_poll: false,
};
assert_eq!(get_note_summary(note), "Hello world!");
let note_with_cw = NoteLike {
file_ids: vec![],
text: Some("Hello world!".to_string()),
cw: Some("Content warning".to_string()),
has_poll: false,
};
assert_eq!(get_note_summary(note_with_cw), "Content warning");
let note_with_file_and_cw = NoteLike {
file_ids: vec!["9s7fmcqogiq4igin".to_string()],
text: None,
cw: Some("Selfie, no ec".to_string()),
has_poll: false,
};
assert_eq!(get_note_summary(note_with_file_and_cw), "Selfie, no ec 📎");
let note_with_files_only = NoteLike {
file_ids: vec![
"9s7fmcqogiq4igin".to_string(),
"9s7qrld5u14cey98".to_string(),
"9s7gebs5zgts4kca".to_string(),
"9s5z3e4vefqd29ee".to_string(),
],
text: None,
cw: None,
has_poll: false,
};
assert_eq!(get_note_summary(note_with_files_only), "📎 (4)");
let note_all = NoteLike {
file_ids: vec![
"9s7fmcqogiq4igin".to_string(),
"9s7qrld5u14cey98".to_string(),
"9s7gebs5zgts4kca".to_string(),
"9s5z3e4vefqd29ee".to_string(),
],
text: Some("Hello world!".to_string()),
cw: Some("Content warning".to_string()),
has_poll: true,
};
assert_eq!(get_note_summary(note_all), "Content warning 📎 (4) 📊");
}
}

View File

@ -0,0 +1,83 @@
use crate::database::db_conn;
use crate::model::entity::meta;
use rand::prelude::*;
use sea_orm::{prelude::*, ActiveValue};
use std::sync::Mutex;
type Meta = meta::Model;
static CACHE: Mutex<Option<Meta>> = Mutex::new(None);
fn update_cache(meta: &Meta) {
let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
}
#[crate::export]
pub async fn fetch_meta(use_cache: bool) -> Result<Meta, DbErr> {
// try using cache
if use_cache {
if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) {
return Ok(cache);
}
}
// try fetching from db
let db = db_conn().await?;
let meta = meta::Entity::find().one(db).await?;
if let Some(meta) = meta {
update_cache(&meta);
return Ok(meta);
}
// create a new meta object and insert into db
let meta = meta::Entity::insert(meta::ActiveModel {
id: ActiveValue::Set("x".to_owned()),
..Default::default()
})
.exec_with_returning(db)
.await?;
update_cache(&meta);
Ok(meta)
}
#[crate::export(object)]
pub struct PugArgs {
pub img: Option<String>,
pub title: String,
pub instance_name: String,
pub desc: Option<String>,
pub icon: Option<String>,
pub splash_icon: Option<String>,
pub theme_color: Option<String>,
pub random_motd: String,
pub private_mode: Option<bool>,
}
#[crate::export]
pub fn meta_to_pug_args(meta: Meta) -> PugArgs {
let mut rng = rand::thread_rng();
let splash_icon = meta
.custom_splash_icons
.choose(&mut rng)
.map(|s| s.to_owned())
.or_else(|| meta.icon_url.to_owned());
let random_motd = meta
.custom_motd
.choose(&mut rng)
.map(|s| s.to_owned())
.unwrap_or_else(|| "Loading...".to_owned());
let name = meta.name.unwrap_or_else(|| "Firefish".to_owned());
PugArgs {
img: meta.banner_url,
title: name.clone(),
instance_name: name.clone(),
desc: meta.description,
icon: meta.icon_url,
splash_icon,
theme_color: meta.theme_color,
random_motd,
private_mode: meta.private_mode,
}
}

View File

@ -1,5 +1,12 @@
pub mod acct;
pub mod check_word_mute;
pub mod convert_host;
pub mod emoji;
pub mod escape_sql;
pub mod format_milliseconds;
pub mod get_note_summary;
pub mod mastodon_id;
pub mod meta;
pub mod nyaify;
pub mod password;
pub mod reaction;

View File

@ -0,0 +1,69 @@
use argon2::{
password_hash,
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
#[crate::export]
pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
let salt = SaltString::generate(&mut OsRng);
Ok(Argon2::default()
.hash_password(password.as_bytes(), &salt)?
.to_string())
}
#[derive(thiserror::Error, Debug)]
pub enum VerifyError {
#[error("An error occured while bcrypt verification: {0}")]
BcryptError(#[from] bcrypt::BcryptError),
#[error("Invalid argon2 password hash: {0}")]
InvalidArgon2Hash(#[from] password_hash::Error),
#[error("An error occured while argon2 verification: {0}")]
Argon2Error(#[from] argon2::Error),
}
#[crate::export]
pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError> {
if is_old_password_algorithm(hash) {
Ok(bcrypt::verify(password, hash)?)
} else {
let parsed_hash = PasswordHash::new(hash)?;
Ok(Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
}
#[inline]
#[crate::export]
pub fn is_old_password_algorithm(hash: &str) -> bool {
// bcrypt hashes start with $2[ab]$
hash.starts_with("$2")
}
#[cfg(test)]
mod unit_test {
use super::{hash_password, is_old_password_algorithm, verify_password};
#[test]
fn verify_password_test() {
let password = "omWc*%sD^fn7o2cXmc9e2QasBdrbRuhNB*gx!J5";
let hash = hash_password(password).unwrap();
assert!(verify_password(password, hash.as_str()).unwrap());
let argon2_hash = "$argon2id$v=19$m=19456,t=2,p=1$jty3puDFd4ENv/lgHn3ROQ$kRHDdEoVv2rruvnF731E74NxnYlvj5FMgePdGIIq3Jk";
let argon2_invalid_hash = "$argon2id$v=19$m=19456,t=2,p=1$jty3puDFd4ENv/lgHn3ROQ$kRHDdEoVv2rruvnF731E74NxnYlvj4FMgePdGIIq3Jk";
let bcrypt_hash = "$2a$12$WzUc.20jgbHmQjUMqTr8vOhKqYbS1BUvubapv/GLjCK1IN.h4e4la";
let bcrypt_invalid_hash = "$2a$12$WzUc.20jgbHmQjUMqTr7vOhKqYbS1BUvubapv/GLjCK1IN.h4e4la";
assert!(!is_old_password_algorithm(argon2_hash));
assert!(is_old_password_algorithm(bcrypt_hash));
assert!(verify_password(password, argon2_hash).unwrap());
assert!(verify_password(password, bcrypt_hash).unwrap());
assert!(!verify_password(password, argon2_invalid_hash).unwrap());
assert!(!verify_password(password, bcrypt_invalid_hash).unwrap());
}
}

View File

@ -0,0 +1,191 @@
use crate::database::db_conn;
use crate::misc::{convert_host::to_puny, emoji::is_unicode_emoji, meta::fetch_meta};
use crate::model::entity::emoji;
use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::prelude::*;
use std::collections::HashMap;
#[derive(PartialEq, Debug)]
#[crate::export(object)]
pub struct DecodedReaction {
pub reaction: String,
pub name: Option<String>,
pub host: Option<String>,
}
#[crate::export]
pub fn decode_reaction(reaction: &str) -> DecodedReaction {
// Misskey allows you to include "+" and "-" in emoji shortcodes
// MFM spec: https://github.com/misskey-dev/mfm.js/blob/6aaf68089023c6adebe44123eebbc4dcd75955e0/docs/syntax.md?plain=1#L583
// Misskey's implementation: https://github.com/misskey-dev/misskey/blob/bba3097765317cbf95d09627961b5b5dce16a972/packages/backend/src/core/ReactionService.ts#L68
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^:([0-9A-Za-z_+-]+)(?:@([0-9A-Za-z_.-]+))?:$").unwrap());
if let Some(captures) = RE.captures(reaction) {
let name = &captures[1];
let host = captures.get(2).map(|s| s.as_str());
DecodedReaction {
reaction: format!(":{}@{}:", name, host.unwrap_or(".")),
name: Some(name.to_owned()),
host: host.map(|s| s.to_owned()),
}
} else {
DecodedReaction {
reaction: reaction.to_owned(),
name: None,
host: None,
}
}
}
#[crate::export]
pub fn count_reactions(reactions: &HashMap<String, u32>) -> HashMap<String, u32> {
let mut res = HashMap::<String, u32>::new();
for (reaction, count) in reactions.iter() {
if count > &0 {
let decoded = decode_reaction(reaction).reaction;
let total = res.entry(decoded).or_insert(0);
*total += count;
}
}
res
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Idna error: {0}")]
IdnaError(#[from] idna::Errors),
#[error("Database error: {0}")]
DbError(#[from] DbErr),
}
#[crate::export]
pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Result<String, Error> {
if let Some(reaction) = reaction {
// FIXME: Is it okay to do this only here?
// This was introduced in https://firefish.dev/firefish/firefish/-/commit/af730e75b6fc1a57ca680ce83459d7e433b130cf
if reaction.contains('❤') || reaction.contains("♥️") {
return Ok("❤️".to_owned());
}
if is_unicode_emoji(reaction) {
return Ok(reaction.to_owned());
}
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^:([0-9A-Za-z_+-]+)(?:@\.)?:$").unwrap());
if let Some(captures) = RE.captures(reaction) {
let name = &captures[1];
let db = db_conn().await?;
if let Some(host) = host {
// remote emoji
let ascii_host = to_puny(host)?;
// TODO: Does SeaORM have the `exists` method?
if emoji::Entity::find()
.filter(emoji::Column::Name.eq(name))
.filter(emoji::Column::Host.eq(&ascii_host))
.one(db)
.await?
.is_some()
{
return Ok(format!(":{name}@{ascii_host}:"));
}
} else {
// local emoji
// TODO: Does SeaORM have the `exists` method?
if emoji::Entity::find()
.filter(emoji::Column::Name.eq(name))
.filter(emoji::Column::Host.is_null())
.one(db)
.await?
.is_some()
{
return Ok(format!(":{name}:"));
}
}
};
};
Ok(fetch_meta(true).await?.default_reaction)
}
#[cfg(test)]
mod unit_test {
use super::{decode_reaction, DecodedReaction};
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn test_decode_reaction() {
let unicode_emoji_1 = DecodedReaction {
reaction: "".to_string(),
name: None,
host: None,
};
let unicode_emoji_2 = DecodedReaction {
reaction: "🩷".to_string(),
name: None,
host: None,
};
assert_eq!(decode_reaction(""), unicode_emoji_1);
assert_eq!(decode_reaction("🩷"), unicode_emoji_2);
assert_ne!(decode_reaction(""), unicode_emoji_2);
assert_ne!(decode_reaction("🩷"), unicode_emoji_1);
let unicode_emoji_3 = DecodedReaction {
reaction: "🖖🏿".to_string(),
name: None,
host: None,
};
assert_eq!(decode_reaction("🖖🏿"), unicode_emoji_3);
let local_emoji = DecodedReaction {
reaction: ":meow_melt_tears@.:".to_string(),
name: Some("meow_melt_tears".to_string()),
host: None,
};
assert_eq!(decode_reaction(":meow_melt_tears:"), local_emoji);
let remote_emoji_1 = DecodedReaction {
reaction: ":meow_uwu@some-domain.example.org:".to_string(),
name: Some("meow_uwu".to_string()),
host: Some("some-domain.example.org".to_string()),
};
assert_eq!(
decode_reaction(":meow_uwu@some-domain.example.org:"),
remote_emoji_1
);
let remote_emoji_2 = DecodedReaction {
reaction: ":C++23@xn--eckwd4c7c.example.org:".to_string(),
name: Some("C++23".to_string()),
host: Some("xn--eckwd4c7c.example.org".to_string()),
};
assert_eq!(
decode_reaction(":C++23@xn--eckwd4c7c.example.org:"),
remote_emoji_2
);
let invalid_reaction_1 = DecodedReaction {
reaction: ":foo".to_string(),
name: None,
host: None,
};
assert_eq!(decode_reaction(":foo"), invalid_reaction_1);
let invalid_reaction_2 = DecodedReaction {
reaction: ":foo&@example.com:".to_string(),
name: None,
host: None,
};
assert_eq!(decode_reaction(":foo&@example.com:"), invalid_reaction_2);
}
}

View File

@ -1,5 +1,6 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
use super::sea_orm_active_enums::DriveFileUsageHintEnum;
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
@ -52,6 +53,8 @@ pub struct Model {
pub request_headers: Option<Json>,
#[sea_orm(column_name = "requestIp")]
pub request_ip: Option<String>,
#[sea_orm(column_name = "usageHint")]
pub usage_hint: Option<DriveFileUsageHintEnum>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -21,6 +21,7 @@ pub struct Model {
#[sea_orm(column_type = "Text", nullable)]
pub text: Option<String>,
pub name: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub cw: Option<String>,
#[sea_orm(column_name = "userId")]
pub user_id: String,

View File

@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")]
pub enum AntennaSrcEnum {
#[sea_orm(string_value = "all")]
@ -22,7 +22,21 @@ pub enum AntennaSrcEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "drive_file_usage_hint_enum"
)]
pub enum DriveFileUsageHintEnum {
#[sea_orm(string_value = "userAvatar")]
UserAvatar,
#[sea_orm(string_value = "userBanner")]
UserBanner,
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -40,7 +54,7 @@ pub enum MutedNoteReasonEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -60,7 +74,7 @@ pub enum NoteVisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -94,7 +108,7 @@ pub enum NotificationTypeEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -110,7 +124,7 @@ pub enum PageVisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -128,7 +142,7 @@ pub enum PollNotevisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")]
pub enum RelayStatusEnum {
#[sea_orm(string_value = "accepted")]
@ -140,7 +154,7 @@ pub enum RelayStatusEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -158,7 +172,7 @@ pub enum UserEmojimodpermEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -174,7 +188,7 @@ pub enum UserProfileFfvisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",

View File

@ -22,9 +22,9 @@
"@swc/core-android-arm64": "1.3.11"
},
"dependencies": {
"@bull-board/api": "5.15.3",
"@bull-board/koa": "5.15.3",
"@bull-board/ui": "5.15.3",
"@bull-board/api": "5.15.5",
"@bull-board/koa": "5.15.5",
"@bull-board/ui": "5.15.5",
"@discordapp/twemoji": "^15.0.3",
"@koa/cors": "5.0.0",
"@koa/multer": "3.0.2",
@ -33,15 +33,12 @@
"@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.11.0",
"@sinonjs/fake-timers": "11.2.2",
"@twemoji/parser": "^15.1.1",
"adm-zip": "0.5.10",
"ajv": "8.12.0",
"archiver": "7.0.1",
"argon2": "^0.40.1",
"aws-sdk": "2.1597.0",
"aws-sdk": "2.1599.0",
"axios": "^1.6.8",
"backend-rs": "workspace:*",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"bull": "4.12.2",
"cacheable-lookup": "TheEssem/cacheable-lookup",
@ -54,7 +51,7 @@
"date-fns": "3.6.0",
"decompress": "^4.2.1",
"deep-email-validator": "0.1.21",
"deepl-node": "1.12.0",
"deepl-node": "1.13.0",
"escape-regexp": "0.0.1",
"feed": "4.2.2",
"file-type": "19.0.0",
@ -100,7 +97,7 @@
"punycode": "2.3.1",
"pureimage": "0.4.13",
"qrcode": "1.5.3",
"qs": "6.12.0",
"qs": "6.12.1",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"redis-semaphore": "5.5.1",
@ -130,7 +127,6 @@
"@swc/cli": "0.3.12",
"@swc/core": "1.4.13",
"@types/adm-zip": "^0.5.5",
"@types/bcryptjs": "2.4.6",
"@types/color-convert": "^2.0.3",
"@types/content-disposition": "^0.5.8",
"@types/escape-regexp": "0.0.3",

View File

@ -3,7 +3,7 @@ import chalk from "chalk";
import Xev from "xev";
import Logger from "@/services/logger.js";
import { envOption } from "../env.js";
import { envOption } from "@/config/index.js";
import { inspect } from "node:util";
// for typeorm
@ -76,9 +76,7 @@ cluster.on("exit", (worker) => {
});
// Display detail of unhandled promise rejection
if (!envOption.quiet) {
process.on("unhandledRejection", console.dir);
}
process.on("unhandledRejection", console.dir);
// Display detail of uncaught exception
process.on("uncaughtException", (err) => {

View File

@ -10,7 +10,7 @@ import semver from "semver";
import Logger from "@/services/logger.js";
import loadConfig from "@/config/load.js";
import type { Config } from "@/config/types.js";
import { envOption } from "@/env.js";
import { envOption } from "@/config/index.js";
import { showMachineInfo } from "@/misc/show-machine-info.js";
import { db, initDb } from "@/db/postgre.js";
import { inspect } from "node:util";
@ -28,58 +28,56 @@ const bootLogger = logger.createSubLogger("boot", "magenta", false);
const themeColor = chalk.hex("#31748f");
function greet() {
if (!envOption.quiet) {
//#region Firefish logo
console.log(
themeColor(
"██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄ ",
),
);
console.log(
themeColor(
"██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄ ",
),
);
console.log(
themeColor(
"█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄ ",
),
);
console.log(
themeColor(
"██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄ ",
),
);
console.log(
themeColor(
"██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █ ",
),
);
console.log(
themeColor(
"╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀ ",
),
);
//#endregion
//#region Firefish logo
console.log(
themeColor(
"██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄ ",
),
);
console.log(
themeColor(
"██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄ ",
),
);
console.log(
themeColor(
"█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄ ",
),
);
console.log(
themeColor(
"██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄ ",
),
);
console.log(
themeColor(
"██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █ ",
),
);
console.log(
themeColor(
"╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀ ",
),
);
//#endregion
console.log(
" Firefish is an open-source decentralized microblogging platform.",
);
console.log(
chalk.rgb(
255,
136,
0,
)(
" If you like Firefish, please consider contributing to the repo. https://firefish.dev/firefish/firefish",
),
);
console.log(
" Firefish is an open-source decentralized microblogging platform.",
);
console.log(
chalk.rgb(
255,
136,
0,
)(
" If you like Firefish, please consider contributing to the repo. https://firefish.dev/firefish/firefish",
),
);
console.log("");
console.log(
chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`,
);
}
console.log("");
console.log(
chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`,
);
bootLogger.info("Welcome to Firefish!");
bootLogger.info(`Firefish v${meta.version}`, null, true);

View File

@ -1,3 +1,5 @@
import load from "./load.js";
import { readEnvironmentConfig } from "backend-rs";
export default load();
export const envOption = readEnvironmentConfig();

View File

@ -55,6 +55,7 @@ export default function load() {
mixin.userAgent = `Firefish/${meta.version} (${config.url})`;
mixin.clientEntry = clientManifest["src/init.ts"];
if (config.proxyRemoteFiles == null) config.proxyRemoteFiles = true;
if (!config.redis.prefix) config.redis.prefix = mixin.hostname;
if (config.cacheServer && !config.cacheServer.prefix)
config.cacheServer.prefix = mixin.hostname;

View File

@ -1,7 +1,7 @@
import si from "systeminformation";
import Xev from "xev";
import * as osUtils from "os-utils";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { fetchMeta } from "backend-rs";
const ev = new Xev();
@ -13,16 +13,15 @@ const round = (num: number) => Math.round(num * 10) / 10;
/**
* Report server stats regularly
*/
export default function () {
export default async function () {
const log = [] as any[];
ev.on("requestServerStatsLog", (x) => {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50));
});
fetchMeta().then((meta) => {
if (!meta.enableServerMachineStats) return;
});
const meta = await fetchMeta(true);
if (!meta.enableServerMachineStats) return;
async function tick() {
const cpu = await cpuUsage();

View File

@ -237,31 +237,3 @@ export async function initDb(force = false) {
await db.initialize();
}
}
export async function resetDb() {
const reset = async () => {
await redisClient.flushdb();
const tables = await db.query(`SELECT relname AS "table"
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind = 'r'
AND nspname !~ '^pg_toast';`);
for (const table of tables) {
await db.query(`DELETE FROM "${table.table}" CASCADE`);
}
};
for (let i = 1; i <= 3; i++) {
try {
await reset();
} catch (e) {
if (i === 3) {
throw e;
} else {
await new Promise((resolve) => setTimeout(resolve, 1000));
continue;
}
}
break;
}
}

View File

@ -1,25 +0,0 @@
const envOption = {
onlyQueue: false,
onlyServer: false,
noDaemons: false,
disableClustering: false,
verbose: false,
withLogTime: false,
quiet: false,
slow: false,
};
for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) {
if (
process.env[
`MK_${key.replace(/[A-Z]/g, (letter) => `_${letter}`).toUpperCase()}`
]
)
envOption[key] = true;
}
if (process.env.NODE_ENV === "test") envOption.disableClustering = true;
if (process.env.NODE_ENV === "test") envOption.quiet = true;
if (process.env.NODE_ENV === "test") envOption.noDaemons = true;
export { envOption };

View File

@ -0,0 +1,21 @@
import type { MigrationInterface, QueryRunner } from "typeorm";
export class ConvertCwVarcharToText1713225866247 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"`);
queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE text`);
queryRunner.query(
`CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
queryRunner.query(`DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"`);
queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "cw" TYPE character varying(512)`,
);
queryRunner.query(
`CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2)`,
);
}
}

View File

@ -0,0 +1,17 @@
import type { MigrationInterface, QueryRunner } from "typeorm";
export class AddDriveFileUsage1713451569342 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE drive_file_usage_hint_enum AS ENUM ('userAvatar', 'userBanner')`,
);
await queryRunner.query(
`ALTER TABLE "drive_file" ADD "usageHint" drive_file_usage_hint_enum DEFAULT NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "usageHint"`);
await queryRunner.query(`DROP TYPE drive_file_usage_hint_enum`);
}
}

View File

@ -1,6 +1,6 @@
import { redisClient } from "@/db/redis.js";
import { encode, decode } from "msgpackr";
import { ChainableCommander } from "ioredis";
import type { ChainableCommander } from "ioredis";
export class Cache<T> {
private ttl: number;

View File

@ -1,17 +0,0 @@
export function convertMilliseconds(ms: number) {
let seconds = Math.round(ms / 1000);
let minutes = Math.round(seconds / 60);
let hours = Math.round(minutes / 60);
const days = Math.round(hours / 24);
seconds %= 60;
minutes %= 60;
hours %= 24;
const result = [];
if (days > 0) result.push(`${days} day(s)`);
if (hours > 0) result.push(`${hours} hour(s)`);
if (minutes > 0) result.push(`${minutes} minute(s)`);
if (seconds > 0) result.push(`${seconds} second(s)`);
return result.join(", ");
}

View File

@ -1,5 +0,0 @@
import twemoji from "@twemoji/parser/dist/lib/regex.js";
const twemojiRegex = twemoji.default;
export const emojiRegex = new RegExp(`(${twemojiRegex.source})`);
export const emojiRegexAtStartToEnd = new RegExp(`^(${twemojiRegex.source})$`);

View File

@ -1,72 +0,0 @@
import { db } from "@/db/postgre.js";
import { Meta } from "@/models/entities/meta.js";
let cache: Meta;
export function metaToPugArgs(meta: Meta): object {
let motd = ["Loading..."];
if (meta.customMotd.length > 0) {
motd = meta.customMotd;
}
let splashIconUrl = meta.iconUrl;
if (meta.customSplashIcons.length > 0) {
splashIconUrl =
meta.customSplashIcons[
Math.floor(Math.random() * meta.customSplashIcons.length)
];
}
return {
img: meta.bannerUrl,
title: meta.name || "Firefish",
instanceName: meta.name || "Firefish",
desc: meta.description,
icon: meta.iconUrl,
splashIcon: splashIconUrl,
themeColor: meta.themeColor,
randomMOTD: motd[Math.floor(Math.random() * motd.length)],
privateMode: meta.privateMode,
};
}
export async function fetchMeta(noCache = false): Promise<Meta> {
if (!noCache && cache) return cache;
return await db.transaction(async (transactionalEntityManager) => {
// New IDs are prioritized because multiple records may have been created due to past bugs.
const metas = await transactionalEntityManager.find(Meta, {
order: {
id: "DESC",
},
});
const meta = metas[0];
if (meta) {
cache = meta;
return meta;
} else {
// If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert.
const saved = await transactionalEntityManager
.upsert(
Meta,
{
id: "x",
},
["id"],
)
.then((x) =>
transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]),
);
cache = saved;
return saved;
}
});
}
setInterval(() => {
fetchMeta(true).then((meta) => {
cache = meta;
});
}, 1000 * 10);

View File

@ -1,9 +1,9 @@
import { fetchMeta } from "./fetch-meta.js";
import { fetchMeta } from "backend-rs";
import type { ILocalUser } from "@/models/entities/user.js";
import { Users } from "@/models/index.js";
export async function fetchProxyAccount(): Promise<ILocalUser | null> {
const meta = await fetchMeta();
const meta = await fetchMeta(true);
if (meta.proxyAccountId == null) return null;
return (await Users.findOneByOrFail({
id: meta.proxyAccountId,

View File

@ -1,53 +0,0 @@
import type { Packed } from "./schema.js";
/**
* 稿
* @param {*} note (packされた)稿
*/
export const getNoteSummary = (note: Packed<"Note">): string => {
if (note.deletedAt) {
return "❌";
}
let summary = "";
// 本文
if (note.cw != null) {
summary += note.cw;
} else {
summary += note.text ? note.text : "";
}
// ファイルが添付されているとき
if ((note.files || []).length !== 0) {
const len = note.files?.length;
summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
}
// 投票が添付されているとき
if (note.poll) {
summary += " 📊";
}
/*
// 返信のとき
if (note.replyId) {
if (note.reply) {
summary += `\n\nRE: ${getNoteSummary(note.reply)}`;
} else {
summary += '\n\nRE: ...';
}
}
// Renoteのとき
if (note.renoteId) {
if (note.renote) {
summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
} else {
summary += '\n\nRN: ...';
}
}
*/
return summary.trim();
};

View File

@ -1,20 +0,0 @@
import bcrypt from "bcryptjs";
import * as argon2 from "argon2";
export async function hashPassword(password: string): Promise<string> {
return argon2.hash(password);
}
export async function comparePassword(
password: string,
hash: string,
): Promise<boolean> {
if (isOldAlgorithm(hash)) return bcrypt.compare(password, hash);
return argon2.verify(hash, password);
}
export function isOldAlgorithm(hash: string): boolean {
// bcrypt hashes start with $2[ab]$
return hash.startsWith("$2");
}

View File

@ -3,8 +3,7 @@ import { Emojis } from "@/models/index.js";
import type { Emoji } from "@/models/entities/emoji.js";
import type { Note } from "@/models/entities/note.js";
import { Cache } from "./cache.js";
import { isSelfHost, toPuny } from "backend-rs";
import { decodeReaction } from "./reaction-lib.js";
import { decodeReaction, isSelfHost, toPuny } from "backend-rs";
import config from "@/config/index.js";
import { query } from "@/prelude/url.js";
import { redisClient } from "@/db/redis.js";

View File

@ -12,7 +12,7 @@ export function parse(acct: any): Post {
cw: acct.cw,
localOnly: acct.localOnly,
createdAt: new Date(acct.createdAt),
visibility: "hidden" + (acct.visibility || ""),
visibility: `hidden${acct.visibility || ""}`,
};
}

View File

@ -1,87 +0,0 @@
import { emojiRegex } from "./emoji-regex.js";
import { fetchMeta } from "./fetch-meta.js";
import { Emojis } from "@/models/index.js";
import { toPuny } from "backend-rs";
import { IsNull } from "typeorm";
export function convertReactions(reactions: Record<string, number>) {
const result = new Map();
for (const reaction in reactions) {
if (reactions[reaction] <= 0) continue;
const decoded = decodeReaction(reaction).reaction;
result.set(decoded, (result.get(decoded) || 0) + reactions[reaction]);
}
return Object.fromEntries(result);
}
export async function toDbReaction(
reaction?: string | null,
reacterHost?: string | null,
): Promise<string> {
if (!reaction) return (await fetchMeta()).defaultReaction;
reacterHost = reacterHost == null ? null : toPuny(reacterHost);
if (reaction.includes("❤") || reaction.includes("♥️")) return "❤️";
// Allow unicode reactions
const match = emojiRegex.exec(reaction);
if (match) {
const unicode = match[0];
return unicode;
}
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
if (custom) {
const name = custom[1];
const emoji = await Emojis.findOneBy({
host: reacterHost || IsNull(),
name,
});
if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
}
return (await fetchMeta()).defaultReaction;
}
type DecodedReaction = {
/**
* (Unicode Emoji or ':name@hostname' or ':name@.')
*/
reaction: string;
/**
* name (name, Emojiクエリに使う)
*/
name?: string;
/**
* host (host, Emojiクエリに使う)
*/
host?: string | null;
};
export function decodeReaction(str: string): DecodedReaction {
const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/);
if (custom) {
const name = custom[1];
const host = custom[2] || null;
return {
reaction: `:${name}@${host || "."}:`, // ローカル分は@以降を省略するのではなく.にする
name,
host,
};
}
return {
reaction: str,
name: undefined,
host: undefined,
};
}

View File

@ -1,3 +0,0 @@
export function safeForSql(text: string): boolean {
return !/[\0\x08\x09\x1a\n\r"'\\\%]/g.test(text);
}

View File

@ -33,8 +33,10 @@ import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
import { packedEmojiSchema } from "@/models/schema/emoji.js";
import { packedNoteEdit } from "@/models/schema/note-edit.js";
import { packedNoteFileSchema } from "@/models/schema/note-file.js";
import { packedAbuseUserReportSchema } from "@/models/schema/abuse-user-report.js";
export const refs = {
AbuseUserReport: packedAbuseUserReportSchema,
UserLite: packedUserLiteSchema,
UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
MeDetailedOnly: packedMeDetailedOnlySchema,

View File

@ -1,4 +1,4 @@
import { fetchMeta } from "@/misc/fetch-meta.js";
import { fetchMeta } from "backend-rs";
import type { Instance } from "@/models/entities/instance.js";
import type { Meta } from "@/models/entities/meta.js";
@ -13,7 +13,7 @@ export async function shouldBlockInstance(
host: Instance["host"],
meta?: Meta,
): Promise<boolean> {
const { blockedHosts } = meta ?? (await fetchMeta());
const { blockedHosts } = meta ?? (await fetchMeta(true));
return blockedHosts.some(
(blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`),
);
@ -30,7 +30,7 @@ export async function shouldSilenceInstance(
host: Instance["host"],
meta?: Meta,
): Promise<boolean> {
const { silencedHosts } = meta ?? (await fetchMeta());
const { silencedHosts } = meta ?? (await fetchMeta(true));
return silencedHosts.some(
(silencedHost) =>
host === silencedHost || host.endsWith(`.${silencedHost}`),

View File

@ -1,5 +1,5 @@
import { Brackets } from "typeorm";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { fetchMeta } from "backend-rs";
import { Instances } from "@/models/index.js";
import type { Instance } from "@/models/entities/instance.js";
import { DAY } from "@/const.js";
@ -19,7 +19,7 @@ export async function skippedInstances(
hosts: Instance["host"][],
): Promise<Instance["host"][]> {
// first check for blocked instances since that info may already be in memory
const meta = await fetchMeta();
const meta = await fetchMeta(true);
const shouldSkip = await Promise.all(
hosts.map((host) => shouldBlockInstance(host, meta)),
);

View File

@ -1,3 +0,0 @@
export function sqlLikeEscape(s: string) {
return s.replace(/([%_])/g, "\\$1");
}

View File

@ -1,7 +1,7 @@
import fetch from "node-fetch";
import { Converter } from "opencc-js";
import { getAgentByUrl } from "@/misc/fetch.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { fetchMeta } from "backend-rs";
import type { PostLanguage } from "@/misc/langmap";
import * as deepl from "deepl-node";
@ -26,7 +26,7 @@ export async function translate(
from: PostLanguage | null,
to: PostLanguage,
) {
const instance = await fetchMeta();
const instance = await fetchMeta(true);
if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) {
throw Error("No translator is set up on this server.");

View File

@ -16,6 +16,8 @@ import { DriveFolder } from "./drive-folder.js";
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
import { NoteFile } from "./note-file.js";
export type DriveFileUsageHint = "userAvatar" | "userBanner" | null;
@Entity()
@Index(["userId", "folderId", "id"])
export class DriveFile {
@ -177,6 +179,14 @@ export class DriveFile {
})
public isSensitive: boolean;
// Hint for what this file is used for
@Column({
type: "enum",
enum: ["userAvatar", "userBanner"],
nullable: true,
})
public usageHint: DriveFileUsageHint;
/**
* ()URLへの直リンクか否か
*/

View File

@ -72,9 +72,8 @@ export class Note {
})
public name: string | null;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: 512,
@Index() // USING pgroonga
@Column("text", {
nullable: true,
})
public cw: string | null;

View File

@ -2,6 +2,7 @@ import { db } from "@/db/postgre.js";
import { Users } from "../index.js";
import { AbuseUserReport } from "@/models/entities/abuse-user-report.js";
import { awaitAll } from "@/prelude/await-all.js";
import type { Packed } from "@/misc/schema.js";
export const AbuseUserReportRepository = db
.getRepository(AbuseUserReport)
@ -10,7 +11,7 @@ export const AbuseUserReportRepository = db
const report =
typeof src === "object" ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
const packed: Packed<"AbuseUserReport"> = await awaitAll({
id: report.id,
createdAt: report.createdAt.toISOString(),
comment: report.comment,
@ -31,9 +32,10 @@ export const AbuseUserReportRepository = db
: null,
forwarded: report.forwarded,
});
return packed;
},
packMany(reports: any[]) {
packMany(reports: (AbuseUserReport["id"] | AbuseUserReport)[]) {
return Promise.all(reports.map((x) => this.pack(x)));
},
});

View File

@ -40,6 +40,7 @@ export const ChannelRepository = db.getRepository(Channel).extend({
name: channel.name,
description: channel.description,
userId: channel.userId,
bannerId: channel.bannerId,
bannerUrl: banner ? DriveFiles.getPublicUrl(banner, false) : null,
usersCount: channel.usersCount,
notesCount: channel.notesCount,

View File

@ -152,6 +152,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
md5: file.md5,
size: file.size,
isSensitive: file.isSensitive,
usageHint: file.usageHint,
blurhash: file.blurhash,
properties: opts.self ? file.properties : this.getPublicProperties(file),
url: opts.self ? file.url : this.getPublicUrl(file, false),
@ -193,6 +194,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
md5: file.md5,
size: file.size,
isSensitive: file.isSensitive,
usageHint: file.usageHint,
blurhash: file.blurhash,
properties: opts.self ? file.properties : this.getPublicProperties(file),
url: opts.self ? file.url : this.getPublicUrl(file, false),

View File

@ -19,7 +19,9 @@ export const GalleryPostRepository = db.getRepository(GalleryPost).extend({
createdAt: post.createdAt.toISOString(),
updatedAt: post.updatedAt.toISOString(),
userId: post.userId,
user: Users.pack(post.user || post.userId, me),
user: Users.pack(post.user || post.userId, me, {
detail: true,
}),
title: post.title,
description: post.description,
fileIds: post.fileIds,

View File

@ -2,7 +2,7 @@ import { db } from "@/db/postgre.js";
import { NoteReaction } from "@/models/entities/note-reaction.js";
import { Notes, Users } from "../index.js";
import type { Packed } from "@/misc/schema.js";
import { decodeReaction } from "@/misc/reaction-lib.js";
import { decodeReaction } from "backend-rs";
import type { User } from "@/models/entities/user.js";
export const NoteReactionRepository = db.getRepository(NoteReaction).extend({

View File

@ -12,9 +12,8 @@ import {
Channels,
} from "../index.js";
import type { Packed } from "@/misc/schema.js";
import { nyaify } from "backend-rs";
import { countReactions, decodeReaction, nyaify } from "backend-rs";
import { awaitAll } from "@/prelude/await-all.js";
import { convertReactions, decodeReaction } from "@/misc/reaction-lib.js";
import type { NoteReaction } from "@/models/entities/note-reaction.js";
import {
aggregateNoteEmojis,
@ -214,7 +213,7 @@ export const NoteRepository = db.getRepository(Note).extend({
note.visibility === "specified" ? note.visibleUserIds : undefined,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
reactions: convertReactions(note.reactions),
reactions: countReactions(note.reactions),
reactionEmojis: reactionEmoji,
emojis: noteEmoji,
tags: note.tags.length > 0 ? note.tags : undefined,
@ -233,6 +232,7 @@ export const NoteRepository = db.getRepository(Note).extend({
uri: note.uri || undefined,
url: note.url || undefined,
updatedAt: note.updatedAt?.toISOString() || undefined,
hasPoll: note.hasPoll,
poll: note.hasPoll ? populatePoll(note, meId) : undefined,
...(meId
? {

View File

@ -0,0 +1,69 @@
export const packedAbuseUserReportSchema = {
type: "object",
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx",
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time",
},
comment: {
type: "string",
optional: false,
nullable: false,
},
resolved: {
type: "boolean",
optional: false,
nullable: false,
},
reporterId: {
type: "string",
optional: false,
nullable: false,
format: "id",
},
targetUserId: {
type: "string",
optional: false,
nullable: false,
format: "id",
},
assigneeId: {
type: "string",
optional: false,
nullable: true,
format: "id",
},
reporter: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailed",
},
targetUser: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailed",
},
assignee: {
type: "object",
optional: true,
nullable: true,
ref: "UserDetailed",
},
forwarded: {
type: "boolean",
optional: false,
nullable: false,
},
},
} as const;

View File

@ -36,6 +36,13 @@ export const packedChannelSchema = {
nullable: true,
optional: false,
},
bannerId: {
type: "string",
optional: false,
nullable: true,
format: "id",
example: "xxxxxxxxxx",
},
notesCount: {
type: "number",
nullable: false,
@ -57,5 +64,10 @@ export const packedChannelSchema = {
optional: false,
format: "id",
},
hasUnreadNote: {
type: "boolean",
optional: true,
nullable: false,
},
},
} as const;

View File

@ -44,6 +44,12 @@ export const packedDriveFileSchema = {
optional: false,
nullable: false,
},
usageHint: {
type: "string",
optional: false,
nullable: true,
enum: ["userAvatar", "userBanner"],
},
blurhash: {
type: "string",
optional: false,

View File

@ -38,7 +38,7 @@ export const packedGalleryPostSchema = {
},
user: {
type: "object",
ref: "UserLite",
ref: "UserDetailed",
optional: false,
nullable: false,
},
@ -79,5 +79,15 @@ export const packedGalleryPostSchema = {
optional: false,
nullable: false,
},
isLiked: {
type: "boolean",
optional: true,
nullable: false,
},
likedCount: {
type: "number",
optional: false,
nullable: false,
},
},
} as const;

View File

@ -28,7 +28,7 @@ export const packedNoteSchema = {
},
cw: {
type: "string",
optional: true,
optional: false,
nullable: true,
},
userId: {
@ -98,7 +98,7 @@ export const packedNoteSchema = {
},
fileIds: {
type: "array",
optional: true,
optional: false,
nullable: false,
items: {
type: "string",
@ -128,6 +128,11 @@ export const packedNoteSchema = {
nullable: false,
},
},
hasPoll: {
type: "boolean",
optional: false,
nullable: false,
},
poll: {
type: "object",
optional: true,

View File

@ -5,7 +5,7 @@ import config from "@/config/index.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import type { IActivity } from "@/remote/activitypub/type.js";
import type { Webhook, webhookEventTypes } from "@/models/entities/webhook.js";
import { envOption } from "../env.js";
import { envOption } from "@/config/index.js";
import processDeliver from "./processors/deliver.js";
import processInbox from "./processors/inbox.js";

View File

@ -34,7 +34,7 @@ export function initialize<T>(name: string, limitPerSec = -1) {
function apBackoff(attemptsMade: number, err: Error) {
const baseDelay = 60 * 1000; // 1min
const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
let backoff = (2 ** attemptsMade - 1) * baseDelay;
backoff = Math.min(backoff, maxBackoff);
backoff += Math.round(backoff * Math.random() * 0.2);
return backoff;

View File

@ -5,7 +5,7 @@ import perform from "@/remote/activitypub/perform.js";
import Logger from "@/services/logger.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
import { Instances } from "@/models/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { fetchMeta } from "backend-rs";
import { toPuny, extractHost } from "backend-rs";
import { getApId } from "@/remote/activitypub/type.js";
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
@ -41,7 +41,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
const host = toPuny(new URL(signature.keyId).hostname);
// interrupt if blocked
const meta = await fetchMeta();
const meta = await fetchMeta(true);
if (await shouldBlockInstance(host, meta)) {
return `Blocked request: ${host}`;
}

View File

@ -1,7 +1,7 @@
import { URL } from "url";
import httpSignature, { IParsedSignature } from "@peertube/http-signature";
import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { fetchMeta } from "backend-rs";
import { toPuny } from "backend-rs";
import DbResolver from "@/remote/activitypub/db-resolver.js";
import { getApId } from "@/remote/activitypub/type.js";
@ -12,7 +12,7 @@ import type { UserPublickey } from "@/models/entities/user-publickey.js";
import { verify } from "node:crypto";
export async function hasSignature(req: IncomingMessage): Promise<string> {
const meta = await fetchMeta();
const meta = await fetchMeta(true);
const required = meta.secureMode || meta.privateMode;
try {
@ -27,7 +27,7 @@ export async function hasSignature(req: IncomingMessage): Promise<string> {
}
export async function checkFetch(req: IncomingMessage): Promise<number> {
const meta = await fetchMeta();
const meta = await fetchMeta(true);
if (meta.secureMode || meta.privateMode) {
if (req.headers.host !== config.host) return 400;

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