Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f14dbc215
|
||
|
|
5fb5f087d6
|
||
|
|
6ff0e3653d
|
||
|
|
4ddc26c6ca
|
||
|
|
63c4c5f2e8 |
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.4.2
|
||||||
|
- Fix URL fragment detected as hashtag
|
||||||
|
|
||||||
|
## v0.4.1
|
||||||
|
- "en" translation fixes
|
||||||
|
- add `messages.json`
|
||||||
|
- Config files are now parsed as JSON5 = comments are allowed
|
||||||
|
|
||||||
|
## v0.4.0
|
||||||
|
- Add a translation system using the `locales` folder
|
||||||
|
- Add more trace logging
|
||||||
|
|
||||||
## v0.3.0
|
## v0.3.0
|
||||||
- Changed config/storage format to directory-based, removed shared config mutex
|
- Changed config/storage format to directory-based, removed shared config mutex
|
||||||
- Made more options configurable (timeouts, catch-up limits, etc)
|
- Made more options configurable (timeouts, catch-up limits, etc)
|
||||||
|
|||||||
Generated
+68
-1
@@ -328,13 +328,14 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fedigroups"
|
name = "fedigroups"
|
||||||
version = "0.4.0"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"elefren",
|
"elefren",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures 0.3.16",
|
"futures 0.3.16",
|
||||||
|
"json5",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -764,6 +765,17 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json5"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel32-sys"
|
name = "kernel32-sys"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -829,6 +841,12 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maplit"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matches"
|
name = "matches"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
@@ -1105,6 +1123,49 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_derive"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_generator"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_meta",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_meta"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||||
|
dependencies = [
|
||||||
|
"maplit",
|
||||||
|
"pest",
|
||||||
|
"sha-1 0.8.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.7.24"
|
version = "0.7.24"
|
||||||
@@ -2110,6 +2171,12 @@ version = "1.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fedigroups"
|
name = "fedigroups"
|
||||||
version = "0.4.0"
|
version = "0.4.2"
|
||||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
@@ -26,6 +26,7 @@ futures = "0.3"
|
|||||||
voca_rs = "1.13.0"
|
voca_rs = "1.13.0"
|
||||||
regex = "1.5.4"
|
regex = "1.5.4"
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
|
json5 = "0.4.1"
|
||||||
|
|
||||||
native-tls = "0.2.8"
|
native-tls = "0.2.8"
|
||||||
websocket = "0.26.2"
|
websocket = "0.26.2"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ In case you need to re-authenticate an existing group, do the same but use `-A`
|
|||||||
|
|
||||||
### Editing config
|
### Editing config
|
||||||
|
|
||||||
**JSON does not support comments! Remove comments before using examples copied from this guide!**
|
**Staring v0.4.1, the config files support comments!**
|
||||||
|
|
||||||
A typical setup could look like this:
|
A typical setup could look like this:
|
||||||
|
|
||||||
@@ -55,43 +55,75 @@ A typical setup could look like this:
|
|||||||
│ │ ├── control.json
|
│ │ ├── control.json
|
||||||
│ │ └── state.json
|
│ │ └── state.json
|
||||||
│ └── chatterbox@botsin.space
|
│ └── chatterbox@botsin.space
|
||||||
│ ├── config.json
|
│ ├── config.json ... fixed config edited manually
|
||||||
│ ├── control.json
|
│ ├── messages.json ... custom locale overrides (optional)
|
||||||
│ └── state.json
|
│ ├── control.json ... mutable config updated by the group service
|
||||||
|
│ └── state.json ... group state data
|
||||||
|
├── locales
|
||||||
|
│ ├── ... custom locale files, same format like en.json
|
||||||
|
│ └── ru.json
|
||||||
└── groups.json
|
└── groups.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Locales
|
||||||
|
|
||||||
|
English locale ("en") is bundled in the binary. Additional locales can be placed in the `locales` folder.
|
||||||
|
If an entry is missing, the English version will be used.
|
||||||
|
|
||||||
|
The locale file looks like this (excerpt):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group_announcement": "**📢Group announcement**\n{message}",
|
||||||
|
"ping_response": "pong, this is fedigroups service v{version}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- All messages can use markdown formatting.
|
||||||
|
- Words in curly braces (`{}`) are substitution tokens. These must be preserved in all translations.
|
||||||
|
- Pay attention to line endings and blank lines (`\n`). Some messages from the locale file are combined to form the
|
||||||
|
final post, leaving out newlines can result in a mangled output.
|
||||||
|
|
||||||
|
The locale to use is chosen in each group's `config.json`, "en" by default (if not specified).
|
||||||
|
|
||||||
|
Group-specific overrides are also possible: create a file `messages.json` in the group folder
|
||||||
|
and define messages you wish to change, e.g. the greeting or announcement templates.
|
||||||
|
|
||||||
#### Common config
|
#### Common config
|
||||||
|
|
||||||
There is one shared config file: `groups.json`
|
There is one shared config file: `groups.json`
|
||||||
|
|
||||||
- If the file does not exist, default settings are used. This is usually good enough.
|
- If the file does not exist, default settings are used. This is usually sufficient.
|
||||||
- This file applies to all groups
|
- This file applies to all groups and serves as the default config.
|
||||||
- Prior to 0.3, the groups were also configured here.
|
|
||||||
- Running 0.3+ with the old file will produce an error, you need to update the config before continuing - move groups to subfolders
|
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
|
// name of the directory with groups
|
||||||
|
"groups_dir": "groups",
|
||||||
|
// name of the directory with locales
|
||||||
|
"locales_dir": "locales",
|
||||||
|
// Show warning if locales are missing keys
|
||||||
|
"validate_locales": true,
|
||||||
// Max number of missed notifs to process after connect
|
// Max number of missed notifs to process after connect
|
||||||
max_catchup_notifs: 30,
|
"max_catchup_notifs": 30,
|
||||||
// Max number of missed statuses to process after connect
|
// Max number of missed statuses to process after connect
|
||||||
max_catchup_statuses: 50,
|
"max_catchup_statuses": 50,
|
||||||
// Delay between fetched pages when catching up
|
// Delay between fetched pages when catching up
|
||||||
delay_fetch_page_s: 0.25,
|
"delay_fetch_page_s": 0.25,
|
||||||
// Delay after sending a status, making a follow or some other action.
|
// Delay after sending a status, making a follow or some other action.
|
||||||
// Set if there are Throttled errors and you need to slow the service down.
|
// Set if there are Throttled errors and you need to slow the service down.
|
||||||
delay_after_post_s: 0.0,
|
"delay_after_post_s": 0.0,
|
||||||
// Delay before trying to re-connect after the server closed the socket
|
// Delay before trying to re-connect after the server closed the socket
|
||||||
delay_reopen_closed_s: 0.5,
|
"delay_reopen_closed_s": 0.5,
|
||||||
// Delay before trying to re-connect after an error
|
// Delay before trying to re-connect after an error
|
||||||
delay_reopen_error_s: 5.0,
|
"delay_reopen_error_s": 5.0,
|
||||||
// Timeout for a notification/timeline socket to be considered alive.
|
// Timeout for a notification/timeline socket to be considered alive.
|
||||||
// If nothing arrives in this interval, reopen it. Some servers have a buggy socket
|
// If nothing arrives in this interval, reopen it. Some servers have a buggy socket
|
||||||
// implementation where it stays open but no longer works.
|
// implementation where it stays open but no longer works.
|
||||||
socket_alive_timeout_s: 30.0,
|
"socket_alive_timeout_s": 30.0,
|
||||||
// Time after which a socket is always closed, even if seemingly alive.
|
// Time after which a socket is always closed, even if seemingly alive.
|
||||||
// This is a work-around for servers that stop sending notifs after a while.
|
// This is a work-around for servers that stop sending notifs after a while.
|
||||||
socket_retire_time_s: 120.0,
|
"socket_retire_time_s": 120.0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -111,6 +143,7 @@ Only the `config.json` file with credentials is required; the others will be cre
|
|||||||
- `state.json` - frequently changing state data. The last-seen status/notification timestamps are kept here.
|
- `state.json` - frequently changing state data. The last-seen status/notification timestamps are kept here.
|
||||||
State is split from Control to limit the write frequency of the control file. Timestamps can be updated multiple times
|
State is split from Control to limit the write frequency of the control file. Timestamps can be updated multiple times
|
||||||
per minute.
|
per minute.
|
||||||
|
- `messages.json` - optional per-group locale overrides
|
||||||
|
|
||||||
**Do not edit the control and state files while the group service is running, it may overwrite your changes!**
|
**Do not edit the control and state files while the group service is running, it may overwrite your changes!**
|
||||||
|
|
||||||
@@ -119,7 +152,7 @@ Note that changing config externally requires a restart. It's better to use slas
|
|||||||
|
|
||||||
When adding hashtags, *they must be entered as lowercase* and without the `#` symbol!
|
When adding hashtags, *they must be entered as lowercase* and without the `#` symbol!
|
||||||
|
|
||||||
The file formats are quite self-explanatory (again, remove comments before copying, JSON does not support comments!)
|
The file formats are quite self-explanatory:
|
||||||
|
|
||||||
**config.json**
|
**config.json**
|
||||||
|
|
||||||
@@ -127,9 +160,11 @@ The file formats are quite self-explanatory (again, remove comments before copyi
|
|||||||
{
|
{
|
||||||
// Enable or disable the group service
|
// Enable or disable the group service
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
// Group locale (optional, defaults to "en")
|
||||||
|
"locale": "en",
|
||||||
// Group account name
|
// Group account name
|
||||||
"acct": "group@myserver.xyz",
|
"acct": "group@myserver.xyz",
|
||||||
// Saved mastodon API credentials
|
// Saved mastodon API credentials, this is created when authenticating the group.
|
||||||
"appdata": {
|
"appdata": {
|
||||||
"base": "https://myserver.xyz",
|
"base": "https://myserver.xyz",
|
||||||
"client_id": "...",
|
"client_id": "...",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{
|
{
|
||||||
"welcome_public": "Ahoj",
|
|
||||||
"ping_response": "pong, toto je fedigroups verze {version}"
|
"ping_response": "pong, toto je fedigroups verze {version}"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"help_membership_member": "*You are a member.*",
|
"help_membership_member": "*You are a member.*",
|
||||||
"help_membership_guest_closed": "*You are not a member, ask one of the admins to add you.*",
|
"help_membership_guest_closed": "*You are not a member, ask one of the admins to add you.*",
|
||||||
"help_membership_guest_open": "*You are not a member, follow or use /join to join the group.*",
|
"help_membership_guest_open": "*You are not a member, follow or use /join to join the group.*",
|
||||||
"help_admin_commands": "\n\n**Admin commands:**\n`/ping` - check the group works\n`/members - show group members / admins\n`/add user` - add a member (user@domain)\n`/remove user` - remove a member\n`/add #hashtag` - add a group hashtag\n`/remove #hashtag` - remove a group hashtag\n`/undo` - un-boost a replied-to post, delete an announcement\n`/ban x` - ban a user or server\n`/unban x` - lift a ban\n`/admin user` - grant admin rights\n`/deadmin user` - revoke admin rights\n`/closegroup` - make member-only\n`/opengroup` - make public-access\n`/announce x` - make a public announcement",
|
"help_admin_commands": "\n\n**Admin commands:**\n`/ping` - check the group works\n`/members` - show group members / admins\n`/add user` - add a member (user@domain)\n`/remove user` - remove a member\n`/add #hashtag` - add a group hashtag\n`/remove #hashtag` - remove a group hashtag\n`/undo` - un-boost a replied-to post, delete an announcement\n`/ban x` - ban a user or server\n`/unban x` - lift a ban\n`/admin user` - grant admin rights\n`/deadmin user` - revoke admin rights\n`/closegroup` - make member-only\n`/opengroup` - make public-access\n`/announce x` - make a public announcement",
|
||||||
"help_basic_commands": "To share a post, @ the group user or use a group hashtag.\n\n**Supported commands:**\n`/boost`, `/b` - boost the replied-to post into the group\n`/ignore`, `/i` - make the group ignore the post\n`/tags` - show group hashtags\n`/join` - (re-)join the group\n`/leave` - leave the group\n`/optout` - forbid sharing of your posts",
|
"help_basic_commands": "To share a post, @ the group user or use a group hashtag.\n\n**Supported commands:**\n`/boost`, `/b` - boost the replied-to post into the group\n`/ignore`, `/i` - make the group ignore the post\n`/tags` - show group hashtags\n`/join` - (re-)join the group\n`/leave` - leave the group\n`/optout` - forbid sharing of your posts",
|
||||||
"help_member_commands": "\n`/admins` - show group admins\n`/undo` - un-boost your post (use in a reply)",
|
"help_member_commands": "\n`/admins` - show group admins\n`/undo` - un-boost your post (use in a reply)",
|
||||||
"cmd_leave_ok": "You're no longer a group member. Unfollow the group user to stop receiving group messages.",
|
"cmd_leave_ok": "You're no longer a group member. Unfollow the group user to stop receiving group messages.",
|
||||||
|
|||||||
+6
-4
@@ -135,13 +135,13 @@ static RE_PING: once_cell::sync::Lazy<Regex> = Lazy::new(|| command!(r"ping"));
|
|||||||
static RE_ANNOUNCE: once_cell::sync::Lazy<Regex> =
|
static RE_ANNOUNCE: once_cell::sync::Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"(?:^|\s|>|\n)[\\/]announce\s+(.*)$").unwrap());
|
Lazy::new(|| Regex::new(r"(?:^|\s|>|\n)[\\/]announce\s+(.*)$").unwrap());
|
||||||
|
|
||||||
static RE_A_HASHTAG: once_cell::sync::Lazy<Regex> = Lazy::new(|| Regex::new(r"(?:^|\b|\s|>|\n)#(\w+)").unwrap());
|
static RE_A_HASHTAG: once_cell::sync::Lazy<Regex> = Lazy::new(|| Regex::new(r"(?:^|\s|>|\n)#(\w+)").unwrap());
|
||||||
|
|
||||||
pub static RE_NOBOT_TAG: once_cell::sync::Lazy<Regex> =
|
pub static RE_NOBOT_TAG: once_cell::sync::Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"(?:^|\b|\s|>|\n)#nobot(?:\b|$)").unwrap());
|
Lazy::new(|| Regex::new(r"(?:^|\s|>|\n)#nobot(?:\b|$)").unwrap());
|
||||||
|
|
||||||
pub static RE_HASHTAG_TRIGGERING_PLEROMA_BUG: once_cell::sync::Lazy<Regex> =
|
pub static RE_HASHTAG_TRIGGERING_PLEROMA_BUG: once_cell::sync::Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"(?:^|\b|\s|>|\n)#\w+[^\s]*$").unwrap());
|
Lazy::new(|| Regex::new(r"(?:^|\s|>|\n)#\w+[^\s]*$").unwrap());
|
||||||
|
|
||||||
pub fn parse_status_tags(content: &str) -> Vec<String> {
|
pub fn parse_status_tags(content: &str) -> Vec<String> {
|
||||||
debug!("Raw content: {}", content);
|
debug!("Raw content: {}", content);
|
||||||
@@ -563,6 +563,8 @@ mod test {
|
|||||||
assert!(RE_A_HASHTAG.is_match("#городДляЛюдей"));
|
assert!(RE_A_HASHTAG.is_match("#городДляЛюдей"));
|
||||||
assert!(RE_A_HASHTAG.is_match("foo #banana gfdfgd"));
|
assert!(RE_A_HASHTAG.is_match("foo #banana gfdfgd"));
|
||||||
assert!(RE_A_HASHTAG.is_match("foo #городДляЛюдей aaa"));
|
assert!(RE_A_HASHTAG.is_match("foo #городДляЛюдей aaa"));
|
||||||
|
assert!(!RE_A_HASHTAG.is_match("foo https://google.com/#banana gfdfgd"));
|
||||||
|
assert!(!RE_A_HASHTAG.is_match("foo https://google.com/?foo#banana gfdfgd"));
|
||||||
|
|
||||||
for (i, c) in RE_A_HASHTAG.captures_iter("foo #banana #χαλβάς #ласточка").enumerate() {
|
for (i, c) in RE_A_HASHTAG.captures_iter("foo #banana #χαλβάς #ласточка").enumerate() {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@@ -590,7 +592,7 @@ mod test {
|
|||||||
assert!(!RE_NOBOT_TAG.is_match("banana #tag sdfsd"));
|
assert!(!RE_NOBOT_TAG.is_match("banana #tag sdfsd"));
|
||||||
assert!(!RE_NOBOT_TAG.is_match("banana #nobotanicals sdfsd"));
|
assert!(!RE_NOBOT_TAG.is_match("banana #nobotanicals sdfsd"));
|
||||||
assert!(RE_NOBOT_TAG.is_match("#nobot"));
|
assert!(RE_NOBOT_TAG.is_match("#nobot"));
|
||||||
assert!(RE_NOBOT_TAG.is_match("aaa#nobot"));
|
assert!(RE_NOBOT_TAG.is_match("aaa\n#nobot"));
|
||||||
assert!(RE_NOBOT_TAG.is_match("aaa #nobot"));
|
assert!(RE_NOBOT_TAG.is_match("aaa #nobot"));
|
||||||
assert!(RE_NOBOT_TAG.is_match("#nobot xxx"));
|
assert!(RE_NOBOT_TAG.is_match("#nobot xxx"));
|
||||||
assert!(RE_NOBOT_TAG.is_match("#nobot\nxxx"));
|
assert!(RE_NOBOT_TAG.is_match("#nobot\nxxx"));
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ pub enum GroupError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Serializer(#[from] serde_json::Error),
|
Serializer(#[from] serde_json::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
Serializer5(#[from] json5::Error),
|
||||||
|
#[error(transparent)]
|
||||||
Elefren(#[from] elefren::Error),
|
Elefren(#[from] elefren::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,12 +30,11 @@ pub struct ProcessMention<'a> {
|
|||||||
replies: String,
|
replies: String,
|
||||||
announcements: String,
|
announcements: String,
|
||||||
do_boost_prev_post: bool,
|
do_boost_prev_post: bool,
|
||||||
want_markdown: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ProcessMention<'a> {
|
impl<'a> ProcessMention<'a> {
|
||||||
fn tr(&self) -> &TranslationTable {
|
fn tr(&self) -> &TranslationTable {
|
||||||
self.cc.tr(self.config.get_locale())
|
self.config.tr()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn lookup_acct_id(&self, acct: &str, followed: bool) -> Result<Option<String>, GroupError> {
|
async fn lookup_acct_id(&self, acct: &str, followed: bool) -> Result<Option<String>, GroupError> {
|
||||||
@@ -134,7 +133,6 @@ impl<'a> ProcessMention<'a> {
|
|||||||
replies: String::new(),
|
replies: String::new(),
|
||||||
announcements: String::new(),
|
announcements: String::new(),
|
||||||
do_boost_prev_post: false,
|
do_boost_prev_post: false,
|
||||||
want_markdown: false,
|
|
||||||
group_acct,
|
group_acct,
|
||||||
status_acct,
|
status_acct,
|
||||||
status,
|
status,
|
||||||
@@ -271,9 +269,7 @@ impl<'a> ProcessMention<'a> {
|
|||||||
let mut msg = std::mem::take(&mut self.replies);
|
let mut msg = std::mem::take(&mut self.replies);
|
||||||
debug!("r={}", msg);
|
debug!("r={}", msg);
|
||||||
|
|
||||||
if self.want_markdown {
|
apply_trailing_hashtag_pleroma_bug_workaround(&mut msg);
|
||||||
apply_trailing_hashtag_pleroma_bug_workaround(&mut msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mention = crate::tr!(self, "mention_prefix", user = &self.status_acct);
|
let mention = crate::tr!(self, "mention_prefix", user = &self.status_acct);
|
||||||
self.send_reply_multipart(mention, msg).await?;
|
self.send_reply_multipart(mention, msg).await?;
|
||||||
@@ -283,9 +279,7 @@ impl<'a> ProcessMention<'a> {
|
|||||||
let mut msg = std::mem::take(&mut self.announcements);
|
let mut msg = std::mem::take(&mut self.announcements);
|
||||||
debug!("a={}", msg);
|
debug!("a={}", msg);
|
||||||
|
|
||||||
if self.want_markdown {
|
apply_trailing_hashtag_pleroma_bug_workaround(&mut msg);
|
||||||
apply_trailing_hashtag_pleroma_bug_workaround(&mut msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = crate::tr!(self, "group_announcement", message = &msg);
|
let msg = crate::tr!(self, "group_announcement", message = &msg);
|
||||||
self.send_announcement_multipart(&msg).await?;
|
self.send_announcement_multipart(&msg).await?;
|
||||||
@@ -301,11 +295,7 @@ impl<'a> ProcessMention<'a> {
|
|||||||
for p in parts {
|
for p in parts {
|
||||||
if let Ok(post) = StatusBuilder::new()
|
if let Ok(post) = StatusBuilder::new()
|
||||||
.status(p)
|
.status(p)
|
||||||
.content_type(if self.want_markdown {
|
.content_type("text/markdown")
|
||||||
"text/markdown"
|
|
||||||
} else {
|
|
||||||
"text/plain"
|
|
||||||
})
|
|
||||||
.in_reply_to(&parent)
|
.in_reply_to(&parent)
|
||||||
.visibility(Visibility::Direct)
|
.visibility(Visibility::Direct)
|
||||||
.build()
|
.build()
|
||||||
@@ -647,8 +637,6 @@ impl<'a> ProcessMention<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn cmd_help(&mut self) {
|
async fn cmd_help(&mut self) {
|
||||||
self.want_markdown = true;
|
|
||||||
|
|
||||||
let membership_line = if self.is_admin {
|
let membership_line = if self.is_admin {
|
||||||
crate::tr!(self, "help_membership_admin")
|
crate::tr!(self, "help_membership_admin")
|
||||||
} else if self.config.is_member(&self.status_acct) {
|
} else if self.config.is_member(&self.status_acct) {
|
||||||
@@ -677,7 +665,6 @@ impl<'a> ProcessMention<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn cmd_list_members(&mut self) {
|
async fn cmd_list_members(&mut self) {
|
||||||
self.want_markdown = true;
|
|
||||||
if self.is_admin {
|
if self.is_admin {
|
||||||
self.add_reply(crate::tr!(self, "member_list_heading"));
|
self.add_reply(crate::tr!(self, "member_list_heading"));
|
||||||
self.append_member_list_to_reply();
|
self.append_member_list_to_reply();
|
||||||
@@ -689,7 +676,6 @@ impl<'a> ProcessMention<'a> {
|
|||||||
|
|
||||||
async fn cmd_list_tags(&mut self) {
|
async fn cmd_list_tags(&mut self) {
|
||||||
self.add_reply(crate::tr!(self, "tag_list_heading"));
|
self.add_reply(crate::tr!(self, "tag_list_heading"));
|
||||||
self.want_markdown = true;
|
|
||||||
let mut tags = self.config.get_tags().collect::<Vec<_>>();
|
let mut tags = self.config.get_tags().collect::<Vec<_>>();
|
||||||
tags.sort();
|
tags.sort();
|
||||||
|
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ impl GroupHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tr(&self) -> &TranslationTable {
|
fn tr(&self) -> &TranslationTable {
|
||||||
self.cc.tr(self.config.get_locale())
|
self.config.tr()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_new_follow(&mut self, notif_acct: &str, notif_user_id: &str) {
|
async fn handle_new_follow(&mut self, notif_acct: &str, notif_user_id: &str) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::tr::TranslationTable;
|
|||||||
pub struct CommonConfig {
|
pub struct CommonConfig {
|
||||||
pub groups_dir: String,
|
pub groups_dir: String,
|
||||||
pub locales_dir: String,
|
pub locales_dir: String,
|
||||||
|
pub validate_locales: bool,
|
||||||
/// Max number of missed notifs to process after connect
|
/// Max number of missed notifs to process after connect
|
||||||
pub max_catchup_notifs: usize,
|
pub max_catchup_notifs: usize,
|
||||||
/// Max number of missed statuses to process after connect
|
/// Max number of missed statuses to process after connect
|
||||||
@@ -36,6 +37,7 @@ impl Default for CommonConfig {
|
|||||||
Self {
|
Self {
|
||||||
groups_dir: "groups".to_string(),
|
groups_dir: "groups".to_string(),
|
||||||
locales_dir: "locales".to_string(),
|
locales_dir: "locales".to_string(),
|
||||||
|
validate_locales: true,
|
||||||
max_catchup_notifs: 30,
|
max_catchup_notifs: 30,
|
||||||
max_catchup_statuses: 50,
|
max_catchup_statuses: 50,
|
||||||
delay_fetch_page_s: 0.25,
|
delay_fetch_page_s: 0.25,
|
||||||
|
|||||||
+52
-30
@@ -4,7 +4,8 @@ use std::path::{Path, PathBuf};
|
|||||||
use elefren::AppData;
|
use elefren::AppData;
|
||||||
|
|
||||||
use crate::error::GroupError;
|
use crate::error::GroupError;
|
||||||
use crate::store::DEFAULT_LOCALE_NAME;
|
use crate::store::{DEFAULT_LOCALE_NAME, CommonConfig};
|
||||||
|
use crate::tr::TranslationTable;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
@@ -69,6 +70,8 @@ pub struct GroupConfig {
|
|||||||
control: MutableConfig,
|
control: MutableConfig,
|
||||||
/// State config with timestamps and transient data that is changed frequently
|
/// State config with timestamps and transient data that is changed frequently
|
||||||
state: StateConfig,
|
state: StateConfig,
|
||||||
|
/// Group-specific translation table; this is a clone of the global table with group-specific overrides applied.
|
||||||
|
_group_tr: TranslationTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FixedConfig {
|
impl Default for FixedConfig {
|
||||||
@@ -155,22 +158,12 @@ impl_change_tracking!(FixedConfig);
|
|||||||
impl_change_tracking!(MutableConfig);
|
impl_change_tracking!(MutableConfig);
|
||||||
impl_change_tracking!(StateConfig);
|
impl_change_tracking!(StateConfig);
|
||||||
|
|
||||||
impl Default for GroupConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
config: Default::default(),
|
|
||||||
control: Default::default(),
|
|
||||||
state: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn load_or_create_control_file(control_path: impl AsRef<Path>) -> Result<MutableConfig, GroupError> {
|
async fn load_or_create_control_file(control_path: impl AsRef<Path>) -> Result<MutableConfig, GroupError> {
|
||||||
let control_path = control_path.as_ref();
|
let control_path = control_path.as_ref();
|
||||||
let mut dirty = false;
|
let mut dirty = false;
|
||||||
let mut control: MutableConfig = if control_path.is_file() {
|
let mut control: MutableConfig = if control_path.is_file() {
|
||||||
let f = tokio::fs::read(&control_path).await?;
|
let f = tokio::fs::read(&control_path).await?;
|
||||||
let mut control: MutableConfig = serde_json::from_slice(&f)?;
|
let mut control: MutableConfig = json5::from_str(&String::from_utf8_lossy(&f))?;
|
||||||
control._path = control_path.to_owned();
|
control._path = control_path.to_owned();
|
||||||
control
|
control
|
||||||
} else {
|
} else {
|
||||||
@@ -192,7 +185,7 @@ async fn load_or_create_state_file(state_path: impl AsRef<Path>) -> Result<State
|
|||||||
let mut dirty = false;
|
let mut dirty = false;
|
||||||
let mut state: StateConfig = if state_path.is_file() {
|
let mut state: StateConfig = if state_path.is_file() {
|
||||||
let f = tokio::fs::read(&state_path).await?;
|
let f = tokio::fs::read(&state_path).await?;
|
||||||
let mut control: StateConfig = serde_json::from_slice(&f)?;
|
let mut control: StateConfig = json5::from_str(&String::from_utf8_lossy(&f))?;
|
||||||
control._path = state_path.to_owned();
|
control._path = state_path.to_owned();
|
||||||
control
|
control
|
||||||
} else {
|
} else {
|
||||||
@@ -209,7 +202,22 @@ async fn load_or_create_state_file(state_path: impl AsRef<Path>) -> Result<State
|
|||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_locale_override_file(locale_path: impl AsRef<Path>) -> Result<Option<TranslationTable>, GroupError> {
|
||||||
|
let locale_path = locale_path.as_ref();
|
||||||
|
if locale_path.is_file() {
|
||||||
|
let f = tokio::fs::read(&locale_path).await?;
|
||||||
|
let opt : TranslationTable = json5::from_str(&String::from_utf8_lossy(&f))?;
|
||||||
|
Ok(Some(opt))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GroupConfig {
|
impl GroupConfig {
|
||||||
|
pub fn tr(&self) -> &TranslationTable {
|
||||||
|
&self._group_tr
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_dirty(&self) -> bool {
|
pub(crate) fn is_dirty(&self) -> bool {
|
||||||
self.config.is_dirty() || self.control.is_dirty() || self.state.is_dirty()
|
self.config.is_dirty() || self.control.is_dirty() || self.state.is_dirty()
|
||||||
}
|
}
|
||||||
@@ -251,7 +259,7 @@ impl GroupConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// (re)init using new authorization
|
/// (re)init using new authorization
|
||||||
pub(crate) async fn from_appdata(acct: String, appdata: AppData, group_dir: PathBuf) -> Result<Self, GroupError> {
|
pub(crate) async fn initialize_by_appdata(acct: String, appdata: AppData, group_dir: PathBuf) -> Result<(), GroupError> {
|
||||||
if !group_dir.is_dir() {
|
if !group_dir.is_dir() {
|
||||||
debug!("Creating group directory");
|
debug!("Creating group directory");
|
||||||
tokio::fs::create_dir_all(&group_dir).await?;
|
tokio::fs::create_dir_all(&group_dir).await?;
|
||||||
@@ -267,7 +275,7 @@ impl GroupConfig {
|
|||||||
let mut dirty = false;
|
let mut dirty = false;
|
||||||
let mut config: FixedConfig = if config_path.is_file() {
|
let mut config: FixedConfig = if config_path.is_file() {
|
||||||
let f = tokio::fs::read(&config_path).await?;
|
let f = tokio::fs::read(&config_path).await?;
|
||||||
let mut config: FixedConfig = serde_json::from_slice(&f)?;
|
let mut config: FixedConfig = json5::from_str(&String::from_utf8_lossy(&f))?;
|
||||||
config._path = config_path;
|
config._path = config_path;
|
||||||
if config.appdata != appdata {
|
if config.appdata != appdata {
|
||||||
config.appdata = appdata;
|
config.appdata = appdata;
|
||||||
@@ -298,21 +306,22 @@ impl GroupConfig {
|
|||||||
/* state */
|
/* state */
|
||||||
let state = load_or_create_state_file(state_path).await?;
|
let state = load_or_create_state_file(state_path).await?;
|
||||||
|
|
||||||
let g = GroupConfig { config, control, state };
|
let g = GroupConfig { config, control, state, _group_tr: TranslationTable::new() };
|
||||||
g.warn_of_bad_config();
|
g.warn_of_bad_config();
|
||||||
Ok(g)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn from_dir(group_dir: PathBuf) -> Result<Self, GroupError> {
|
pub(crate) async fn from_dir(group_dir: PathBuf, cc : &CommonConfig) -> Result<Self, GroupError> {
|
||||||
let config_path = group_dir.join("config.json");
|
let config_path = group_dir.join("config.json");
|
||||||
let control_path = group_dir.join("control.json");
|
let control_path = group_dir.join("control.json");
|
||||||
let state_path = group_dir.join("state.json");
|
let state_path = group_dir.join("state.json");
|
||||||
|
let locale_path = group_dir.join("messages.json");
|
||||||
|
|
||||||
// try to reuse content of the files, if present
|
// try to reuse content of the files, if present
|
||||||
|
|
||||||
/* config */
|
/* config */
|
||||||
let f = tokio::fs::read(&config_path).await?;
|
let f = tokio::fs::read(&config_path).await?;
|
||||||
let mut config: FixedConfig = serde_json::from_slice(&f)?;
|
let mut config: FixedConfig = json5::from_str(&String::from_utf8_lossy(&f))?;
|
||||||
config._path = config_path;
|
config._path = config_path;
|
||||||
|
|
||||||
/* control */
|
/* control */
|
||||||
@@ -321,7 +330,15 @@ impl GroupConfig {
|
|||||||
/* state */
|
/* state */
|
||||||
let state = load_or_create_state_file(state_path).await?;
|
let state = load_or_create_state_file(state_path).await?;
|
||||||
|
|
||||||
let g = GroupConfig { config, control, state };
|
/* translation table */
|
||||||
|
let mut tr = cc.tr(&config.locale).clone();
|
||||||
|
if let Some(locale_overrides) = load_locale_override_file(locale_path).await? {
|
||||||
|
for (k, v) in locale_overrides.entries() {
|
||||||
|
tr.add_translation(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let g = GroupConfig { config, control, state, _group_tr: tr };
|
||||||
g.warn_of_bad_config();
|
g.warn_of_bad_config();
|
||||||
Ok(g)
|
Ok(g)
|
||||||
}
|
}
|
||||||
@@ -369,10 +386,6 @@ impl GroupConfig {
|
|||||||
&self.config.appdata
|
&self.config.appdata
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_locale(&self) -> &str {
|
|
||||||
&self.config.locale
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_appdata(&mut self, appdata: AppData) {
|
pub(crate) fn set_appdata(&mut self, appdata: AppData) {
|
||||||
if self.config.appdata != appdata {
|
if self.config.appdata != appdata {
|
||||||
self.config.mark_dirty();
|
self.config.mark_dirty();
|
||||||
@@ -570,6 +583,15 @@ mod tests {
|
|||||||
use crate::error::GroupError;
|
use crate::error::GroupError;
|
||||||
use crate::store::group_config::{acct_to_server, GroupConfig};
|
use crate::store::group_config::{acct_to_server, GroupConfig};
|
||||||
|
|
||||||
|
fn empty_group_config() -> GroupConfig {
|
||||||
|
GroupConfig {
|
||||||
|
config: Default::default(),
|
||||||
|
control: Default::default(),
|
||||||
|
state: Default::default(),
|
||||||
|
_group_tr: Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_acct_to_server() {
|
fn test_acct_to_server() {
|
||||||
assert_eq!("pikachu.rocks", acct_to_server("raichu@pikachu.rocks"));
|
assert_eq!("pikachu.rocks", acct_to_server("raichu@pikachu.rocks"));
|
||||||
@@ -579,7 +601,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_rules() {
|
fn test_default_rules() {
|
||||||
let group = GroupConfig::default();
|
let group = empty_group_config();
|
||||||
assert!(!group.is_member_only());
|
assert!(!group.is_member_only());
|
||||||
assert!(!group.is_member("piggo@piggo.space"));
|
assert!(!group.is_member("piggo@piggo.space"));
|
||||||
assert!(!group.is_admin("piggo@piggo.space"));
|
assert!(!group.is_admin("piggo@piggo.space"));
|
||||||
@@ -588,7 +610,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_member_only() {
|
fn test_member_only() {
|
||||||
let mut group = GroupConfig::default();
|
let mut group = empty_group_config();
|
||||||
assert!(group.can_write("piggo@piggo.space"), "rando can write in public group");
|
assert!(group.can_write("piggo@piggo.space"), "rando can write in public group");
|
||||||
|
|
||||||
group.set_member_only(true);
|
group.set_member_only(true);
|
||||||
@@ -625,7 +647,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_banned_users() {
|
fn test_banned_users() {
|
||||||
// Banning single user
|
// Banning single user
|
||||||
let mut group = GroupConfig::default();
|
let mut group = empty_group_config();
|
||||||
group.ban_user("piggo@piggo.space", true).unwrap();
|
group.ban_user("piggo@piggo.space", true).unwrap();
|
||||||
assert!(!group.can_write("piggo@piggo.space"), "banned user can't post");
|
assert!(!group.can_write("piggo@piggo.space"), "banned user can't post");
|
||||||
group.ban_user("piggo@piggo.space", false).unwrap();
|
group.ban_user("piggo@piggo.space", false).unwrap();
|
||||||
@@ -635,7 +657,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_banned_members() {
|
fn test_banned_members() {
|
||||||
// Banning single user
|
// Banning single user
|
||||||
let mut group = GroupConfig::default();
|
let mut group = empty_group_config();
|
||||||
group.set_member_only(true);
|
group.set_member_only(true);
|
||||||
|
|
||||||
group.set_member("piggo@piggo.space", true).unwrap();
|
group.set_member("piggo@piggo.space", true).unwrap();
|
||||||
@@ -656,7 +678,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_server_ban() {
|
fn test_server_ban() {
|
||||||
let mut group = GroupConfig::default();
|
let mut group = empty_group_config();
|
||||||
assert!(group.can_write("hitler@nazi.camp"), "randos can write");
|
assert!(group.can_write("hitler@nazi.camp"), "randos can write");
|
||||||
|
|
||||||
group.ban_server("nazi.camp", true).unwrap();
|
group.ban_server("nazi.camp", true).unwrap();
|
||||||
@@ -676,7 +698,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sanity() {
|
fn test_sanity() {
|
||||||
let mut group = GroupConfig::default();
|
let mut group = empty_group_config();
|
||||||
|
|
||||||
group.set_admin("piggo@piggo.space", true).unwrap();
|
group.set_admin("piggo@piggo.space", true).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
+11
-10
@@ -71,7 +71,7 @@ impl ConfigStore {
|
|||||||
let config: CommonConfig = if let Some(cf) = &common_file {
|
let config: CommonConfig = if let Some(cf) = &common_file {
|
||||||
debug!("Loading common config from {}", cf.display());
|
debug!("Loading common config from {}", cf.display());
|
||||||
let f = tokio::fs::read(&cf).await?;
|
let f = tokio::fs::read(&cf).await?;
|
||||||
serde_json::from_slice(&f)?
|
json5::from_str(&String::from_utf8_lossy(&f))?
|
||||||
} else {
|
} else {
|
||||||
debug!("No common config file, using defaults");
|
debug!("No common config file, using defaults");
|
||||||
CommonConfig::default()
|
CommonConfig::default()
|
||||||
@@ -127,7 +127,7 @@ impl ConfigStore {
|
|||||||
|
|
||||||
let group_dir = self.groups_path.join(&opts.acct);
|
let group_dir = self.groups_path.join(&opts.acct);
|
||||||
|
|
||||||
let _data = GroupConfig::from_appdata(opts.acct.clone(), appdata, group_dir).await?;
|
GroupConfig::initialize_by_appdata(opts.acct.clone(), appdata, group_dir).await?;
|
||||||
|
|
||||||
// save & persist
|
// save & persist
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ impl ConfigStore {
|
|||||||
pub async fn reauth_group(&self, acct: &str) -> Result<(), GroupError> {
|
pub async fn reauth_group(&self, acct: &str) -> Result<(), GroupError> {
|
||||||
let group_dir = self.groups_path.join(&acct);
|
let group_dir = self.groups_path.join(&acct);
|
||||||
|
|
||||||
let mut config = GroupConfig::from_dir(group_dir).await?;
|
let mut config = GroupConfig::from_dir(group_dir, &self.config).await?;
|
||||||
|
|
||||||
println!("--- Re-authenticating bot user @{} ---", acct);
|
println!("--- Re-authenticating bot user @{} ---", acct);
|
||||||
let registration = Registration::new(config.get_appdata().base.to_string())
|
let registration = Registration::new(config.get_appdata().base.to_string())
|
||||||
@@ -225,7 +225,7 @@ impl ConfigStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_locale(&mut self, locale_name: &str, locale_json: &str, is_default: bool) {
|
fn load_locale(&mut self, locale_name: &str, locale_json: &str, is_default: bool) {
|
||||||
if let Ok(mut tr) = serde_json::from_str::<TranslationTable>(locale_json) {
|
if let Ok(mut tr) = json5::from_str::<TranslationTable>(locale_json) {
|
||||||
debug!("Loaded locale: {}", locale_name);
|
debug!("Loaded locale: {}", locale_name);
|
||||||
|
|
||||||
if !is_default {
|
if !is_default {
|
||||||
@@ -233,11 +233,12 @@ impl ConfigStore {
|
|||||||
|
|
||||||
for (k, v) in def_tr.entries() {
|
for (k, v) in def_tr.entries() {
|
||||||
if !tr.translation_exists(k) {
|
if !tr.translation_exists(k) {
|
||||||
warn!("locale \"{}\" is missing \"{}\", default: {:?}",
|
if self.config.validate_locales {
|
||||||
locale_name,
|
warn!("locale \"{}\" is missing \"{}\", default: {:?}",
|
||||||
k,
|
locale_name,
|
||||||
def_tr.get_translation_raw(k).unwrap());
|
k,
|
||||||
|
def_tr.get_translation_raw(k).unwrap());
|
||||||
|
}
|
||||||
tr.add_translation(k, v);
|
tr.add_translation(k, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +262,7 @@ impl ConfigStore {
|
|||||||
.map(|entry_maybe: Result<std::fs::DirEntry, std::io::Error>| async {
|
.map(|entry_maybe: Result<std::fs::DirEntry, std::io::Error>| async {
|
||||||
match entry_maybe {
|
match entry_maybe {
|
||||||
Ok(entry) => {
|
Ok(entry) => {
|
||||||
let gc = GroupConfig::from_dir(entry.path()).await.ok()?;
|
let gc = GroupConfig::from_dir(entry.path(), &config).await.ok()?;
|
||||||
|
|
||||||
if !gc.is_enabled() {
|
if !gc.is_enabled() {
|
||||||
debug!("Group @{} is DISABLED", gc.get_acct());
|
debug!("Group @{} is DISABLED", gc.get_acct());
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ impl TranslationTable {
|
|||||||
self.entries.get(key).map(|s| s.as_str())
|
self.entries.get(key).map(|s| s.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add or update a translation
|
||||||
pub fn add_translation(&mut self, key : impl ToString, subs : impl ToString) {
|
pub fn add_translation(&mut self, key : impl ToString, subs : impl ToString) {
|
||||||
self.entries.insert(key.to_string(), subs.to_string());
|
self.entries.insert(key.to_string(), subs.to_string());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user