Merge branch 'develop' into 'main'

release: v20240818

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: GitLab CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Co-authored-by: mester yui <oscarodriguez56@gmail.com>
Co-authored-by: jolupa <jolupameister@gmail.com>
Co-authored-by: Lacey Anaya <yecakeh263@anawalls.com>
Co-authored-by: FLY_MC <me@flymc.cc>

See merge request firefish/firefish!11293
This commit is contained in:
naskya 2024-08-18 07:28:35 +00:00
commit aa1d23e6cd
65 changed files with 837 additions and 735 deletions

102
Cargo.lock generated
View file

@ -98,7 +98,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -149,7 +149,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -160,7 +160,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -213,6 +213,7 @@ dependencies = [
"chrono", "chrono",
"cuid2", "cuid2",
"emojis", "emojis",
"error-doc",
"futures-util", "futures-util",
"identicon-rs", "identicon-rs",
"idna 1.0.2", "idna 1.0.2",
@ -660,7 +661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -798,7 +799,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -858,7 +859,7 @@ dependencies = [
"enum-ordinalize", "enum-ordinalize",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -926,7 +927,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -945,6 +946,17 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "error-doc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ffaad84523e0144697672bce3a0d8e300fd43404a630d420f238e2ef2e85b84"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
]
[[package]] [[package]]
name = "etcetera" name = "etcetera"
version = "0.8.0" version = "0.8.0"
@ -1478,7 +1490,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1571,7 +1583,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1600,7 +1612,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1856,7 +1868,7 @@ dependencies = [
"quote", "quote",
"serde", "serde",
"serde_json", "serde_json",
"syn 2.0.72", "syn 2.0.75",
"thiserror", "thiserror",
] ]
@ -1867,7 +1879,7 @@ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1964,7 +1976,7 @@ dependencies = [
"napi-derive-backend", "napi-derive-backend",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1979,7 +1991,7 @@ dependencies = [
"quote", "quote",
"regex", "regex",
"semver", "semver",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -2109,7 +2121,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -2200,7 +2212,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -2261,7 +2273,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -2419,7 +2431,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -2595,7 +2607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -2994,7 +3006,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -3033,7 +3045,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"sea-bae", "sea-bae",
"syn 2.0.72", "syn 2.0.75",
"unicode-ident", "unicode-ident",
] ]
@ -3095,29 +3107,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.205" version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.205" version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.122" version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -3584,9 +3596,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.72" version = "2.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3613,7 +3625,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -3687,7 +3699,7 @@ checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -3698,7 +3710,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -3782,9 +3794,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.39.2" version = "1.39.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -3805,7 +3817,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -3899,7 +3911,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -4135,7 +4147,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4157,7 +4169,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4273,7 +4285,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -4284,7 +4296,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -4482,7 +4494,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"synstructure 0.13.1", "synstructure 0.13.1",
] ]
@ -4504,7 +4516,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -4524,7 +4536,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"synstructure 0.13.1", "synstructure 0.13.1",
] ]
@ -4553,7 +4565,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]

View file

@ -23,6 +23,7 @@ chrono = { version = "0.4.38", default-features = false }
convert_case = { version = "0.6.0", default-features = false } convert_case = { version = "0.6.0", default-features = false }
cuid2 = { version = "0.1.2", default-features = false } cuid2 = { version = "0.1.2", default-features = false }
emojis = { version = "0.6.3", default-features = false } emojis = { version = "0.6.3", default-features = false }
error-doc = { version = "0.1.0" }
futures-util = { version = "0.3.30", default-features = false } futures-util = { version = "0.3.30", default-features = false }
identicon-rs = "5.0.1" identicon-rs = "5.0.1"
idna = { version = "1.0.2", default-features = false } idna = { version = "1.0.2", default-features = false }
@ -38,13 +39,13 @@ redis = { version = "0.26.1", default-features = false }
regex = { version = "1.10.6", default-features = false } regex = { version = "1.10.6", default-features = false }
rmp-serde = { version = "1.3.0", default-features = false } rmp-serde = { version = "1.3.0", default-features = false }
sea-orm = { version = "1.0.0", default-features = false } sea-orm = { version = "1.0.0", default-features = false }
serde = { version = "1.0.205", default-features = false } serde = { version = "1.0.208", default-features = false }
serde_json = { version = "1.0.122", default-features = false } serde_json = { version = "1.0.125", default-features = false }
serde_yaml = { version = "0.9.34", default-features = false } serde_yaml = { version = "0.9.34", default-features = false }
syn = { version = "2.0.72", default-features = false } syn = { version = "2.0.75", default-features = false }
sysinfo = { version = "0.31.2", default-features = false } sysinfo = { version = "0.31.2", default-features = false }
thiserror = { version = "1.0.63", default-features = false } thiserror = { version = "1.0.63", default-features = false }
tokio = { version = "1.39.2", default-features = false } tokio = { version = "1.39.3", default-features = false }
tokio-test = { version = "0.4.4", default-features = false } tokio-test = { version = "0.4.4", default-features = false }
tracing = { version = "0.1.40", default-features = false } tracing = { version = "0.1.40", default-features = false }
tracing-subscriber = { version = "0.3.18", default-features = false } tracing-subscriber = { version = "0.3.18", default-features = false }

View file

@ -7,6 +7,10 @@ This changelog is not an exhaustive list. Code refactorings, minor bug fixes, do
- Server administrators must check [notice-for-admins.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/notice-for-admins.md) as well. - Server administrators must check [notice-for-admins.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/notice-for-admins.md) as well.
- Third-party client/bot developers may want to check [api-change.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/api-change.md) as well. - Third-party client/bot developers may want to check [api-change.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/api-change.md) as well.
## [v20240818](https://firefish.dev/firefish/firefish/-/merge_requests/11293/commits)
- Fix bugs
## [v20240809](https://firefish.dev/firefish/firefish/-/merge_requests/11262/commits) ## [v20240809](https://firefish.dev/firefish/firefish/-/merge_requests/11262/commits)
- Add writing mode (right-to-left, vertical) support (!11222) - Add writing mode (right-to-left, vertical) support (!11222)

View file

