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

View file

@ -23,6 +23,7 @@ chrono = { version = "0.4.38", default-features = false }
convert_case = { version = "0.6.0", default-features = false }
cuid2 = { version = "0.1.2", 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 }
identicon-rs = "5.0.1"
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 }
rmp-serde = { version = "1.3.0", default-features = false }
sea-orm = { version = "1.0.0", default-features = false }
serde = { version = "1.0.205", default-features = false }
serde_json = { version = "1.0.122", default-features = false }
serde = { version = "1.0.208", default-features = false }
serde_json = { version = "1.0.125", 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 }
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 }
tracing = { version = "0.1.40", 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.
- 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)
- 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).
## Upcoming breaking change (unreleased)
Please take a look at #10947.
## v20240809
### 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
behavior: Comportament
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
generar, es tancarà la sessió a tots els dispositius.
durant l'inici de sessió. Usualment aquesta acció no és necessària. Si la clau és
torna a generar, es tancarà la sessió a tots els dispositius.
setMultipleBySeparatingWithSpace: Separa diferents entrades amb espais.
reportAbuseOf: Informa d'un abús de {name}
sample: Exemple

View file

@ -128,7 +128,7 @@ pinnedNote: "Pinned post"
pinned: "Pin to profile"
you: "You"
clickToShow: "Click to show"
sensitive: "NSFW"
sensitive: "Sensitive"
add: "Add"
reaction: "Reaction"
reactions: "Reactions"
@ -139,8 +139,8 @@ reactionSetting: "Reactions to show in the reaction picker"
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
rememberNoteVisibility: "Remember post visibility settings"
attachCancel: "Remove attachment"
markAsSensitive: "Mark as NSFW"
unmarkAsSensitive: "Unmark as NSFW"
markAsSensitive: "Mark as sensitive"
unmarkAsSensitive: "Unmark as sensitive"
clickToShowPatterns: "Click to show module patterns"
enterFileName: "Enter filename"
mute: "Mute"
@ -176,7 +176,7 @@ cacheRemoteFilesDescription: "When this setting is disabled, remote files are lo
increase traffic, as thumbnails will not be generated."
markLocalFilesNsfwByDefault: "Mark all new local file as sensitive by default"
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"
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
@ -362,7 +362,7 @@ copyUrl: "Copy URL"
rename: "Rename"
avatar: "Avatar"
banner: "Banner"
nsfw: "NSFW"
nsfw: "Sensitive"
whenServerDisconnected: "When losing connection to the server"
disconnectedFromServer: "Connection to server has been lost"
reload: "Refresh"
@ -727,7 +727,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b
other: "Other"
regenerateLoginToken: "Regenerate sign in token"
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."
setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
fileIdOrUrl: "File ID or URL"
@ -792,7 +792,7 @@ noCrawle: "Reject crawler indexing"
noCrawleDescription: "Ask external search engines to not index your content."
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."
alwaysMarkSensitive: "Mark as NSFW by default"
alwaysMarkSensitive: "Mark as sensitive by default"
loadRawImages: "Load original images instead of showing thumbnails"
disableShowingAnimatedImages: "Don't play animated images"
verificationEmailSent: "A verification email has been sent. Please follow the included
@ -1035,20 +1035,16 @@ type: "Type"
speed: "Speed"
slow: "Slow"
fast: "Fast"
sensitiveMediaDetection: "Detection of NSFW media"
localOnly: "Local only"
remoteOnly: "Remote only"
failedToUpload: "Upload failed"
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."
cannotUploadBecauseExceedsFileSizeLimit: "This file could not be uploaded because
it exceeds the maximum allowed size."
beta: "Beta"
enableAutoSensitive: "Automatic NSFW-Marking"
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."
enableAutoSensitive: "Mark as sensitive automatically"
activeEmailValidationDescription: "Enables stricter validation of email addresses,
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."
@ -1249,19 +1245,6 @@ _emojiModPerm:
add: "Add"
mod: "Add and Edit"
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:
used: "This email address is already being used"
format: "The format of this email address is invalid"
@ -1354,8 +1337,8 @@ _aboutFirefish:
to help support its operation costs."
donateHost: "Donate to {host}"
_nsfw:
respect: "Hide NSFW media"
ignore: "Don't hide NSFW media"
respect: "Hide sensitive media"
ignore: "Don't hide sensitive media"
force: "Hide all media"
writingMode: "Writing mode"
_writingMode:

View file

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

View file

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

View file

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

View file

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

View file