@ -2,10 +2,6 @@
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](https://firefish.dev/firefish/firefish/-/blob/main/docs/upgrade.md). 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](https://firefish.dev/firefish/firefish/-/blob/main/docs/upgrade.md).
## Upcoming breaking change (unreleased)
Please take a look at #10947.
## v20240809 ## v20240809
### For systemd/pm2 users ### For systemd/pm2 users

View file

@ -1250,8 +1250,8 @@ disablePlayer: Tancar el reproductor de vídeo
fileIdOrUrl: ID o adreça URL del fitxer fileIdOrUrl: ID o adreça URL del fitxer
behavior: Comportament behavior: Comportament
regenerateLoginTokenDescription: Regenera la clau que es fa servir de manera interna regenerateLoginTokenDescription: Regenera la clau que es fa servir de manera interna
durant l'inici de sessió. Normalment això no és necessari. Si la clau és torna a durant l'inici de sessió. Usualment aquesta acció no és necessària. Si la clau és
generar, es tancarà la sessió a tots els dispositius. torna a generar, es tancarà la sessió a tots els dispositius.
setMultipleBySeparatingWithSpace: Separa diferents entrades amb espais. setMultipleBySeparatingWithSpace: Separa diferents entrades amb espais.
reportAbuseOf: Informa d'un abús de {name} reportAbuseOf: Informa d'un abús de {name}
sample: Exemple sample: Exemple

View file

@ -128,7 +128,7 @@ pinnedNote: "Pinned post"
pinned: "Pin to profile" pinned: "Pin to profile"
you: "You" you: "You"
clickToShow: "Click to show" clickToShow: "Click to show"
sensitive: "NSFW" sensitive: "Sensitive"
add: "Add" add: "Add"
reaction: "Reaction" reaction: "Reaction"
reactions: "Reactions" reactions: "Reactions"
@ -139,8 +139,8 @@ reactionSetting: "Reactions to show in the reaction picker"
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add." reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
rememberNoteVisibility: "Remember post visibility settings" rememberNoteVisibility: "Remember post visibility settings"
attachCancel: "Remove attachment" attachCancel: "Remove attachment"
markAsSensitive: "Mark as NSFW" markAsSensitive: "Mark as sensitive"
unmarkAsSensitive: "Unmark as NSFW" unmarkAsSensitive: "Unmark as sensitive"
clickToShowPatterns: "Click to show module patterns" clickToShowPatterns: "Click to show module patterns"
enterFileName: "Enter filename" enterFileName: "Enter filename"
mute: "Mute" mute: "Mute"
@ -176,7 +176,7 @@ cacheRemoteFilesDescription: "When this setting is disabled, remote files are lo
increase traffic, as thumbnails will not be generated." increase traffic, as thumbnails will not be generated."
markLocalFilesNsfwByDefault: "Mark all new local file as sensitive by default" markLocalFilesNsfwByDefault: "Mark all new local file as sensitive by default"
markLocalFilesNsfwByDefaultDescription: "Regardless of this setting, users can remove markLocalFilesNsfwByDefaultDescription: "Regardless of this setting, users can remove
the NSFW flag themselves. Existing files are unaffected." the sensitive flag themselves. Existing files are unaffected."
flagAsBot: "Mark this account as automated" flagAsBot: "Mark this account as automated"
flagAsBotDescription: "Enable this option if this account is controlled by a program. flagAsBotDescription: "Enable this option if this account is controlled by a program.
If enabled, it will act as a flag for other developers to prevent endless interaction If enabled, it will act as a flag for other developers to prevent endless interaction
@ -362,7 +362,7 @@ copyUrl: "Copy URL"
rename: "Rename" rename: "Rename"
avatar: "Avatar" avatar: "Avatar"
banner: "Banner" banner: "Banner"
nsfw: "NSFW" nsfw: "Sensitive"
whenServerDisconnected: "When losing connection to the server" whenServerDisconnected: "When losing connection to the server"
disconnectedFromServer: "Connection to server has been lost" disconnectedFromServer: "Connection to server has been lost"
reload: "Refresh" reload: "Refresh"
@ -727,7 +727,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b
other: "Other" other: "Other"
regenerateLoginToken: "Regenerate sign in token" regenerateLoginToken: "Regenerate sign in token"
regenerateLoginTokenDescription: "Regenerates the token used internally during sign regenerateLoginTokenDescription: "Regenerates the token used internally during sign
in. Normally this action is not necessary. If regenerated, all devices will be logged in. Usually this action is not necessary. If regenerated, all devices will be logged
out." out."
setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces." setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
fileIdOrUrl: "File ID or URL" fileIdOrUrl: "File ID or URL"
@ -792,7 +792,7 @@ noCrawle: "Reject crawler indexing"
noCrawleDescription: "Ask external search engines to not index your content." noCrawleDescription: "Ask external search engines to not index your content."
lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your
posts will be visible to anyone, even if you require followers to be manually approved." posts will be visible to anyone, even if you require followers to be manually approved."
alwaysMarkSensitive: "Mark as NSFW by default" alwaysMarkSensitive: "Mark as sensitive by default"
loadRawImages: "Load original images instead of showing thumbnails" loadRawImages: "Load original images instead of showing thumbnails"
disableShowingAnimatedImages: "Don't play animated images" disableShowingAnimatedImages: "Don't play animated images"
verificationEmailSent: "A verification email has been sent. Please follow the included verificationEmailSent: "A verification email has been sent. Please follow the included
@ -1035,20 +1035,16 @@ type: "Type"
speed: "Speed" speed: "Speed"
slow: "Slow" slow: "Slow"
fast: "Fast" fast: "Fast"
sensitiveMediaDetection: "Detection of NSFW media"
localOnly: "Local only" localOnly: "Local only"
remoteOnly: "Remote only" remoteOnly: "Remote only"
failedToUpload: "Upload failed" failedToUpload: "Upload failed"
cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of
it have been detected as potentially NSFW." it have been detected as potentially sensitive."
cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity." cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity."
cannotUploadBecauseExceedsFileSizeLimit: "This file could not be uploaded because cannotUploadBecauseExceedsFileSizeLimit: "This file could not be uploaded because
it exceeds the maximum allowed size." it exceeds the maximum allowed size."
beta: "Beta" beta: "Beta"
enableAutoSensitive: "Automatic NSFW-Marking" enableAutoSensitive: "Mark as sensitive automatically"
enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW media
through Machine Learning where possible. Even if this option is disabled, it may
be enabled server-wide."
activeEmailValidationDescription: "Enables stricter validation of email addresses, activeEmailValidationDescription: "Enables stricter validation of email addresses,
which includes checking for disposable addresses and by whether it can actually which includes checking for disposable addresses and by whether it can actually
be communicated with. When unchecked, only the format of the email is validated." be communicated with. When unchecked, only the format of the email is validated."
@ -1249,19 +1245,6 @@ _emojiModPerm:
add: "Add" add: "Add"
mod: "Add and Edit" mod: "Add and Edit"
full: "Allow All" full: "Allow All"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
NSFW media via Machine Learning. This will slightly increase the load on the server."
sensitivity: "Detection sensitivity"
sensitivityDescription: "Reducing the sensitivity will lead to fewer misdetections
(false positives) whereas increasing it will lead to fewer missed detections (false
negatives)."
setSensitiveFlagAutomatically: "Mark as NSFW"
setSensitiveFlagAutomaticallyDescription: "The results of the internal detection
will be retained even if this option is turned off."
analyzeVideos: "Enable analysis of videos"
analyzeVideosDescription: "Analyzes videos in addition to images. This will slightly
increase the load on the server."
_emailUnavailable: _emailUnavailable:
used: "This email address is already being used" used: "This email address is already being used"
format: "The format of this email address is invalid" format: "The format of this email address is invalid"
@ -1354,8 +1337,8 @@ _aboutFirefish:
to help support its operation costs." to help support its operation costs."
donateHost: "Donate to {host}" donateHost: "Donate to {host}"
_nsfw: _nsfw:
respect: "Hide NSFW media" respect: "Hide sensitive media"
ignore: "Don't hide NSFW media" ignore: "Don't hide sensitive media"
force: "Hide all media" force: "Hide all media"
writingMode: "Writing mode" writingMode: "Writing mode"
_writingMode: _writingMode:

View file

@ -30,3 +30,11 @@ loggingIn: Ensalutante
logout: Elsaluti logout: Elsaluti
timeline: Templinio timeline: Templinio
signup: Registru signup: Registru
favorites: Paĝosignoj
unfavorite: Forigi el paĝosignoj
uploading: Alŝutante...
save: Konservi
users: Uzantoj
addUser: Aldoni uzanton
addInstance: Aldoni servilon
favorite: Aldoni al paĝosigno

View file

@ -939,7 +939,7 @@ cannotUploadBecauseInappropriate: "不適切な内容を含む可能性がある
cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。" cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。"
cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。" cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。"
beta: "ベータ" beta: "ベータ"
enableAutoSensitive: "自動NSFW判定" enableAutoSensitive: "自動で閲覧注意にする"
enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。" enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。"
activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。"
showAds: "コミュニティバナーを表示する" showAds: "コミュニティバナーを表示する"
@ -1017,7 +1017,7 @@ searchWordsDescription: "投稿を検索するには、ここに検索語句を
(@user@example.com) や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。" (@user@example.com) や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。"
searchUsers: "投稿元(省略可)" searchUsers: "投稿元(省略可)"
searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.comローカルユーザーなら @userの形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名 searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.comローカルユーザーなら @userの形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名
(example.com) を指定すると、そのサーバーの投稿を検索します。\n\nme とだけ入力すると、自分の投稿を検索します。この検索結果には未収載・フォロワー限定・ダイレクト・秘密を含む全ての投稿が含まれます。\n (example.com) を指定すると、そのサーバーの投稿を検索します。\n\nme とだけ入力すると、自分の投稿を検索します。この検索結果には控えめな公開・フォロワー限定・ダイレクト・秘密を含む全ての投稿が含まれます。\n
\nlocal とだけ入力すると、ローカルサーバーの投稿を検索します。" \nlocal とだけ入力すると、ローカルサーバーの投稿を検索します。"
searchRange: "投稿期間(省略可)" searchRange: "投稿期間(省略可)"
searchRangeDescription: "投稿検索で投稿期間を絞りたい場合、20220615-20231031 のような形式で投稿期間を入力してください。今年の日付を指定する場合には年の指定を省略できます0105-0106 searchRangeDescription: "投稿検索で投稿期間を絞りたい場合、20220615-20231031 のような形式で投稿期間を入力してください。今年の日付を指定する場合には年の指定を省略できます0105-0106
@ -1516,7 +1516,7 @@ _poll:
_visibility: _visibility:
public: "公開" public: "公開"
publicDescription: "全ての公開タイムラインに配信されます" publicDescription: "全ての公開タイムラインに配信されます"
home: "未収載" home: "控えめな公開"
homeDescription: "ホームタイムラインのみに公開" homeDescription: "ホームタイムラインのみに公開"
followers: "フォロワー" followers: "フォロワー"
followersDescription: "フォロワーと会話相手のみに公開" followersDescription: "フォロワーと会話相手のみに公開"
@ -2070,7 +2070,7 @@ driveCapacityOverride: ドライブ容量の変更
autocorrectNoteLanguage: 設定した投稿言語が自動検出されたものと異なる場合に警告する autocorrectNoteLanguage: 設定した投稿言語が自動検出されたものと異なる場合に警告する
incorrectLanguageWarning: "この投稿は{detected}で書かれていると判定されました。\n投稿言語を{current}ではなく{detected}にしますか?" incorrectLanguageWarning: "この投稿は{detected}で書かれていると判定されました。\n投稿言語を{current}ではなく{detected}にしますか?"
markLocalFilesNsfwByDefault: このサーバーの全てのファイルをデフォルトでNSFWに設定する markLocalFilesNsfwByDefault: このサーバーの全てのファイルをデフォルトでNSFWに設定する
markLocalFilesNsfwByDefaultDescription: この設定が有効でも、ユーザーは自分でNSFWのフラグを外すことができます。また、この設定は既存のファイルには影響しません。 markLocalFilesNsfwByDefaultDescription: この設定が有効でも、ユーザーは自分で閲覧注意のフラグを外すことができます。また、この設定は既存のファイルには影響しません。
noteEditHistory: 編集履歴 noteEditHistory: 編集履歴
showAddFileDescriptionAtFirstPost: 説明の無い添付ファイルを投稿しようとした際に説明を書く画面を自動で開く showAddFileDescriptionAtFirstPost: 説明の無い添付ファイルを投稿しようとした際に説明を書く画面を自動で開く
antennaLimit: 各ユーザーが作れるアンテナの最大数 antennaLimit: 各ユーザーが作れるアンテナの最大数

View file

@ -1392,7 +1392,7 @@ _poll:
_visibility: _visibility:
public: "公開" public: "公開"
publicDescription: "發佈至公開時間軸" publicDescription: "發佈至公開時間軸"
home: "不在主頁顯示" home: "悄悄公開"
homeDescription: "僅發送至首頁的時間軸" homeDescription: "僅發送至首頁的時間軸"
followers: "追隨者" followers: "追隨者"
followersDescription: "僅發佈至關注者" followersDescription: "僅發佈至關注者"

View file

@ -1,11 +1,11 @@
{ {
"name": "firefish", "name": "firefish",
"version": "20240809", "version": "20240818",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://firefish.dev/firefish/firefish.git" "url": "https://firefish.dev/firefish/firefish.git"
}, },
"packageManager": "pnpm@9.7.0", "packageManager": "pnpm@9.7.1",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm run build", "rebuild": "pnpm run clean && pnpm run build",
@ -47,8 +47,8 @@
"@biomejs/cli-darwin-x64": "1.8.3", "@biomejs/cli-darwin-x64": "1.8.3",
"@biomejs/cli-linux-arm64": "1.8.3", "@biomejs/cli-linux-arm64": "1.8.3",
"@biomejs/cli-linux-x64": "1.8.3", "@biomejs/cli-linux-x64": "1.8.3",
"@types/node": "20.14.14", "@types/node": "20.15.0",
"execa": "9.3.0", "execa": "9.3.1",
"pnpm": "9.7.0" "pnpm": "9.7.1"
} }
} }