@ -48,6 +48,20 @@ export interface Acct {
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 {
id: string
createdAt: DateTimeWithTimeZone
@ -106,21 +120,28 @@ export type AntennaSrc = 'all'|
export interface ApAccept {
id: string
type: ApObject
type: Activity
actor: string
object: ApFollow
}
export interface ApAdd {
type: Activity
actor: string
target: string
object: string
}
export interface ApEmoji {
id: string
type: ApObject
type: Activity
name: string
updated: string
icon: Icon
}
export interface ApFlag {
type: ApObject
type: Activity
actor: string
content: string
object: string
@ -128,33 +149,32 @@ export interface ApFlag {
export interface ApFollow {
id: string
type: ApObject
type: Activity
actor: string
object: string
}
export interface ApHashtag {
id: string
type: ApObject
type: Activity
name: string
}
export interface ApLike {
id: string
type: Activity
actor: string
object: string
content: string
tag?: Array<ApEmoji>
}
export interface ApMention {
type: ApObject
type: Activity
href: string
name: string
}
export type ApObject = 'Accept'|
'Emoji'|
'Flag'|
'Follow'|
'Hashtag'|
'Mention'|
'Image'|
'Read'|
'Tombstone';
export interface App {
id: string
createdAt: DateTimeWithTimeZone
@ -167,14 +187,28 @@ export interface App {
}
export interface ApRead {
type: ApObject
type: Activity
actor: 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 {
id: string
type: ApObject
type: Activity
}
export interface AttestationChallenge {
@ -578,7 +612,7 @@ export interface Hashtag {
}
export interface Icon {
type: ApObject
type: Activity
mediaType: 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 renderAdd(userId: string, noteId: string): ApAdd
export declare function renderEmoji(emoji: Emoji): ApEmoji
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 renderLike(reaction: Model): Promise<ApLike>
export declare function renderMention(user: UserLike): ApMention
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 interface RenoteMuting {

View file

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

View file

@ -1,23 +1,22 @@
use super::*;
use crate::{config::CONFIG, misc::user};
use uuid::Uuid;
use crate::misc::user;
#[macros::export(object)]
pub struct ApAccept {
pub id: String,
pub r#type: ApObject,
pub r#type: Activity,
pub actor: String,
pub object: follow::ApFollow,
}
impl ActivityPubObject for ApAccept {}
impl ApObject for 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 {
Self {
id: format!("{}/{}", CONFIG.url, Uuid::new_v4()),
r#type: ApObject::Accept,
id: random_local_uri(),
r#type: Activity::Accept,
actor: user::local_uri(user_id),
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)]
pub struct ApEmoji {
pub id: String,
pub r#type: ApObject,
pub r#type: Activity,
pub name: String,
pub updated: String,
pub icon: Icon,
@ -13,26 +13,25 @@ pub struct ApEmoji {
#[macros::export(object)]
pub struct Icon {
pub r#type: ApObject,
pub r#type: Activity,
pub media_type: String,
pub url: String,
}
impl ActivityPubObject for ApEmoji {}
impl ApObject for ApEmoji {}
impl ApEmoji {
#[allow(dead_code)] // TODO: remove this line
fn new(emoji: emoji::Model) -> Self {
pub fn new(emoji: emoji::Model) -> Self {
Self {
id: misc::emoji::local_uri(&emoji.name),
r#type: ApObject::Emoji,
r#type: Activity::Emoji,
name: format!(":{}:", emoji.name),
updated: emoji
.updated_at
.unwrap_or_else(|| Utc::now().into())
.to_rfc3339(),
icon: Icon {
r#type: ApObject::Image,
r#type: Activity::Image,
media_type: emoji.r#type.unwrap_or_else(|| "image/png".to_owned()),
url: emoji.public_url,
},

View file

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

View file

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

View file

@ -4,18 +4,18 @@ use crate::config::CONFIG;
#[macros::export(object)]
pub struct ApHashtag {
pub id: String,
pub r#type: ApObject,
pub r#type: Activity,
pub name: String,
}
impl ActivityPubObject for ApHashtag {}
impl ApObject for 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 {
Self {
id: format!("{}/tags/{}", CONFIG.url, urlencoding::encode(tag_name)),
r#type: ApObject::Hashtag,
r#type: Activity::Hashtag,
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)]
pub struct ApMention {
pub r#type: ApObject,
pub r#type: Activity,
pub href: String,
pub name: String,
}
impl ActivityPubObject for ApMention {}
impl ApObject for 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> {
Ok(Self {
r#type: ApObject::Mention,
r#type: Activity::Mention,
href: match user::is_local!(user) {
true => user::local_uri(user.id),
false => user.uri.ok_or(MissingRemoteUserUri)?,

View file

@ -1,29 +1,44 @@
pub mod accept;
pub mod add;
pub mod emoji;
pub mod flag;
pub mod follow;
pub mod hashtag;
pub mod like;
pub mod mention;
pub mod read;
pub mod reject;
pub mod remove;
pub mod tombstone;
pub trait ActivityPubObject {}
pub trait ApObject {}
#[macros::export(string_enum)]
pub enum ApObject {
pub enum Activity {
Accept,
Add,
Emoji,
Flag,
Follow,
Hashtag,
Like,
Mention,
Image,
Read,
Reject,
Remove,
Tombstone,
}
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)]
pub struct UserLike {
pub id: String,

View file

@ -3,18 +3,18 @@ use crate::misc::user;
#[macros::export(object)]
pub struct ApRead {
pub r#type: ApObject,
pub r#type: Activity,
pub actor: String,
pub object: String,
}
impl ActivityPubObject for ApRead {}
impl ApObject for 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 {
Self {
r#type: ApObject::Read,
r#type: Activity::Read,
actor: user::local_uri(user_id),
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)]
pub struct ApTombstone {
pub id: String,
pub r#type: ApObject,
pub r#type: Activity,
}
impl ActivityPubObject for ApTombstone {}
impl ApObject for 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 {
Self {
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")]
InstanceActorNotFound,
#[error(transparent)]
#[doc = "database error"]
#[doc = "Database error"]
Db(#[from] DbErr),
}

View file

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

View file

@ -14,7 +14,7 @@ pub enum Error {
HttpClient(#[from] http_client::Error),
#[error("HTTP request failed")]
Http(#[from] isahc::Error),
#[doc = "bad HTTP status"]
#[doc = "Bad HTTP status"]
#[error("bad HTTP status ({0})")]
BadStatus(String),
#[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::errors]
pub enum Error {
#[doc = "database error"]
#[doc = "Database error"]
#[error(transparent)]
Db(#[from] DbErr),
#[error("failed to serialize nodeinfo into JSON")]

View file

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

View file

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

View file

@ -2,17 +2,17 @@
use crate::{cache, database::db_conn, model::entity::user};
use chrono::Duration;
use sea_orm::{DbErr, EntityTrait, QuerySelect, SelectColumns};
use sea_orm::{DbErr, EntityTrait, QuerySelect};
#[macros::errors]
pub enum Error {
#[doc = "database error"]
#[doc = "Database error"]
#[error(transparent)]
Db(#[from] DbErr),
#[doc = "cache error"]
#[doc = "Cache error"]
#[error(transparent)]
Cache(#[from] cache::redis::Error),
#[doc = "user not found"]
#[doc = "User not found"]
#[error("user {0} not found")]
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)
.select_only()
.select_column(user::Column::ReadCatLanguage)
.column(user::Column::ReadCatLanguage)
.into_tuple::<bool>()
.one(db_conn().await?)
.await?

View file

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

View file

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

View file

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

View file

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

View file

@ -17,13 +17,13 @@
"format": "pnpm biome format --write src"
},
"dependencies": {
"@bull-board/api": "5.21.3",
"@bull-board/koa": "5.21.3",
"@bull-board/ui": "5.21.3",
"@discordapp/twemoji": "15.0.3",
"@bull-board/api": "5.21.4",
"@bull-board/koa": "5.21.4",
"@bull-board/ui": "5.21.4",
"@discordapp/twemoji": "15.1.0",
"@koa/cors": "5.0.0",
"@koa/multer": "3.0.2",
"@koa/router": "12.0.1",
"@koa/router": "13.0.0",
"@ladjs/koa-views": "9.0.0",
"@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.19.0",
@ -33,8 +33,8 @@
"archiver": "7.0.1",
"async-lock": "1.4.1",
"async-mutex": "0.5.0",
"aws-sdk": "2.1671.0",
"axios": "1.7.3",
"aws-sdk": "2.1677.0",
"axios": "1.7.4",
"backend-rs": "workspace:*",
"blurhash": "2.0.5",
"bull": "4.16.0",
@ -49,7 +49,7 @@
"deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1",
"feed": "4.2.2",
"file-type": "19.4.0",
"file-type": "19.4.1",
"firefish-js": "workspace:*",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.0",
@ -97,7 +97,7 @@
"rss-parser": "3.13.0",
"sanitize-html": "2.13.0",
"semver": "7.6.3",
"sharp": "0.33.4",
"sharp": "0.33.5",
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "1.0.0",
@ -133,7 +133,7 @@
"@types/koa__cors": "5.0.0",
"@types/koa__multer": "2.0.7",
"@types/koa__router": "12.0.4",
"@types/node": "20.14.14",
"@types/node": "20.15.0",
"@types/node-fetch": "2.6.11",
"@types/nodemailer": "6.4.15",
"@types/oauth": "0.9.5",
@ -163,7 +163,7 @@
"ts-node": "10.9.2",
"tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0",
"type-fest": "4.24.0",
"type-fest": "4.25.0",
"typescript": "5.5.4",
"webpack": "5.93.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);
return await create(
actor,
note,
activity._misskey_reaction || activity.content || activity.name,
)
return await create(actor, note, activity.content || activity.name)
.catch((e) => {
if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") {
return "skip: already reacted";

View file

@ -550,7 +550,6 @@ export const WellKnownContext = {
// Misskey
misskey: "https://misskey-hub.net/ns#",
_misskey_talk: "misskey:_misskey_talk",
_misskey_reaction: "misskey:_misskey_reaction",
_misskey_votes: "misskey:_misskey_votes",
_misskey_summary: "misskey:_misskey_summary",
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,
isSelfHost,
renderFollow,
renderLike,
} from "backend-rs";
import { apGet } from "./request.js";
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
@ -20,7 +21,6 @@ import {
} from "@/models/index.js";
import { parseUri } from "./db-resolver.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 renderQuestion from "@/remote/activitypub/renderer/question.js";
import renderCreate from "@/remote/activitypub/renderer/create.js";
@ -181,7 +181,7 @@ export default class Resolver {
}
case "likes": {
const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id });
return renderActivity(renderLike(reaction, { uri: null }));
return renderActivity(await renderLike(reaction));
}
case "follows": {
// if rest is a <followee id>

View file

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

View file

@ -14,6 +14,7 @@ import {
isSelfHost,
renderEmoji,
renderFollow,
renderLike,
} from "backend-rs";
import {
Notes,
@ -23,7 +24,6 @@ import {
FollowRequests,
} from "@/models/index.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 {
checkFetch,
@ -466,7 +466,7 @@ router.get("/likes/:like", async (ctx) => {
return;
}
ctx.body = renderActivity(await renderLike(reaction, note));
ctx.body = renderActivity(await renderLike(reaction));
const instanceMeta = await fetchMeta();
if (instanceMeta.secureMode || instanceMeta.privateMode) {
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 { renderBlock } from "@/remote/activitypub/renderer/block.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 { User } from "@/models/entities/user.js";
import {
@ -20,6 +19,7 @@ import {
publishToUserStream,
UserEvent,
renderFollow,
renderReject,
} from "backend-rs";
import { getActiveWebhooks } from "@/misc/webhook-cache.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)) {
const content = renderActivity(
renderReject(
followee.id,
renderFollow(follower, followee, request.requestId!),
followee,
),
);
deliver(followee.id, content, follower.inbox);

View file

@ -1,5 +1,4 @@
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderReject from "@/remote/activitypub/renderer/reject.js";
import { deliver } from "@/queue/index.js";
import createFollowRequest from "./requests/create.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
@ -23,6 +22,7 @@ import {
UserEvent,
renderAccept,
renderFollow,
renderReject,
} from "backend-rs";
import { createNotification } from "@/services/create-notification.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) {
// リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。
const content = renderActivity(
renderReject(renderFollow(follower, followee, requestId), followee),
renderReject(followee.id, renderFollow(follower, followee, requestId)),
);
deliver(followee.id, content, follower.inbox);
return;

View file

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

View file

@ -1,5 +1,4 @@
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderReject from "@/remote/activitypub/renderer/reject.js";
import { deliver, webhookDeliver } from "@/queue/index.js";
import {
Event,
@ -7,6 +6,7 @@ import {
publishToUserStream,
UserEvent,
renderFollow,
renderReject,
} from "backend-rs";
import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import { Users, FollowRequests, Followings } from "@/models/index.js";
@ -19,6 +19,7 @@ type Local =
id: ILocalUser["id"];
host: ILocalUser["host"];
uri: ILocalUser["uri"];
username: ILocalUser["username"];
};
type Remote =
| IRemoteUser
@ -26,6 +27,7 @@ type Remote =
id: IRemoteUser["id"];
host: IRemoteUser["host"];
uri: IRemoteUser["uri"];
username: IRemoteUser["username"];
inbox: IRemoteUser["inbox"];
};
type Both = Local | Remote;
@ -109,8 +111,8 @@ async function deliverReject(followee: Local, follower: Remote) {
const content = renderActivity(
renderReject(
followee.id,
renderFollow(follower, followee, request?.requestId || undefined),
followee,
),
);
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 { IdentifiableError } from "@/misc/identifiable-error.js";
import type { User } from "@/models/entities/user.js";
import type { Note } from "@/models/entities/note.js";
import { Notes, UserNotePinings, Users } from "@/models/index.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 { deliverToRelays } from "@/services/relay.js";
@ -115,12 +112,8 @@ export async function deliverPinnedChange(
if (!Users.isLocalUser(user)) return;
const target = `${config.url}/users/${user.id}/collections/featured`;
const item = `${config.url}/notes/${noteId}`;
const content = renderActivity(
isAddition
? renderAdd(user, target, item)
: renderRemove(user, target, item),
isAddition ? renderAdd(user.id, noteId) : renderRemove(user.id, noteId),
);
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 { renderActivity } from "@/remote/activitypub/renderer/index.js";
import type { User, IRemoteUser } from "@/models/entities/user.js";
@ -17,6 +16,7 @@ import {
genIdAt,
NoteEvent,
publishToNoteStream,
renderLike,
toDbReaction,
} from "backend-rs";
import { createNotification } from "@/services/create-notification.js";
@ -152,7 +152,7 @@ export default async (
!note.localOnly &&
note.visibility !== "hidden"
) {
const content = renderActivity(await renderLike(record, note));
const content = renderActivity(await renderLike(record));
const dm = new DeliverManager(user, content);
if (note.userHost != null) {
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 { renderActivity } from "@/remote/activitypub/renderer/index.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 { Note } from "@/models/entities/note.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 (
user: { id: User["id"]; host: User["host"] },
@ -55,7 +59,7 @@ export default async (
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity(
renderUndo(await renderLike(reaction, note), user.id),
renderUndo(await renderLike(reaction), user.id),
);
const dm = new DeliverManager(user, content);
if (note.userHost != null) {

View file

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

View file

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

View file

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

View file

@ -239,7 +239,8 @@ function showMenu(ev) {
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;
}
}
@ -270,7 +271,8 @@ function showMenu(ev) {
inset-inline-start: 42px;
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;
}
}
@ -285,7 +287,8 @@ function showMenu(ev) {
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;
}
}
@ -301,7 +304,8 @@ function showMenu(ev) {
border-radius: var(--radius);
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;
}
@ -374,7 +378,8 @@ function showMenu(ev) {
padding-block: 8px;
padding-inline: 0;
@media (max-inline-size: 900px) {
// TODO: use logical property (max-inline-size doesn't work)
@media (max-width: 900px) {
display: none;
}
}

View file

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

View file

@ -21,7 +21,7 @@
},
"devDependencies": {
"@types/jest": "29.5.12",
"@types/node": "20.14.14",
"@types/node": "20.15.0",
"jest": "29.7.0",
"jest-fetch-mock": "3.0.3",
"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)]
pub mod error;
pub mod napi;
mod util;

View file

@ -84,11 +84,9 @@ define_wrapper_proc_macro_attributes! {
/// 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
///
/// See [`macros_impl::error::error_variants`] for more details.
///
/// # Example
///
/// ```
/// ```ignore
/// # use std::io;
/// #[macros::errors]
/// pub enum Error {
@ -97,13 +95,13 @@ define_wrapper_proc_macro_attributes! {
/// #[error("failed to read the config file")]
/// ReadConfigFile(#[from] io::Error),
/// #[error("invalid file content ({0})")]
/// #[doc = "invalid file content"]
/// #[doc = "Invalid file content"]
/// InvalidContent(String),
/// }
/// ```
errors(attr, item) {
errors(_attr, item) {
#[derive(::thiserror::Error, ::std::fmt::Debug)]
#[macros::error_variants(#attr, #item)]
#[error_doc::error_doc]
#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/).
/// See [macros_impl::napi::napi] for details.
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": {
"firefish-js": "workspace:*",
"idb-keyval": "6.2.1",
"vite": "5.4.0",
"vite": "5.4.1",
"vite-plugin-compression": "0.5.1"
}
}

File diff suppressed because it is too large Load diff