View file

@ -27,6 +27,7 @@ bcrypt = { workspace = true, features = ["std"] }
chrono = { workspace = true } chrono = { workspace = true }
cuid2 = { workspace = true } cuid2 = { workspace = true }
emojis = { workspace = true } emojis = { workspace = true }
error-doc = { workspace = true }
futures-util = { workspace = true, features = ["io"] } futures-util = { workspace = true, features = ["io"] }
identicon-rs = { workspace = true } identicon-rs = { workspace = true }
idna = { workspace = true, features = ["std", "compiled_data"] } idna = { workspace = true, features = ["std", "compiled_data"] }

View file

@ -48,6 +48,20 @@ export interface Acct {
export declare function acctToString(acct: Acct): string export declare function acctToString(acct: Acct): string
export type Activity = 'Accept'|
'Add'|
'Emoji'|
'Flag'|
'Follow'|
'Hashtag'|
'Like'|
'Mention'|
'Image'|
'Read'|
'Reject'|
'Remove'|
'Tombstone';
export interface Ad { export interface Ad {
id: string id: string
createdAt: DateTimeWithTimeZone createdAt: DateTimeWithTimeZone
@ -106,21 +120,28 @@ export type AntennaSrc = 'all'|
export interface ApAccept { export interface ApAccept {
id: string id: string
type: ApObject type: Activity
actor: string actor: string
object: ApFollow object: ApFollow
} }
export interface ApAdd {
type: Activity
actor: string
target: string
object: string
}
export interface ApEmoji { export interface ApEmoji {
id: string id: string
type: ApObject type: Activity
name: string name: string
updated: string updated: string
icon: Icon icon: Icon
} }
export interface ApFlag { export interface ApFlag {
type: ApObject type: Activity
actor: string actor: string
content: string content: string
object: string object: string
@ -128,33 +149,32 @@ export interface ApFlag {
export interface ApFollow { export interface ApFollow {
id: string id: string
type: ApObject type: Activity
actor: string actor: string
object: string object: string
} }
export interface ApHashtag { export interface ApHashtag {
id: string id: string
type: ApObject type: Activity
name: string name: string
} }
export interface ApLike {
id: string
type: Activity
actor: string
object: string
content: string
tag?: Array<ApEmoji>
}
export interface ApMention { export interface ApMention {
type: ApObject type: Activity
href: string href: string
name: string name: string
} }
export type ApObject = 'Accept'|
'Emoji'|
'Flag'|
'Follow'|
'Hashtag'|
'Mention'|
'Image'|
'Read'|
'Tombstone';
export interface App { export interface App {
id: string id: string
createdAt: DateTimeWithTimeZone createdAt: DateTimeWithTimeZone
@ -167,14 +187,28 @@ export interface App {
} }
export interface ApRead { export interface ApRead {
type: ApObject type: Activity
actor: string actor: string
object: string object: string
} }
export interface ApReject {
id: string
type: Activity
actor: string
object: ApFollow
}
export interface ApRemove {
type: Activity
actor: string
target: string
object: string
}
export interface ApTombstone { export interface ApTombstone {
id: string id: string
type: ApObject type: Activity
} }
export interface AttestationChallenge { export interface AttestationChallenge {
@ -578,7 +612,7 @@ export interface Hashtag {
} }
export interface Icon { export interface Icon {
type: ApObject type: Activity
mediaType: string mediaType: string
url: string url: string
} }
@ -1328,6 +1362,8 @@ export declare function removeOldAttestationChallenges(): Promise<void>
export declare function renderAccept(userId: string, followObject: ApFollow): ApAccept export declare function renderAccept(userId: string, followObject: ApFollow): ApAccept
export declare function renderAdd(userId: string, noteId: string): ApAdd
export declare function renderEmoji(emoji: Emoji): ApEmoji export declare function renderEmoji(emoji: Emoji): ApEmoji
export declare function renderFlag(targetUserUri: string, comment: string): Promise<ApFlag> export declare function renderFlag(targetUserUri: string, comment: string): Promise<ApFlag>
@ -1338,10 +1374,16 @@ export declare function renderFollowRelay(relayId: string): Promise<ApFollow>
export declare function renderHashtag(tagName: string): ApHashtag export declare function renderHashtag(tagName: string): ApHashtag
export declare function renderLike(reaction: Model): Promise<ApLike>
export declare function renderMention(user: UserLike): ApMention export declare function renderMention(user: UserLike): ApMention
export declare function renderRead(userId: string, messageUri: string): ApRead export declare function renderRead(userId: string, messageUri: string): ApRead
export declare function renderReject(userId: string, followObject: ApFollow): ApReject
export declare function renderRemove(userId: string, noteId: string): ApRemove
export declare function renderTombstone(noteId: string): ApTombstone export declare function renderTombstone(noteId: string): ApTombstone
export interface RenoteMuting { export interface RenoteMuting {

View file

@ -362,8 +362,8 @@ if (!nativeBinding) {
} }
module.exports.acctToString = nativeBinding.acctToString module.exports.acctToString = nativeBinding.acctToString
module.exports.Activity = nativeBinding.Activity
module.exports.AntennaSrc = nativeBinding.AntennaSrc module.exports.AntennaSrc = nativeBinding.AntennaSrc
module.exports.ApObject = nativeBinding.ApObject
module.exports.ChatEvent = nativeBinding.ChatEvent module.exports.ChatEvent = nativeBinding.ChatEvent
module.exports.ChatIndexEvent = nativeBinding.ChatIndexEvent module.exports.ChatIndexEvent = nativeBinding.ChatIndexEvent
module.exports.checkWordMute = nativeBinding.checkWordMute module.exports.checkWordMute = nativeBinding.checkWordMute
@ -439,13 +439,17 @@ module.exports.PushSubscriptionType = nativeBinding.PushSubscriptionType
module.exports.RelayStatus = nativeBinding.RelayStatus module.exports.RelayStatus = nativeBinding.RelayStatus
module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestationChallenges module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestationChallenges
module.exports.renderAccept = nativeBinding.renderAccept module.exports.renderAccept = nativeBinding.renderAccept
module.exports.renderAdd = nativeBinding.renderAdd
module.exports.renderEmoji = nativeBinding.renderEmoji module.exports.renderEmoji = nativeBinding.renderEmoji
module.exports.renderFlag = nativeBinding.renderFlag module.exports.renderFlag = nativeBinding.renderFlag
module.exports.renderFollow = nativeBinding.renderFollow module.exports.renderFollow = nativeBinding.renderFollow
module.exports.renderFollowRelay = nativeBinding.renderFollowRelay module.exports.renderFollowRelay = nativeBinding.renderFollowRelay
module.exports.renderHashtag = nativeBinding.renderHashtag module.exports.renderHashtag = nativeBinding.renderHashtag
module.exports.renderLike = nativeBinding.renderLike
module.exports.renderMention = nativeBinding.renderMention module.exports.renderMention = nativeBinding.renderMention
module.exports.renderRead = nativeBinding.renderRead module.exports.renderRead = nativeBinding.renderRead
module.exports.renderReject = nativeBinding.renderReject
module.exports.renderRemove = nativeBinding.renderRemove
module.exports.renderTombstone = nativeBinding.renderTombstone module.exports.renderTombstone = nativeBinding.renderTombstone
module.exports.safeForSql = nativeBinding.safeForSql module.exports.safeForSql = nativeBinding.safeForSql
module.exports.sendPushNotification = nativeBinding.sendPushNotification module.exports.sendPushNotification = nativeBinding.sendPushNotification

View file

@ -1,23 +1,22 @@
use super::*; use super::*;
use crate::{config::CONFIG, misc::user}; use crate::misc::user;
use uuid::Uuid;
#[macros::export(object)] #[macros::export(object)]
pub struct ApAccept { pub struct ApAccept {
pub id: String, pub id: String,
pub r#type: ApObject, pub r#type: Activity,
pub actor: String, pub actor: String,
pub object: follow::ApFollow, pub object: follow::ApFollow,
} }
impl ActivityPubObject for ApAccept {} impl ApObject for ApAccept {}
impl ApAccept { impl ApAccept {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
fn new(user_id: String, follow_object: follow::ApFollow) -> Self { fn new(user_id: String, follow_object: follow::ApFollow) -> Self {
Self { Self {
id: format!("{}/{}", CONFIG.url, Uuid::new_v4()), id: random_local_uri(),
r#type: ApObject::Accept, r#type: Activity::Accept,
actor: user::local_uri(user_id), actor: user::local_uri(user_id),
object: follow_object, object: follow_object,
} }

View file

@ -0,0 +1,34 @@
//! Add note to featured collection (pinned posts)
use super::*;
use crate::misc::{note, user};
#[macros::export(object)]
pub struct ApAdd {
pub r#type: Activity,
pub actor: String,
pub target: String,
pub object: String,
}
impl ApObject for ApAdd {}
impl ApAdd {
#[allow(dead_code)] // TODO: remove this line by actually using it
fn new(user_id: String, note_id: String) -> Self {
let actor_uri = user::local_uri(user_id);
let collection_uri = format!("{}/collections/featured", actor_uri);
Self {
r#type: Activity::Add,
actor: actor_uri,
target: collection_uri,
object: note::local_uri(note_id),
}
}
}
#[macros::ts_export]
pub fn render_add(user_id: String, note_id: String) -> ApAdd {
ApAdd::new(user_id, note_id)
}

View file

@ -5,7 +5,7 @@ use chrono::Utc;
#[macros::export(object)] #[macros::export(object)]
pub struct ApEmoji { pub struct ApEmoji {
pub id: String, pub id: String,
pub r#type: ApObject, pub r#type: Activity,
pub name: String, pub name: String,
pub updated: String, pub updated: String,
pub icon: Icon, pub icon: Icon,
@ -13,26 +13,25 @@ pub struct ApEmoji {
#[macros::export(object)] #[macros::export(object)]
pub struct Icon { pub struct Icon {
pub r#type: ApObject, pub r#type: Activity,
pub media_type: String, pub media_type: String,
pub url: String, pub url: String,
} }
impl ActivityPubObject for ApEmoji {} impl ApObject for ApEmoji {}
impl ApEmoji { impl ApEmoji {
#[allow(dead_code)] // TODO: remove this line pub fn new(emoji: emoji::Model) -> Self {
fn new(emoji: emoji::Model) -> Self {
Self { Self {
id: misc::emoji::local_uri(&emoji.name), id: misc::emoji::local_uri(&emoji.name),
r#type: ApObject::Emoji, r#type: Activity::Emoji,
name: format!(":{}:", emoji.name), name: format!(":{}:", emoji.name),
updated: emoji updated: emoji
.updated_at .updated_at
.unwrap_or_else(|| Utc::now().into()) .unwrap_or_else(|| Utc::now().into())
.to_rfc3339(), .to_rfc3339(),
icon: Icon { icon: Icon {
r#type: ApObject::Image, r#type: Activity::Image,
media_type: emoji.r#type.unwrap_or_else(|| "image/png".to_owned()), media_type: emoji.r#type.unwrap_or_else(|| "image/png".to_owned()),
url: emoji.public_url, url: emoji.public_url,
}, },

View file

@ -3,23 +3,23 @@ use crate::{federation::internal_actor, misc::user};
#[macros::export(object)] #[macros::export(object)]
pub struct ApFlag { pub struct ApFlag {
pub r#type: ApObject, pub r#type: Activity,
pub actor: String, pub actor: String,
pub content: String, pub content: String,
// TODO: object can be an array of uri's // TODO: object can be an array of uri's
pub object: String, pub object: String,
} }
impl ActivityPubObject for ApFlag {} impl ApObject for ApFlag {}
impl ApFlag { impl ApFlag {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
async fn new( async fn new(
target_user_uri: String, target_user_uri: String,
comment: String, comment: String,
) -> Result<Self, internal_actor::instance::Error> { ) -> Result<Self, internal_actor::instance::Error> {
Ok(Self { Ok(Self {
r#type: ApObject::Flag, r#type: Activity::Flag,
actor: user::local_uri(&internal_actor::instance::get().await?.id), actor: user::local_uri(&internal_actor::instance::get().await?.id),
content: comment, content: comment,
object: target_user_uri, object: target_user_uri,

View file

@ -4,12 +4,12 @@ use crate::{config::CONFIG, federation::internal_actor, misc::user};
#[macros::export(object)] #[macros::export(object)]
pub struct ApFollow { pub struct ApFollow {
pub id: String, pub id: String,
pub r#type: ApObject, pub r#type: Activity,
pub actor: String, pub actor: String,
pub object: String, pub object: String,
} }
impl ActivityPubObject for ApFollow {} impl ApObject for ApFollow {}
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
@ -20,7 +20,7 @@ pub enum Error {
} }
impl ApFollow { impl ApFollow {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
fn new( fn new(
follower: UserLike, follower: UserLike,
followee: UserLike, followee: UserLike,
@ -30,7 +30,7 @@ impl ApFollow {
id: request_id.unwrap_or_else(|| { id: request_id.unwrap_or_else(|| {
format!("{}/follows/{}/{}", CONFIG.url, follower.id, followee.id) format!("{}/follows/{}/{}", CONFIG.url, follower.id, followee.id)
}), }),
r#type: ApObject::Follow, r#type: Activity::Follow,
actor: match user::is_local!(follower) { actor: match user::is_local!(follower) {
true => user::local_uri(follower.id), true => user::local_uri(follower.id),
false => follower.uri.ok_or(Error::MissingFollowerUri)?, false => follower.uri.ok_or(Error::MissingFollowerUri)?,
@ -42,11 +42,11 @@ impl ApFollow {
}) })
} }
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
async fn new_relay(relay_id: String) -> Result<Self, internal_actor::relay::Error> { async fn new_relay(relay_id: String) -> Result<Self, internal_actor::relay::Error> {
Ok(Self { Ok(Self {
id: format!("{}/activities/follow-relay/{}", CONFIG.url, relay_id), id: format!("{}/activities/follow-relay/{}", CONFIG.url, relay_id),
r#type: ApObject::Follow, r#type: Activity::Follow,
actor: user::local_uri(internal_actor::relay::get_id().await?), actor: user::local_uri(internal_actor::relay::get_id().await?),
object: AS_PUBLIC_URL.to_owned(), object: AS_PUBLIC_URL.to_owned(),
}) })

View file

@ -4,18 +4,18 @@ use crate::config::CONFIG;
#[macros::export(object)] #[macros::export(object)]
pub struct ApHashtag { pub struct ApHashtag {
pub id: String, pub id: String,
pub r#type: ApObject, pub r#type: Activity,
pub name: String, pub name: String,
} }
impl ActivityPubObject for ApHashtag {} impl ApObject for ApHashtag {}
impl ApHashtag { impl ApHashtag {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
fn new(tag_name: &str) -> Self { fn new(tag_name: &str) -> Self {
Self { Self {
id: format!("{}/tags/{}", CONFIG.url, urlencoding::encode(tag_name)), id: format!("{}/tags/{}", CONFIG.url, urlencoding::encode(tag_name)),
r#type: ApObject::Hashtag, r#type: Activity::Hashtag,
name: format!("#{}", tag_name), name: format!("#{}", tag_name),
} }
} }

View file

@ -0,0 +1,74 @@
use super::{emoji::ApEmoji, *};
use crate::{
config::CONFIG,
database::db_conn,
misc::{self, user},
model::entity::{emoji, note, note_reaction},
};
use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter, QuerySelect};
#[macros::errors]
pub enum Error {
#[doc = "Nonexistent note"]
#[error("note {0} not found")]
NoteNotFound(String),
#[doc = "Database error"]
#[error(transparent)]
Db(#[from] DbErr),
}
#[macros::export(object, use_nullable = false)]
pub struct ApLike {
pub id: String,
pub r#type: Activity,
pub actor: String,
pub object: String,
pub content: String,
pub tag: Option<Vec<ApEmoji>>,
}
impl ApObject for ApLike {}
impl ApLike {
#[allow(dead_code)] // TODO: remove this line by actually using it
async fn new(reaction: note_reaction::Model) -> Result<Self, Error> {
let db = db_conn().await?;
let note_uri = {
let note_uri = note::Entity::find()
.select_only()
.column(note::Column::Uri)
.filter(note::Column::Id.eq(&reaction.note_id))
.into_tuple::<Option<String>>()
.one(db)
.await?;
match note_uri {
Some(Some(uri)) => uri,
Some(None) => misc::note::local_uri(reaction.note_id),
None => return Err(Error::NoteNotFound(reaction.note_id)),
}
};
let tag = emoji::Entity::find()
.filter(emoji::Column::Name.eq(reaction.reaction.replace(':', "")))
.filter(emoji::Column::Host.is_null())
.one(db)
.await?
.map(|emoji| vec![ApEmoji::new(emoji)]);
Ok(Self {
id: format!("{}/likes/{}", CONFIG.url, reaction.id),
r#type: Activity::Like,
actor: user::local_uri(reaction.user_id),
object: note_uri,
content: reaction.reaction,
tag,
})
}
}
#[macros::ts_export]
pub async fn render_like(reaction: note_reaction::Model) -> Result<ApLike, Error> {
ApLike::new(reaction).await
}

View file

@ -7,18 +7,18 @@ pub struct MissingRemoteUserUri;
#[macros::export(object)] #[macros::export(object)]
pub struct ApMention { pub struct ApMention {
pub r#type: ApObject, pub r#type: Activity,
pub href: String, pub href: String,
pub name: String, pub name: String,
} }
impl ActivityPubObject for ApMention {} impl ApObject for ApMention {}
impl ApMention { impl ApMention {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
fn new(user: UserLike) -> Result<Self, MissingRemoteUserUri> { fn new(user: UserLike) -> Result<Self, MissingRemoteUserUri> {
Ok(Self { Ok(Self {
r#type: ApObject::Mention, r#type: Activity::Mention,
href: match user::is_local!(user) { href: match user::is_local!(user) {
true => user::local_uri(user.id), true => user::local_uri(user.id),
false => user.uri.ok_or(MissingRemoteUserUri)?, false => user.uri.ok_or(MissingRemoteUserUri)?,

View file

@ -1,29 +1,44 @@
pub mod accept; pub mod accept;
pub mod add;
pub mod emoji; pub mod emoji;
pub mod flag; pub mod flag;
pub mod follow; pub mod follow;
pub mod hashtag; pub mod hashtag;
pub mod like;
pub mod mention; pub mod mention;
pub mod read; pub mod read;
pub mod reject;
pub mod remove;
pub mod tombstone; pub mod tombstone;
pub trait ActivityPubObject {} pub trait ApObject {}
#[macros::export(string_enum)] #[macros::export(string_enum)]
pub enum ApObject { pub enum Activity {
Accept, Accept,
Add,
Emoji, Emoji,
Flag, Flag,
Follow, Follow,
Hashtag, Hashtag,
Like,
Mention, Mention,
Image, Image,
Read, Read,
Reject,
Remove,
Tombstone, Tombstone,
} }
const AS_PUBLIC_URL: &str = "https://www.w3.org/ns/activitystreams#Public"; const AS_PUBLIC_URL: &str = "https://www.w3.org/ns/activitystreams#Public";
use crate::config::CONFIG;
use uuid::Uuid;
fn random_local_uri() -> String {
format!("{}/{}", CONFIG.url, Uuid::new_v4())
}
#[macros::export(object)] #[macros::export(object)]
pub struct UserLike { pub struct UserLike {
pub id: String, pub id: String,

View file

@ -3,18 +3,18 @@ use crate::misc::user;
#[macros::export(object)] #[macros::export(object)]
pub struct ApRead { pub struct ApRead {
pub r#type: ApObject, pub r#type: Activity,
pub actor: String, pub actor: String,
pub object: String, pub object: String,
} }
impl ActivityPubObject for ApRead {} impl ApObject for ApRead {}
impl ApRead { impl ApRead {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
fn new(user_id: String, message_uri: String) -> Self { fn new(user_id: String, message_uri: String) -> Self {
Self { Self {
r#type: ApObject::Read, r#type: Activity::Read,
actor: user::local_uri(user_id), actor: user::local_uri(user_id),
object: message_uri, object: message_uri,
} }

View file

@ -0,0 +1,29 @@
use super::*;
use crate::misc::user;
#[macros::export(object)]
pub struct ApReject {
pub id: String,
pub r#type: Activity,
pub actor: String,
pub object: follow::ApFollow,
}
impl ApObject for ApReject {}
impl ApReject {
#[allow(dead_code)] // TODO: remove this line by actually using it
fn new(user_id: String, follow_object: follow::ApFollow) -> Self {
Self {
id: random_local_uri(),
r#type: Activity::Accept,
actor: user::local_uri(user_id),
object: follow_object,
}
}
}
#[macros::ts_export]
pub fn render_reject(user_id: String, follow_object: follow::ApFollow) -> ApReject {
ApReject::new(user_id, follow_object)
}

View file

@ -0,0 +1,34 @@
//! Remove note from featured collection (pinned posts)
use super::*;
use crate::misc::{note, user};
#[macros::export(object)]
pub struct ApRemove {
pub r#type: Activity,
pub actor: String,
pub target: String,
pub object: String,
}
impl ApObject for ApRemove {}
impl ApRemove {
#[allow(dead_code)] // TODO: remove this line by actually using it
fn new(user_id: String, note_id: String) -> Self {
let actor_uri = user::local_uri(user_id);
let collection_uri = format!("{}/collections/featured", actor_uri);
Self {
r#type: Activity::Remove,
actor: actor_uri,
target: collection_uri,
object: note::local_uri(note_id),
}
}
}
#[macros::ts_export]
pub fn render_remove(user_id: String, note_id: String) -> ApRemove {
ApRemove::new(user_id, note_id)
}

View file

@ -4,17 +4,17 @@ use crate::misc::note;
#[macros::export(object)] #[macros::export(object)]
pub struct ApTombstone { pub struct ApTombstone {
pub id: String, pub id: String,
pub r#type: ApObject, pub r#type: Activity,
} }
impl ActivityPubObject for ApTombstone {} impl ApObject for ApTombstone {}
impl ApTombstone { impl ApTombstone {
#[allow(dead_code)] // TODO: remove this line #[allow(dead_code)] // TODO: remove this line by actually using it
fn new(note_id: String) -> Self { fn new(note_id: String) -> Self {
Self { Self {
id: note::local_uri(note_id), id: note::local_uri(note_id),
r#type: ApObject::Tombstone, r#type: Activity::Tombstone,
} }
} }
} }

View file

@ -12,7 +12,7 @@ pub enum Error {
#[error("@instance.actor not found")] #[error("@instance.actor not found")]
InstanceActorNotFound, InstanceActorNotFound,
#[error(transparent)] #[error(transparent)]
#[doc = "database error"] #[doc = "Database error"]
Db(#[from] DbErr), Db(#[from] DbErr),
} }

View file

@ -1,7 +1,7 @@
//! In-memory relay actor id cache //! In-memory relay actor id cache
use crate::{database::db_conn, model::entity::user}; use crate::{database::db_conn, model::entity::user};
use sea_orm::{prelude::*, QuerySelect, SelectColumns}; use sea_orm::{prelude::*, QuerySelect};
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
pub const USERNAME: &str = "relay.actor"; pub const USERNAME: &str = "relay.actor";
@ -12,7 +12,7 @@ pub enum Error {
#[error("@relay.actor not found")] #[error("@relay.actor not found")]
RelayActorNotFound, RelayActorNotFound,
#[error(transparent)] #[error(transparent)]
#[doc = "database error"] #[doc = "Database error"]
Db(#[from] DbErr), Db(#[from] DbErr),
} }
@ -22,7 +22,7 @@ async fn set_id_cache() -> Result<&'static str, Error> {
tracing::debug!("caching @relay.actor"); tracing::debug!("caching @relay.actor");
let found_id = user::Entity::find() let found_id = user::Entity::find()
.select_only() .select_only()
.select_column(user::Column::Id) .column(user::Column::Id)
.filter(user::Column::Username.eq(USERNAME)) .filter(user::Column::Username.eq(USERNAME))
.filter(user::Column::Host.is_null()) .filter(user::Column::Host.is_null())
.into_tuple::<String>() .into_tuple::<String>()

View file

@ -14,7 +14,7 @@ pub enum Error {
HttpClient(#[from] http_client::Error), HttpClient(#[from] http_client::Error),
#[error("HTTP request failed")] #[error("HTTP request failed")]
Http(#[from] isahc::Error), Http(#[from] isahc::Error),
#[doc = "bad HTTP status"] #[doc = "Bad HTTP status"]
#[error("bad HTTP status ({0})")] #[error("bad HTTP status ({0})")]
BadStatus(String), BadStatus(String),
#[error("failed to parse HTTP response body as text")] #[error("failed to parse HTTP response body as text")]

View file

@ -145,7 +145,7 @@ pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, DbErr> {
#[macros::for_ts] #[macros::for_ts]
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
#[doc = "database error"] #[doc = "Database error"]
#[error(transparent)] #[error(transparent)]
Db(#[from] DbErr), Db(#[from] DbErr),
#[error("failed to serialize nodeinfo into JSON")] #[error("failed to serialize nodeinfo into JSON")]

View file

@ -15,7 +15,7 @@ pub enum Error {
HttpClient(#[from] http_client::Error), HttpClient(#[from] http_client::Error),
#[error("HTTP request failed")] #[error("HTTP request failed")]
Isahc(#[from] isahc::Error), Isahc(#[from] isahc::Error),
#[doc = "bad HTTP status"] #[doc = "Bad HTTP status"]
#[error("bad HTTP status ({0})")] #[error("bad HTTP status ({0})")]
BadStatus(String), BadStatus(String),
#[error("failed to decode an image")] #[error("failed to decode an image")]
@ -24,10 +24,10 @@ pub enum Error {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("failed to extract the exif data")] #[error("failed to extract the exif data")]
Exif(#[from] nom_exif::Error), Exif(#[from] nom_exif::Error),
#[doc = "too many fetch attempts"] #[doc = "Too many fetch attempts"]
#[error("too many fetch attempts for {0}")] #[error("too many fetch attempts for {0}")]
TooManyAttempts(String), TooManyAttempts(String),
#[doc = "unsupported image type"] #[doc = "Unsupported image type"]
#[error("unsupported image type ({0})")] #[error("unsupported image type ({0})")]
UnsupportedImage(String), UnsupportedImage(String),
} }

View file

@ -4,7 +4,7 @@ use identicon_rs::{error::IdenticonError, Identicon};
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
#[doc = "failed to generate identicon"] #[doc = "Failed to generate identicon"]
#[error(transparent)] #[error(transparent)]
Identicon(#[from] IdenticonError), Identicon(#[from] IdenticonError),
#[error("Redis cache operation has failed")] #[error("Redis cache operation has failed")]

View file

@ -2,17 +2,17 @@
use crate::{cache, database::db_conn, model::entity::user}; use crate::{cache, database::db_conn, model::entity::user};
use chrono::Duration; use chrono::Duration;
use sea_orm::{DbErr, EntityTrait, QuerySelect, SelectColumns}; use sea_orm::{DbErr, EntityTrait, QuerySelect};
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
#[doc = "database error"] #[doc = "Database error"]
#[error(transparent)] #[error(transparent)]
Db(#[from] DbErr), Db(#[from] DbErr),
#[doc = "cache error"] #[doc = "Cache error"]
#[error(transparent)] #[error(transparent)]
Cache(#[from] cache::redis::Error), Cache(#[from] cache::redis::Error),
#[doc = "user not found"] #[doc = "User not found"]
#[error("user {0} not found")] #[error("user {0} not found")]
NotFound(String), NotFound(String),
} }
@ -26,7 +26,7 @@ pub async fn should_nyaify(reader_user_id: &str) -> Result<bool, Error> {
let fetched_value = user::Entity::find_by_id(reader_user_id) let fetched_value = user::Entity::find_by_id(reader_user_id)
.select_only() .select_only()
.select_column(user::Column::ReadCatLanguage) .column(user::Column::ReadCatLanguage)
.into_tuple::<bool>() .into_tuple::<bool>()
.one(db_conn().await?) .one(db_conn().await?)
.await? .await?

View file

@ -5,7 +5,7 @@ use crate::{
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
#[doc = "database error"] #[doc = "Database error"]
#[error(transparent)] #[error(transparent)]
Db(#[from] sea_orm::DbErr), Db(#[from] sea_orm::DbErr),
#[error("failed to acquire an HTTP client")] #[error("failed to acquire an HTTP client")]

View file

@ -10,7 +10,7 @@ use sea_orm::{prelude::*, QuerySelect};
#[macros::errors] #[macros::errors]
pub enum AntennaCheckError { pub enum AntennaCheckError {
#[doc = "database error"] #[doc = "Database error"]
#[error(transparent)] #[error(transparent)]
Db(#[from] DbErr), Db(#[from] DbErr),
#[error("Redis cache operation has failed")] #[error("Redis cache operation has failed")]

View file

@ -16,7 +16,7 @@ use sea_orm::prelude::*;
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
#[doc = "database error"] #[doc = "Database error"]
#[error(transparent)] #[error(transparent)]
Db(#[from] DbErr), Db(#[from] DbErr),
#[error("Redis cache operation has failed")] #[error("Redis cache operation has failed")]
@ -25,7 +25,7 @@ pub enum Error {
Redis(#[from] RedisError), Redis(#[from] RedisError),
#[error("bad Redis connection")] #[error("bad Redis connection")]
RedisConn(#[from] RedisConnError), RedisConn(#[from] RedisConnError),
#[doc = "provided string is not a valid Firefish ID"] #[doc = "Provided string is not a valid Firefish ID"]
#[error(transparent)] #[error(transparent)]
InvalidId(#[from] InvalidIdError), InvalidId(#[from] InvalidIdError),
#[error("Redis stream operation has failed")] #[error("Redis stream operation has failed")]

View file

@ -15,17 +15,17 @@ use web_push::*;
#[macros::errors] #[macros::errors]
pub enum Error { pub enum Error {
#[doc = "database error"] #[doc = "Database error"]
#[error(transparent)] #[error(transparent)]
Db(#[from] DbErr), Db(#[from] DbErr),
#[error("web push has failed")] #[error("web push has failed")]
WebPush(#[from] WebPushError), WebPush(#[from] WebPushError),
#[error("failed to (de)serialize an object")] #[error("failed to (de)serialize an object")]
Serialize(#[from] serde_json::Error), Serialize(#[from] serde_json::Error),
#[doc = "provided content is invalid"] #[doc = "Provided content is invalid"]
#[error("invalid content ({0})")] #[error("invalid content ({0})")]
InvalidContent(String), InvalidContent(String),
#[doc = "found Mastodon subscription is invalid"] #[doc = "Found Mastodon subscription is invalid"]
#[error("invalid subscription ({0})")] #[error("invalid subscription ({0})")]
InvalidSubscription(String), InvalidSubscription(String),
#[error("invalid notification ID")] #[error("invalid notification ID")]

View file

@ -17,13 +17,13 @@
"format": "pnpm biome format --write src" "format": "pnpm biome format --write src"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.21.3", "@bull-board/api": "5.21.4",
"@bull-board/koa": "5.21.3", "@bull-board/koa": "5.21.4",
"@bull-board/ui": "5.21.3", "@bull-board/ui": "5.21.4",
"@discordapp/twemoji": "15.0.3", "@discordapp/twemoji": "15.1.0",
"@koa/cors": "5.0.0", "@koa/cors": "5.0.0",
"@koa/multer": "3.0.2", "@koa/multer": "3.0.2",
"@koa/router": "12.0.1", "@koa/router": "13.0.0",
"@ladjs/koa-views": "9.0.0", "@ladjs/koa-views": "9.0.0",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.19.0", "@redocly/openapi-core": "1.19.0",
@ -33,8 +33,8 @@
"archiver": "7.0.1", "archiver": "7.0.1",
"async-lock": "1.4.1", "async-lock": "1.4.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"aws-sdk": "2.1671.0", "aws-sdk": "2.1677.0",
"axios": "1.7.3", "axios": "1.7.4",
"backend-rs": "workspace:*", "backend-rs": "workspace:*",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"bull": "4.16.0", "bull": "4.16.0",
@ -49,7 +49,7 @@
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "19.4.0", "file-type": "19.4.1",
"firefish-js": "workspace:*", "firefish-js": "workspace:*",
"fluent-ffmpeg": "2.1.3", "fluent-ffmpeg": "2.1.3",
"form-data": "4.0.0", "form-data": "4.0.0",
@ -97,7 +97,7 @@
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"sanitize-html": "2.13.0", "sanitize-html": "2.13.0",
"semver": "7.6.3", "semver": "7.6.3",
"sharp": "0.33.4", "sharp": "0.33.5",
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "2.7.0", "summaly": "2.7.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
@ -133,7 +133,7 @@
"@types/koa__cors": "5.0.0", "@types/koa__cors": "5.0.0",
"@types/koa__multer": "2.0.7", "@types/koa__multer": "2.0.7",
"@types/koa__router": "12.0.4", "@types/koa__router": "12.0.4",
"@types/node": "20.14.14", "@types/node": "20.15.0",
"@types/node-fetch": "2.6.11", "@types/node-fetch": "2.6.11",
"@types/nodemailer": "6.4.15", "@types/nodemailer": "6.4.15",
"@types/oauth": "0.9.5", "@types/oauth": "0.9.5",
@ -163,7 +163,7 @@
"ts-node": "10.9.2", "ts-node": "10.9.2",
"tsc-alias": "1.8.10", "tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"type-fest": "4.24.0", "type-fest": "4.25.0",
"typescript": "5.5.4", "typescript": "5.5.4",
"webpack": "5.93.0", "webpack": "5.93.0",
"ws": "8.18.0" "ws": "8.18.0"

View file

@ -12,11 +12,7 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => {
await extractEmojis(activity.tag || [], actor.host).catch(() => null); await extractEmojis(activity.tag || [], actor.host).catch(() => null);
return await create( return await create(actor, note, activity.content || activity.name)
actor,
note,
activity._misskey_reaction || activity.content || activity.name,
)
.catch((e) => { .catch((e) => {
if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") { if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") {
return "skip: already reacted"; return "skip: already reacted";

View file

@ -550,7 +550,6 @@ export const WellKnownContext = {
// Misskey // Misskey
misskey: "https://misskey-hub.net/ns#", misskey: "https://misskey-hub.net/ns#",
_misskey_talk: "misskey:_misskey_talk", _misskey_talk: "misskey:_misskey_talk",
_misskey_reaction: "misskey:_misskey_reaction",
_misskey_votes: "misskey:_misskey_votes", _misskey_votes: "misskey:_misskey_votes",
_misskey_summary: "misskey:_misskey_summary", _misskey_summary: "misskey:_misskey_summary",
isCat: "misskey:isCat", isCat: "misskey:isCat",

View file

@ -1,9 +0,0 @@
import { config } from "@/config.js";
import type { ILocalUser } from "@/models/entities/user.js";
export default (user: ILocalUser, target: any, object: any) => ({
type: "Add",
actor: `${config.url}/users/${user.id}`,
target,
object,
});

View file

@ -1,31 +0,0 @@
import { IsNull } from "typeorm";
import { config } from "@/config.js";
import type { NoteReaction } from "@/models/entities/note-reaction.js";
import type { Note } from "@/models/entities/note.js";
import { Emojis } from "@/models/index.js";
import { renderEmoji } from "backend-rs";
export const renderLike = async (noteReaction: NoteReaction, note: Note) => {
const reaction = noteReaction.reaction;
const object = {
type: "Like",
id: `${config.url}/likes/${noteReaction.id}`,
actor: `${config.url}/users/${noteReaction.userId}`,
object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
content: reaction,
_misskey_reaction: reaction,
} as any;
if (reaction.startsWith(":")) {
const name = reaction.replace(/:/g, "");
const emoji = await Emojis.findOneBy({
name,
host: IsNull(),
});
if (emoji) object.tag = [renderEmoji(emoji)];
}
return object;
};

View file

@ -1,8 +0,0 @@
import { config } from "@/config.js";
import type { User } from "@/models/entities/user.js";
export default (object: any, user: { id: User["id"] }) => ({
type: "Reject",
actor: `${config.url}/users/${user.id}`,
object,
});

View file

@ -1,9 +0,0 @@
import { config } from "@/config.js";
import type { User } from "@/models/entities/user.js";
export default (user: { id: User["id"] }, target: any, object: any) => ({
type: "Remove",
actor: `${config.url}/users/${user.id}`,
target,
object,
});

View file

@ -7,6 +7,7 @@ import {
isBlockedServer, isBlockedServer,
isSelfHost, isSelfHost,
renderFollow, renderFollow,
renderLike,
} from "backend-rs"; } from "backend-rs";
import { apGet } from "./request.js"; import { apGet } from "./request.js";
import type { IObject, ICollection, IOrderedCollection } from "./type.js"; import type { IObject, ICollection, IOrderedCollection } from "./type.js";
@ -20,7 +21,6 @@ import {
} from "@/models/index.js"; } from "@/models/index.js";
import { parseUri } from "./db-resolver.js"; import { parseUri } from "./db-resolver.js";
import renderNote from "@/remote/activitypub/renderer/note.js"; import renderNote from "@/remote/activitypub/renderer/note.js";
import { renderLike } from "@/remote/activitypub/renderer/like.js";
import { renderPerson } from "@/remote/activitypub/renderer/person.js"; import { renderPerson } from "@/remote/activitypub/renderer/person.js";
import renderQuestion from "@/remote/activitypub/renderer/question.js"; import renderQuestion from "@/remote/activitypub/renderer/question.js";
import renderCreate from "@/remote/activitypub/renderer/create.js"; import renderCreate from "@/remote/activitypub/renderer/create.js";
@ -181,7 +181,7 @@ export default class Resolver {
} }
case "likes": { case "likes": {
const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id }); const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id });
return renderActivity(renderLike(reaction, { uri: null })); return renderActivity(await renderLike(reaction));
} }
case "follows": { case "follows": {
// if rest is a <followee id> // if rest is a <followee id>

View file

@ -303,7 +303,7 @@ export interface IRemove extends IActivity {
export interface ILike extends IActivity { export interface ILike extends IActivity {
type: "Like" | "EmojiReaction" | "EmojiReact"; type: "Like" | "EmojiReaction" | "EmojiReact";
_misskey_reaction?: string; content?: string;
} }
export interface IAnnounce extends IActivity { export interface IAnnounce extends IActivity {

View file

@ -14,6 +14,7 @@ import {
isSelfHost, isSelfHost,
renderEmoji, renderEmoji,
renderFollow, renderFollow,
renderLike,
} from "backend-rs"; } from "backend-rs";
import { import {
Notes, Notes,
@ -23,7 +24,6 @@ import {
FollowRequests, FollowRequests,
} from "@/models/index.js"; } from "@/models/index.js";
import type { ILocalUser, User } from "@/models/entities/user.js"; import type { ILocalUser, User } from "@/models/entities/user.js";
import { renderLike } from "@/remote/activitypub/renderer/like.js";
import { getUserKeypair } from "@/misc/keypair-store.js"; import { getUserKeypair } from "@/misc/keypair-store.js";
import { import {
checkFetch, checkFetch,
@ -466,7 +466,7 @@ router.get("/likes/:like", async (ctx) => {
return; return;
} }
ctx.body = renderActivity(await renderLike(reaction, note)); ctx.body = renderActivity(await renderLike(reaction));
const instanceMeta = await fetchMeta(); const instanceMeta = await fetchMeta();
if (instanceMeta.secureMode || instanceMeta.privateMode) { if (instanceMeta.secureMode || instanceMeta.privateMode) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); ctx.set("Cache-Control", "private, max-age=0, must-revalidate");

View file

@ -2,7 +2,6 @@ import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import { renderUndo } from "@/remote/activitypub/renderer/undo.js"; import { renderUndo } from "@/remote/activitypub/renderer/undo.js";
import { renderBlock } from "@/remote/activitypub/renderer/block.js"; import { renderBlock } from "@/remote/activitypub/renderer/block.js";
import { deliver } from "@/queue/index.js"; import { deliver } from "@/queue/index.js";
import renderReject from "@/remote/activitypub/renderer/reject.js";
import type { Blocking } from "@/models/entities/blocking.js"; import type { Blocking } from "@/models/entities/blocking.js";
import type { User } from "@/models/entities/user.js"; import type { User } from "@/models/entities/user.js";
import { import {
@ -20,6 +19,7 @@ import {
publishToUserStream, publishToUserStream,
UserEvent, UserEvent,
renderFollow, renderFollow,
renderReject,
} from "backend-rs"; } from "backend-rs";
import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { webhookDeliver } from "@/queue/index.js"; import { webhookDeliver } from "@/queue/index.js";
@ -103,8 +103,8 @@ async function cancelRequest(follower: User, followee: User) {
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) {
const content = renderActivity( const content = renderActivity(
renderReject( renderReject(
followee.id,
renderFollow(follower, followee, request.requestId!), renderFollow(follower, followee, request.requestId!),
followee,
), ),
); );
deliver(followee.id, content, follower.inbox); deliver(followee.id, content, follower.inbox);

View file

@ -1,5 +1,4 @@
import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderReject from "@/remote/activitypub/renderer/reject.js";
import { deliver } from "@/queue/index.js"; import { deliver } from "@/queue/index.js";
import createFollowRequest from "./requests/create.js"; import createFollowRequest from "./requests/create.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
@ -23,6 +22,7 @@ import {
UserEvent, UserEvent,
renderAccept, renderAccept,
renderFollow, renderFollow,
renderReject,
} from "backend-rs"; } from "backend-rs";
import { createNotification } from "@/services/create-notification.js"; import { createNotification } from "@/services/create-notification.js";
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
@ -196,7 +196,7 @@ export default async function (
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocked) { if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocked) {
// リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。 // リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。
const content = renderActivity( const content = renderActivity(
renderReject(renderFollow(follower, followee, requestId), followee), renderReject(followee.id, renderFollow(follower, followee, requestId)),
); );
deliver(followee.id, content, follower.inbox); deliver(followee.id, content, follower.inbox);
return; return;

View file

@ -4,10 +4,10 @@ import {
publishToUserStream, publishToUserStream,
UserEvent, UserEvent,
renderFollow, renderFollow,
renderReject,
} from "backend-rs"; } from "backend-rs";
import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import { renderUndo } from "@/remote/activitypub/renderer/undo.js"; import { renderUndo } from "@/remote/activitypub/renderer/undo.js";
import renderReject from "@/remote/activitypub/renderer/reject.js";
import { deliver, webhookDeliver } from "@/queue/index.js"; import { deliver, webhookDeliver } from "@/queue/index.js";
import Logger from "../logger.js"; import Logger from "../logger.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
@ -22,6 +22,7 @@ export default async function (
id: User["id"]; id: User["id"];
host: User["host"]; host: User["host"];
uri: User["host"]; uri: User["host"];
username: User["username"];
inbox: User["inbox"]; inbox: User["inbox"];
sharedInbox: User["sharedInbox"]; sharedInbox: User["sharedInbox"];
}, },
@ -29,6 +30,7 @@ export default async function (
id: User["id"]; id: User["id"];
host: User["host"]; host: User["host"];
uri: User["host"]; uri: User["host"];
username: User["username"];
inbox: User["inbox"]; inbox: User["inbox"];
sharedInbox: User["sharedInbox"]; sharedInbox: User["sharedInbox"];
}, },
@ -79,7 +81,7 @@ export default async function (
if (Users.isLocalUser(followee) && Users.isRemoteUser(follower)) { if (Users.isLocalUser(followee) && Users.isRemoteUser(follower)) {
// local user has null host // local user has null host
const content = renderActivity( const content = renderActivity(
renderReject(renderFollow(follower, followee), followee), renderReject(followee.id, renderFollow(follower, followee)),
); );
deliver(followee.id, content, follower.inbox); deliver(followee.id, content, follower.inbox);
} }

View file

@ -1,5 +1,4 @@
import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderReject from "@/remote/activitypub/renderer/reject.js";
import { deliver, webhookDeliver } from "@/queue/index.js"; import { deliver, webhookDeliver } from "@/queue/index.js";
import { import {
Event, Event,
@ -7,6 +6,7 @@ import {
publishToUserStream, publishToUserStream,
UserEvent, UserEvent,
renderFollow, renderFollow,
renderReject,
} from "backend-rs"; } from "backend-rs";
import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import { Users, FollowRequests, Followings } from "@/models/index.js"; import { Users, FollowRequests, Followings } from "@/models/index.js";
@ -19,6 +19,7 @@ type Local =
id: ILocalUser["id"]; id: ILocalUser["id"];
host: ILocalUser["host"]; host: ILocalUser["host"];
uri: ILocalUser["uri"]; uri: ILocalUser["uri"];
username: ILocalUser["username"];
}; };
type Remote = type Remote =
| IRemoteUser | IRemoteUser
@ -26,6 +27,7 @@ type Remote =
id: IRemoteUser["id"]; id: IRemoteUser["id"];
host: IRemoteUser["host"]; host: IRemoteUser["host"];
uri: IRemoteUser["uri"]; uri: IRemoteUser["uri"];
username: IRemoteUser["username"];
inbox: IRemoteUser["inbox"]; inbox: IRemoteUser["inbox"];
}; };
type Both = Local | Remote; type Both = Local | Remote;
@ -109,8 +111,8 @@ async function deliverReject(followee: Local, follower: Remote) {
const content = renderActivity( const content = renderActivity(
renderReject( renderReject(
followee.id,
renderFollow(follower, followee, request?.requestId || undefined), renderFollow(follower, followee, request?.requestId || undefined),
followee,
), ),
); );
deliver(followee.id, content, follower.inbox); deliver(followee.id, content, follower.inbox);

View file

@ -1,13 +1,10 @@
import { config } from "@/config.js";
import renderAdd from "@/remote/activitypub/renderer/add.js";
import renderRemove from "@/remote/activitypub/renderer/remove.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IdentifiableError } from "@/misc/identifiable-error.js";
import type { User } from "@/models/entities/user.js"; import type { User } from "@/models/entities/user.js";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import { Notes, UserNotePinings, Users } from "@/models/index.js"; import { Notes, UserNotePinings, Users } from "@/models/index.js";
import type { UserNotePining } from "@/models/entities/user-note-pining.js"; import type { UserNotePining } from "@/models/entities/user-note-pining.js";
import { genIdAt } from "backend-rs"; import { genIdAt, renderAdd, renderRemove } from "backend-rs";
import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js"; import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js";
import { deliverToRelays } from "@/services/relay.js"; import { deliverToRelays } from "@/services/relay.js";
@ -115,12 +112,8 @@ export async function deliverPinnedChange(
if (!Users.isLocalUser(user)) return; if (!Users.isLocalUser(user)) return;
const target = `${config.url}/users/${user.id}/collections/featured`;
const item = `${config.url}/notes/${noteId}`;
const content = renderActivity( const content = renderActivity(
isAddition isAddition ? renderAdd(user.id, noteId) : renderRemove(user.id, noteId),
? renderAdd(user, target, item)
: renderRemove(user, target, item),
); );
deliverToFollowers(user, content); deliverToFollowers(user, content);

View file

@ -1,4 +1,3 @@
import { renderLike } from "@/remote/activitypub/renderer/like.js";
import DeliverManager from "@/remote/activitypub/deliver-manager.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import type { User, IRemoteUser } from "@/models/entities/user.js"; import type { User, IRemoteUser } from "@/models/entities/user.js";
@ -17,6 +16,7 @@ import {
genIdAt, genIdAt,
NoteEvent, NoteEvent,
publishToNoteStream, publishToNoteStream,
renderLike,
toDbReaction, toDbReaction,
} from "backend-rs"; } from "backend-rs";
import { createNotification } from "@/services/create-notification.js"; import { createNotification } from "@/services/create-notification.js";
@ -152,7 +152,7 @@ export default async (
!note.localOnly && !note.localOnly &&
note.visibility !== "hidden" note.visibility !== "hidden"
) { ) {
const content = renderActivity(await renderLike(record, note)); const content = renderActivity(await renderLike(record));
const dm = new DeliverManager(user, content); const dm = new DeliverManager(user, content);
if (note.userHost != null) { if (note.userHost != null) {
const reactee = await Users.findOneBy({ id: note.userId }); const reactee = await Users.findOneBy({ id: note.userId });

View file

@ -1,4 +1,3 @@
import { renderLike } from "@/remote/activitypub/renderer/like.js";
import { renderUndo } from "@/remote/activitypub/renderer/undo.js"; import { renderUndo } from "@/remote/activitypub/renderer/undo.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import DeliverManager from "@/remote/activitypub/deliver-manager.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js";
@ -6,7 +5,12 @@ import { IdentifiableError } from "@/misc/identifiable-error.js";
import type { User, IRemoteUser } from "@/models/entities/user.js"; import type { User, IRemoteUser } from "@/models/entities/user.js";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import { NoteReactions, Users, Notes } from "@/models/index.js"; import { NoteReactions, Users, Notes } from "@/models/index.js";
import { decodeReaction, NoteEvent, publishToNoteStream } from "backend-rs"; import {
decodeReaction,
NoteEvent,
publishToNoteStream,
renderLike,
} from "backend-rs";
export default async ( export default async (
user: { id: User["id"]; host: User["host"] }, user: { id: User["id"]; host: User["host"] },
@ -55,7 +59,7 @@ export default async (
//#region 配信 //#region 配信
if (Users.isLocalUser(user) && !note.localOnly) { if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity( const content = renderActivity(
renderUndo(await renderLike(reaction, note), user.id), renderUndo(await renderLike(reaction), user.id),
); );
const dm = new DeliverManager(user, content); const dm = new DeliverManager(user, content);
if (note.userHost != null) { if (note.userHost != null) {

View file

@ -33,7 +33,7 @@
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/uuid": "10.0.0", "@types/uuid": "10.0.0",
"@vitejs/plugin-vue": "5.1.2", "@vitejs/plugin-vue": "5.1.2",
"@vue/runtime-core": "3.4.37", "@vue/runtime-core": "3.4.38",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "6.0.1", "autosize": "6.0.1",
"broadcast-channel": "7.0.0", "broadcast-channel": "7.0.0",
@ -82,9 +82,9 @@
"typescript": "5.5.4", "typescript": "5.5.4",
"unicode-emoji-json": "0.6.0", "unicode-emoji-json": "0.6.0",
"uuid": "10.0.0", "uuid": "10.0.0",
"vite": "5.4.0", "vite": "5.4.1",
"vite-plugin-compression": "0.5.1", "vite-plugin-compression": "0.5.1",
"vue": "3.4.37", "vue": "3.4.38",
"vue-draggable-plus": "0.5.3", "vue-draggable-plus": "0.5.3",
"vue-plyr": "7.0.0", "vue-plyr": "7.0.0",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",

View file

@ -1,5 +1,5 @@
<template> <template>
<MkModal ref="modal" @click="done(true)" @closed="emit('closed')"> <MkModal ref="modal">
<div class="container"> <div class="container">
<div class="fullwidth top-caption"> <div class="fullwidth top-caption">
<div class="mk-dialog"> <div class="mk-dialog">
@ -50,7 +50,6 @@
:src="image.url" :src="image.url"
:alt="image.comment || undefined" :alt="image.comment || undefined"
:title="image.comment || undefined" :title="image.comment || undefined"
@click="modal!.close()"
/> />
<footer> <footer>
<span>{{ image.type }}</span> <span>{{ image.type }}</span>
@ -89,13 +88,11 @@ const props = withDefaults(
showOkButton?: boolean; showOkButton?: boolean;
showCaptionButton?: boolean; showCaptionButton?: boolean;
showCancelButton?: boolean; showCancelButton?: boolean;
cancelableByBgClick?: boolean;
}>(), }>(),
{ {
showOkButton: true, showOkButton: true,
showCaptionButton: true, showCaptionButton: true,
showCancelButton: true, showCancelButton: true,
cancelableByBgClick: true,
}, },
); );
@ -130,12 +127,6 @@ function cancel() {
done(true); done(true);
} }
// function onBgClick() {
// if (props.cancelableByBgClick) {
// cancel();
// }
// }
function onKeydown(evt) { function onKeydown(evt) {
if (evt.which === 27) { if (evt.which === 27) {
// ESC // ESC
@ -184,7 +175,8 @@ onBeforeUnmount(() => {
inset-inline-start: 0; inset-inline-start: 0;
inset-block-start: 0; inset-block-start: 0;
} }
@media (max-inline-size: 850px) { // TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 850px) {
.container { .container {
flex-direction: column; flex-direction: column;
} }

View file

@ -26,6 +26,7 @@
</div> </div>
<div class="right"> <div class="right">
<span <span
v-if="maxTextLength - textLength < 500"
class="text-count" class="text-count"
:class="{ over: textLength > maxTextLength }" :class="{ over: textLength > maxTextLength }"
>{{ maxTextLength - textLength }}</span >{{ maxTextLength - textLength }}</span

View file

@ -239,7 +239,8 @@ function showMenu(ev) {
rgba(0, 0, 0, 0) 100% rgba(0, 0, 0, 0) 100%
); );
@media (max-inline-size: 1200px) { // TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 1200px) {
display: none; display: none;
} }
} }
@ -270,7 +271,8 @@ function showMenu(ev) {
inset-inline-start: 42px; inset-inline-start: 42px;
inline-size: 140px; inline-size: 140px;
@media (max-inline-size: 450px) { // TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 450px) {
inline-size: 130px; inline-size: 130px;
} }
} }
@ -285,7 +287,8 @@ function showMenu(ev) {
margin-inline-end: 8px; margin-inline-end: 8px;
} }
@media (max-inline-size: 1200px) { // TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 1200px) {
display: none; display: none;
} }
} }
@ -301,7 +304,8 @@ function showMenu(ev) {
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: 0 12px 32px rgb(0 0 0 / 25%); box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
@media (max-inline-size: 1200px) { // TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 1200px) {
margin: auto; margin: auto;
} }
@ -374,7 +378,8 @@ function showMenu(ev) {
padding-block: 8px; padding-block: 8px;
padding-inline: 0; padding-inline: 0;
@media (max-inline-size: 900px) { // TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 900px) {
display: none; display: none;
} }
} }

View file

@ -250,6 +250,9 @@ a {
&:hover { &:hover {
text-decoration-color: currentColor; text-decoration-color: currentColor;
} }
&[target="_blank"] {
-webkit-touch-callout: default;
}
} }
// i { // i {

View file

@ -21,7 +21,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/node": "20.14.14", "@types/node": "20.15.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-fetch-mock": "3.0.3", "jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.5.0", "jest-websocket-mock": "2.5.0",

View file

@ -1,75 +0,0 @@
//! Automatically generate doc comments for error variants from the error messages
use proc_macro2::TokenStream;
use quote::quote;
/// Generates doc comments for error enums.
///
/// # Example
/// ```
/// # use macros_impl::error::error_variants as errors;
/// # macros_impl::macro_doctest!({
/// #[macros::errors]
/// pub enum Error {
/// #[error("config file name is not set")]
/// NoConfigFileName,
/// #[error("failed to read the config file")]
/// ReadConfigFile(#[from] io::Error),
/// #[error("invalid file content ({0})")]
/// #[doc = "invalid file content"]
/// InvalidContent(String),
/// #[error(transparent)]
/// #[doc = "database error"]
/// Db(#[from] sea_orm::DbErr)
/// }
///
/// # }, {
/// /******* the code above expands to *******/
///
/// pub enum Error {
/// #[error("config file name is not set")]
/// #[doc = "config file name is not set"]
/// NoConfigFileName,
/// #[error("failed to read the config file")]
/// #[doc = "failed to read the config file"]
/// ReadConfigFile(#[from] io::Error),
/// #[error("invalid file content ({0})")]
/// #[doc = "invalid file content"]
/// InvalidContent(String),
/// #[error(transparent)]
/// #[doc = "database error"]
/// Db(#[from] sea_orm::DbErr)
/// }
/// # });
/// ```
pub fn error_variants(_attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let mut item: syn::ItemEnum = syn::parse2(item)?;
item.variants = item
.variants
.into_iter()
.map(|mut variant| {
// check if doc attribute is alredy there
if variant.attrs.iter().any(|attr| attr.path().is_ident("doc")) {
return variant;
}
let msg = variant.attrs.iter().find_map(|attr| {
if !attr.path().is_ident("error") {
return None;
}
let lit: syn::LitStr = attr.parse_args().ok()?;
Some(lit.value())
});
// add #[doc] attribute
if let Some(msg) = msg {
variant.attrs.push(syn::parse_quote! { #[doc = #msg] });
}
variant
})
.collect();
Ok(quote! { #item })
}

View file

@ -1,5 +1,4 @@
#![allow(clippy::items_after_test_module)] #![allow(clippy::items_after_test_module)]
pub mod error;
pub mod napi; pub mod napi;
mod util; mod util;

View file

@ -84,11 +84,9 @@ define_wrapper_proc_macro_attributes! {
/// When applied to enums, this macro implements [`std::error::Error`] trait /// When applied to enums, this macro implements [`std::error::Error`] trait
/// and generates a document based on error messages unless there is already a doc comment /// and generates a document based on error messages unless there is already a doc comment
/// ///
/// See [`macros_impl::error::error_variants`] for more details.
///
/// # Example /// # Example
/// ///
/// ``` /// ```ignore
/// # use std::io; /// # use std::io;
/// #[macros::errors] /// #[macros::errors]
/// pub enum Error { /// pub enum Error {
@ -97,13 +95,13 @@ define_wrapper_proc_macro_attributes! {
/// #[error("failed to read the config file")] /// #[error("failed to read the config file")]
/// ReadConfigFile(#[from] io::Error), /// ReadConfigFile(#[from] io::Error),
/// #[error("invalid file content ({0})")] /// #[error("invalid file content ({0})")]
/// #[doc = "invalid file content"] /// #[doc = "Invalid file content"]
/// InvalidContent(String), /// InvalidContent(String),
/// } /// }
/// ``` /// ```
errors(attr, item) { errors(_attr, item) {
#[derive(::thiserror::Error, ::std::fmt::Debug)] #[derive(::thiserror::Error, ::std::fmt::Debug)]
#[macros::error_variants(#attr, #item)] #[error_doc::error_doc]
#item #item
} }
} }
@ -112,7 +110,4 @@ reexport_proc_macro_attributes! {
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/). /// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
/// See [macros_impl::napi::napi] for details. /// See [macros_impl::napi::napi] for details.
macros_impl::napi::napi as napi macros_impl::napi::napi as napi
/// Generates doc comments for error variants from the error messages
macros_impl::error::error_variants as error_variants
} }

View file

@ -12,7 +12,7 @@
"devDependencies": { "devDependencies": {
"firefish-js": "workspace:*", "firefish-js": "workspace:*",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"vite": "5.4.0", "vite": "5.4.1",
"vite-plugin-compression": "0.5.1" "vite-plugin-compression": "0.5.1"
} }
} }

File diff suppressed because it is too large Load diff