forked from MightyPork/group-actor
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
131a59b3b3 | ||
|
|
1f08706a44 | ||
|
|
e52e692889 | ||
|
|
b4619f25e7 | ||
|
|
5389031d8c | ||
|
|
e77c8157ae
|
||
|
|
cb6724baab
|
||
|
|
cbd3c0a575 |
+24
-14
@@ -1,30 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## v0.4.3
|
||||
## v0.4.6 [2025-06-15]
|
||||
- Update dependencies
|
||||
|
||||
## v0.4.5 [2022-11-02]
|
||||
- Ignore #nobot in bio if the user is also a member
|
||||
|
||||
## v0.4.4 [2021-11-02]
|
||||
- Fix some failing tests
|
||||
- Lowercase the domain when normalizing an account
|
||||
|
||||
## v0.4.3 [2021-10-12]
|
||||
- Fix hashtag not working in a mention
|
||||
|
||||
## v0.4.2
|
||||
## v0.4.2 [2021-10-12]
|
||||
- Fix URL fragment detected as hashtag
|
||||
|
||||
## v0.4.1
|
||||
## v0.4.1 [2021-10-10]
|
||||
- "en" translation fixes
|
||||
- add `messages.json`
|
||||
- Config files are now parsed as JSON5 = comments are allowed
|
||||
|
||||
## v0.4.0
|
||||
## v0.4.0 [2021-10-10]
|
||||
- Add a translation system using the `locales` folder
|
||||
- Add more trace logging
|
||||
|
||||
## v0.3.0
|
||||
## v0.3.0 [2021-10-05]
|
||||
- Changed config/storage format to directory-based, removed shared config mutex
|
||||
- Made more options configurable (timeouts, catch-up limits, etc)
|
||||
- Changed default log level to Debug, added `-q` to reduce it (opposite of `-v`)
|
||||
- Code cleaning
|
||||
|
||||
## v0.2.8
|
||||
## v0.2.8 [2021-08-30]
|
||||
- fix error processing statuses when a misskey poll has infinite run time
|
||||
|
||||
## v0.2.7
|
||||
## v0.2.7 [2021-08-30]
|
||||
- Fix some wrong responses to admin commands
|
||||
- Remove automatic announcements from some admin commands
|
||||
- Send no reply to unauthorized admin commands
|
||||
@@ -32,34 +42,34 @@
|
||||
- Add `/optout` and `/optin`
|
||||
- Add `#nobot` checking when using the `/boost` command
|
||||
|
||||
## v0.2.6
|
||||
## v0.2.6 [2021-08-28]
|
||||
- Allow boosting group hashtags when they are in a reply, except when it is private/DM
|
||||
or contains actionable commands
|
||||
- `/follow` and `/unfollow` are now aliases to `/add` and `/remove` (for users and tags)
|
||||
- Add workaround for pleroma markdown processor eating trailing hashtags
|
||||
- Command replies are now always DM again so we don't spam timelines
|
||||
|
||||
## v0.2.5
|
||||
## v0.2.5 [2021-08-27]
|
||||
- Add `/undo` command
|
||||
- Fix users joining via follow not marked as members
|
||||
|
||||
## v0.2.4
|
||||
## v0.2.4 [2021-08-27]
|
||||
- make account lookup try harder
|
||||
|
||||
## v0.2.3
|
||||
## v0.2.3 [2021-08-27]
|
||||
- `/add user` will now try to follow even if already a member
|
||||
|
||||
## v0.2.2
|
||||
## v0.2.2 [2021-08-27]
|
||||
- All hashtags, server names and handles are now lowercased = case-insensitive
|
||||
- Prevent the `-a` flag overwriting existing group in the config
|
||||
- Update the help text
|
||||
- `/i` now works in hashtag posts
|
||||
- `/add user` and `/remove user` now correctly follow/unfollow
|
||||
|
||||
## v0.2.1
|
||||
## v0.2.1 [2021-08-27]
|
||||
- More reliable websocket reconnect, workaround for pleroma socket going silent
|
||||
|
||||
## v0.2.0
|
||||
## v0.2.0 [2021-08-27]
|
||||
- Add hashtag boosting and back-follow/unfollow
|
||||
- Add hashtag commands
|
||||
- Code reorganization
|
||||
|
||||
Generated
+1339
-1406
File diff suppressed because it is too large
Load Diff
+10
-14
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fedigroups"
|
||||
version = "0.4.3"
|
||||
version = "0.4.6"
|
||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
@@ -9,24 +9,20 @@ build = "build.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
#elefren = { path = "../elefren22-fork" }
|
||||
elefren = { git = "https://git.ondrovo.com/MightyPork/elefren-fork.git", rev = "b10e5935ae32f4756b19e9ca58b78a5382f865d1" }
|
||||
#elefren = { path = "../elefren-fork" }
|
||||
elefren = { git = "https://git.ondrovo.com/MightyPork/elefren-fork.git", rev = "8aa32c060eddb825e46f777661a93ff6f745659d" }
|
||||
|
||||
env_logger = "0.9.0"
|
||||
env_logger = "0.11.8"
|
||||
|
||||
log = "0.4.14"
|
||||
log = "0.4.27"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
anyhow = "1"
|
||||
clap = "2.33.0"
|
||||
clap = "3.2"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-stream = "0.1.7"
|
||||
thiserror = "1.0.26"
|
||||
thiserror = "2.0.12"
|
||||
futures = "0.3"
|
||||
voca_rs = "1.13.0"
|
||||
regex = "1.5.4"
|
||||
once_cell = "1.8.0"
|
||||
voca_rs = "1.15.2"
|
||||
regex = "1.11.1"
|
||||
once_cell = "1.21.3"
|
||||
json5 = "0.4.1"
|
||||
|
||||
native-tls = "0.2.8"
|
||||
websocket = "0.26.2"
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
fn main() {
|
||||
let desc_c = Command::new("git").args(&["describe", "--all", "--long"]).output().unwrap();
|
||||
let desc_c = Command::new("git").args(["describe", "--all", "--long"]).output().unwrap();
|
||||
|
||||
let desc = unsafe { str::from_utf8_unchecked(&desc_c.stdout) };
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"ping_response": "pong, toto je fedigroups verze {version}"
|
||||
}
|
||||
+9
-1
@@ -8,6 +8,8 @@ pub enum GroupError {
|
||||
UserIsBanned,
|
||||
#[error("User opted out from the group")]
|
||||
UserOptedOut,
|
||||
#[error("User opted out from the group using #nobot")]
|
||||
UserOptedOutNobot,
|
||||
#[error("Server could not be banned because there are admin users on it")]
|
||||
AdminsOnServer,
|
||||
#[error("Config error: {0}")]
|
||||
@@ -21,7 +23,13 @@ pub enum GroupError {
|
||||
#[error(transparent)]
|
||||
Serializer5(#[from] json5::Error),
|
||||
#[error(transparent)]
|
||||
Elefren(#[from] elefren::Error),
|
||||
Elefren(Box<elefren::Error>),
|
||||
}
|
||||
|
||||
impl From<elefren::Error> for GroupError {
|
||||
fn from(e: elefren::Error) -> Self {
|
||||
Self::Elefren(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
// this is for tests
|
||||
|
||||
@@ -2,25 +2,21 @@ use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
use elefren::{FediClient, SearchType, StatusBuilder};
|
||||
use elefren::entities::account::Account;
|
||||
use elefren::entities::prelude::Status;
|
||||
use elefren::status_builder::Visibility;
|
||||
use elefren::{FediClient, SearchType, StatusBuilder};
|
||||
|
||||
use crate::command::{RE_NOBOT_TAG, StatusCommand};
|
||||
use crate::command::{StatusCommand, RE_NOBOT_TAG};
|
||||
use crate::error::GroupError;
|
||||
use crate::group_handler::GroupHandle;
|
||||
use crate::store::CommonConfig;
|
||||
use crate::store::group_config::GroupConfig;
|
||||
use crate::store::CommonConfig;
|
||||
use crate::tr::TranslationTable;
|
||||
use crate::utils;
|
||||
use crate::utils::{LogError, normalize_acct, VisExt};
|
||||
use crate::utils::{normalize_acct, LogError, VisExt};
|
||||
|
||||
use crate::{
|
||||
grp_debug,
|
||||
grp_warn,
|
||||
grp_info
|
||||
};
|
||||
use crate::{grp_debug, grp_info, grp_warn};
|
||||
|
||||
pub struct ProcessMention<'a> {
|
||||
status: Status,
|
||||
@@ -51,7 +47,7 @@ impl<'a> ProcessMention<'a> {
|
||||
self.client
|
||||
.search_v2(acct, !followed, Some(SearchType::Accounts), Some(1), followed),
|
||||
)
|
||||
.await
|
||||
.await
|
||||
{
|
||||
Err(_) => {
|
||||
grp_warn!(self, "Account lookup timeout!");
|
||||
@@ -98,9 +94,9 @@ impl<'a> ProcessMention<'a> {
|
||||
let mut to_add = String::new();
|
||||
for m in members {
|
||||
to_add.push_str(&if admins.contains(&m) {
|
||||
crate::tr!(self, "user_list_entry_admin", user=m)
|
||||
crate::tr!(self, "user_list_entry_admin", user = m)
|
||||
} else {
|
||||
crate::tr!(self, "user_list_entry", user=m)
|
||||
crate::tr!(self, "user_list_entry", user = m)
|
||||
});
|
||||
}
|
||||
self.add_reply(&to_add);
|
||||
@@ -149,8 +145,7 @@ impl<'a> ProcessMention<'a> {
|
||||
}
|
||||
|
||||
async fn reblog_status(&self) {
|
||||
self.client.reblog(&self.status.id)
|
||||
.await.log_error("Failed to reblog status");
|
||||
self.client.reblog(&self.status.id).await.log_error("Failed to reblog status");
|
||||
self.delay_after_post().await;
|
||||
}
|
||||
|
||||
@@ -419,7 +414,7 @@ impl<'a> ProcessMention<'a> {
|
||||
|
||||
async fn cmd_undo(&mut self) -> Result<(), GroupError> {
|
||||
if let (Some(ref parent_account_id), Some(ref parent_status_id)) =
|
||||
(&self.status.in_reply_to_account_id, &self.status.in_reply_to_id)
|
||||
(&self.status.in_reply_to_account_id, &self.status.in_reply_to_id)
|
||||
{
|
||||
if parent_account_id == &self.group_account.id {
|
||||
// This is a post sent by the group user, likely an announcement.
|
||||
@@ -675,7 +670,11 @@ impl<'a> ProcessMention<'a> {
|
||||
};
|
||||
|
||||
if self.config.is_member_only() {
|
||||
self.add_reply(crate::tr!(self, "help_group_info_closed", membership = &membership_line));
|
||||
self.add_reply(crate::tr!(
|
||||
self,
|
||||
"help_group_info_closed",
|
||||
membership = &membership_line
|
||||
));
|
||||
} else {
|
||||
self.add_reply(crate::tr!(self, "help_group_info_open", membership = &membership_line));
|
||||
}
|
||||
@@ -709,7 +708,7 @@ impl<'a> ProcessMention<'a> {
|
||||
let mut to_add = String::new();
|
||||
|
||||
for t in tags {
|
||||
to_add.push_str(&crate::tr!(self, "tag_list_entry", tag=t));
|
||||
to_add.push_str(&crate::tr!(self, "tag_list_entry", tag = t));
|
||||
}
|
||||
self.add_reply(to_add);
|
||||
}
|
||||
@@ -774,11 +773,11 @@ impl<'a> ProcessMention<'a> {
|
||||
// Try to unfollow
|
||||
let account = self.client.get_account(id).await?;
|
||||
let bio = utils::strip_html(&account.note);
|
||||
if RE_NOBOT_TAG.is_match(&bio) {
|
||||
// #nobot
|
||||
Err(GroupError::UserOptedOut)
|
||||
let normalized = normalize_acct(&account.acct, &self.group_acct)?;
|
||||
if RE_NOBOT_TAG.is_match(&bio) && !self.config.is_member(&normalized) {
|
||||
// #nobot in a non-member account
|
||||
Err(GroupError::UserOptedOutNobot)
|
||||
} else {
|
||||
let normalized = normalize_acct(&account.acct, &self.group_acct)?;
|
||||
if self.config.is_banned(&normalized) {
|
||||
Err(GroupError::UserIsBanned)
|
||||
} else if self.config.is_optout(&normalized) {
|
||||
@@ -844,7 +843,7 @@ fn smart_split(msg: &str, prefix: Option<String>, limit: usize) -> Vec<String> {
|
||||
|
||||
while this_piece.len() > limit {
|
||||
// line too long, try splitting at the last space, if any
|
||||
let to_send = if let Some(last_space) = (&this_piece[..=limit]).rfind(' ') {
|
||||
let to_send = if let Some(last_space) = this_piece[..=limit].rfind(' ') {
|
||||
// println!("line split at word boundary");
|
||||
let mut p = this_piece.split_off(last_space + 1);
|
||||
std::mem::swap(&mut p, &mut this_piece);
|
||||
|
||||
@@ -34,19 +34,11 @@ pub struct GroupHandle {
|
||||
pub internal: GroupInternal,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GroupInternal {
|
||||
recently_seen_notif_statuses: VecDeque<String>,
|
||||
}
|
||||
|
||||
impl Default for GroupInternal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
recently_seen_notif_statuses: VecDeque::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! grp_debug {
|
||||
($self:ident, $f:expr) => {
|
||||
@@ -138,11 +130,11 @@ impl GroupHandle {
|
||||
match self.run_internal().await {
|
||||
Ok(()) => unreachable!(),
|
||||
Err(e @ GroupError::BadConfig(_)) => {
|
||||
grp_error!(self, "ERROR in group handler, aborting! {}", e);
|
||||
grp_error!(self, "ERROR in group handler, aborting! {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
Err(other) => {
|
||||
grp_error!(self, "ERROR in group handler, will restart! {}", other);
|
||||
grp_error!(self, "ERROR in group handler, will restart! {:?}", other);
|
||||
tokio::time::sleep(Duration::from_secs_f64(self.cc.delay_reopen_error_s)).await;
|
||||
}
|
||||
}
|
||||
@@ -175,7 +167,7 @@ impl GroupHandle {
|
||||
grp_debug!(self, "No notifs missed");
|
||||
}
|
||||
Err(e) => {
|
||||
grp_error!(self, "Failed to handle missed notifs: {}", e);
|
||||
grp_error!(self, "Failed to handle missed notifs: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +181,7 @@ impl GroupHandle {
|
||||
grp_debug!(self, "No statuses missed");
|
||||
}
|
||||
Err(e) => {
|
||||
grp_error!(self, "Failed to handle missed statuses: {}", e);
|
||||
grp_error!(self, "Failed to handle missed statuses: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,8 +544,7 @@ impl GroupHandle {
|
||||
|
||||
self.config.set_member(notif_acct, true).log_error("Fail add a member");
|
||||
|
||||
crate::tr!(self, "mention_prefix", user = notif_acct)
|
||||
+ &crate::tr!(self, "welcome_public")
|
||||
crate::tr!(self, "mention_prefix", user = notif_acct) + &crate::tr!(self, "welcome_public")
|
||||
};
|
||||
|
||||
let post = StatusBuilder::new()
|
||||
|
||||
+9
-11
@@ -31,26 +31,26 @@ async fn main() -> anyhow::Result<()> {
|
||||
let args = clap::App::new("groups")
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
.short('v')
|
||||
.multiple_occurrences(true)
|
||||
.help("increase logging, can be repeated"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("quiet")
|
||||
.short("q")
|
||||
.multiple(true)
|
||||
.short('q')
|
||||
.multiple_occurrences(true)
|
||||
.help("decrease logging, can be repeated"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("config")
|
||||
.short("c")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true)
|
||||
.help("set custom config directory, defaults to the current folder"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("auth")
|
||||
.short("a")
|
||||
.short('a')
|
||||
.long("auth")
|
||||
.takes_value(true)
|
||||
.value_name("HANDLE")
|
||||
@@ -58,7 +58,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("reauth")
|
||||
.short("A")
|
||||
.short('A')
|
||||
.long("reauth")
|
||||
.takes_value(true)
|
||||
.value_name("HANDLE")
|
||||
@@ -76,10 +76,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let default_level = 3;
|
||||
|
||||
let level = (
|
||||
default_level as isize
|
||||
+ args.occurrences_of("verbose") as isize
|
||||
- args.occurrences_of("quiet") as isize)
|
||||
let level = (default_level as isize + args.occurrences_of("verbose") as isize
|
||||
- args.occurrences_of("quiet") as isize)
|
||||
.clamp(0, LEVELS.len() as isize) as usize;
|
||||
|
||||
env_logger::Builder::new()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::store::DEFAULT_LOCALE_NAME;
|
||||
use crate::tr::TranslationTable;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
@@ -29,7 +29,7 @@ pub struct CommonConfig {
|
||||
/// This is a work-around for servers that stop sending notifs after a while.
|
||||
pub socket_retire_time_s: f64,
|
||||
#[serde(skip)]
|
||||
pub tr : HashMap<String, TranslationTable>,
|
||||
pub tr: HashMap<String, TranslationTable>,
|
||||
}
|
||||
|
||||
impl Default for CommonConfig {
|
||||
@@ -52,10 +52,10 @@ impl Default for CommonConfig {
|
||||
}
|
||||
|
||||
impl CommonConfig {
|
||||
pub fn tr(&self, lang : &str) -> &TranslationTable {
|
||||
pub fn tr(&self, lang: &str) -> &TranslationTable {
|
||||
match self.tr.get(lang) {
|
||||
Some(tr) => tr,
|
||||
None => self.tr.get(DEFAULT_LOCALE_NAME).expect("default locale is not loaded")
|
||||
None => self.tr.get(DEFAULT_LOCALE_NAME).expect("default locale is not loaded"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-42
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
use elefren::AppData;
|
||||
|
||||
use crate::error::GroupError;
|
||||
use crate::store::{DEFAULT_LOCALE_NAME, CommonConfig};
|
||||
use crate::store::{CommonConfig, DEFAULT_LOCALE_NAME};
|
||||
use crate::tr::TranslationTable;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -25,7 +25,7 @@ struct FixedConfig {
|
||||
_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
struct MutableConfig {
|
||||
/// Hashtags the group will auto-boost from it's members
|
||||
@@ -48,7 +48,7 @@ struct MutableConfig {
|
||||
_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
struct StateConfig {
|
||||
/// Last seen notification timestamp (millis)
|
||||
@@ -94,33 +94,6 @@ impl Default for FixedConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MutableConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
group_tags: Default::default(),
|
||||
admin_users: Default::default(),
|
||||
member_users: Default::default(),
|
||||
banned_users: Default::default(),
|
||||
optout_users: Default::default(),
|
||||
member_only: false,
|
||||
banned_servers: Default::default(),
|
||||
_dirty: false,
|
||||
_path: PathBuf::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StateConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last_notif_ts: 0,
|
||||
last_status_ts: 0,
|
||||
_dirty: false,
|
||||
_path: PathBuf::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_change_tracking {
|
||||
($struc:ident) => {
|
||||
impl $struc {
|
||||
@@ -206,7 +179,7 @@ async fn load_locale_override_file(locale_path: impl AsRef<Path>) -> Result<Opti
|
||||
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))?;
|
||||
let opt: TranslationTable = json5::from_str(&String::from_utf8_lossy(&f))?;
|
||||
Ok(Some(opt))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -259,7 +232,11 @@ impl GroupConfig {
|
||||
}
|
||||
|
||||
/// (re)init using new authorization
|
||||
pub(crate) async fn initialize_by_appdata(acct: String, appdata: AppData, group_dir: PathBuf) -> Result<(), GroupError> {
|
||||
pub(crate) async fn initialize_by_appdata(
|
||||
acct: String,
|
||||
appdata: AppData,
|
||||
group_dir: PathBuf,
|
||||
) -> Result<(), GroupError> {
|
||||
if !group_dir.is_dir() {
|
||||
debug!("Creating group directory");
|
||||
tokio::fs::create_dir_all(&group_dir).await?;
|
||||
@@ -306,12 +283,17 @@ impl GroupConfig {
|
||||
/* state */
|
||||
let state = load_or_create_state_file(state_path).await?;
|
||||
|
||||
let g = GroupConfig { config, control, state, _group_tr: TranslationTable::new() };
|
||||
let g = GroupConfig {
|
||||
config,
|
||||
control,
|
||||
state,
|
||||
_group_tr: TranslationTable::new(),
|
||||
};
|
||||
g.warn_of_bad_config();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn from_dir(group_dir: PathBuf, cc : &CommonConfig) -> 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 control_path = group_dir.join("control.json");
|
||||
let state_path = group_dir.join("state.json");
|
||||
@@ -338,7 +320,12 @@ impl GroupConfig {
|
||||
}
|
||||
}
|
||||
|
||||
let g = GroupConfig { config, control, state, _group_tr: tr };
|
||||
let g = GroupConfig {
|
||||
config,
|
||||
control,
|
||||
state,
|
||||
_group_tr: tr,
|
||||
};
|
||||
g.warn_of_bad_config();
|
||||
Ok(g)
|
||||
}
|
||||
@@ -458,7 +445,7 @@ impl GroupConfig {
|
||||
/// Check if the user's server is banned
|
||||
fn is_users_server_banned(&self, acct: &str) -> bool {
|
||||
let server = acct_to_server(acct);
|
||||
self.is_server_banned(server)
|
||||
self.is_server_banned(&server)
|
||||
}
|
||||
|
||||
pub(crate) fn can_write(&self, acct: &str) -> bool {
|
||||
@@ -574,8 +561,8 @@ impl GroupConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn acct_to_server(acct: &str) -> &str {
|
||||
acct.split('@').nth(1).unwrap_or_default()
|
||||
fn acct_to_server(acct: &str) -> String {
|
||||
crate::utils::acct_to_server(acct).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -588,15 +575,15 @@ mod tests {
|
||||
config: Default::default(),
|
||||
control: Default::default(),
|
||||
state: Default::default(),
|
||||
_group_tr: Default::default()
|
||||
_group_tr: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_acct_to_server() {
|
||||
assert_eq!("pikachu.rocks", acct_to_server("raichu@pikachu.rocks"));
|
||||
assert_eq!("pikachu.rocks", acct_to_server("m@pikachu.rocks"));
|
||||
assert_eq!("", acct_to_server("what"));
|
||||
assert_eq!("pikachu.rocks".to_string(), acct_to_server("raichu@pikachu.rocks"));
|
||||
assert_eq!("pikachu.rocks".to_string(), acct_to_server("m@pikachu.rocks"));
|
||||
assert_eq!("".to_string(), acct_to_server("what"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+20
-20
@@ -9,9 +9,9 @@ use crate::group_handler::{GroupHandle, GroupInternal};
|
||||
|
||||
pub mod common_config;
|
||||
pub mod group_config;
|
||||
use crate::tr::TranslationTable;
|
||||
pub use common_config::CommonConfig;
|
||||
pub use group_config::GroupConfig;
|
||||
use crate::tr::TranslationTable;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ConfigStore {
|
||||
@@ -32,8 +32,8 @@ pub struct StoreOptions {
|
||||
pub store_dir: String,
|
||||
}
|
||||
|
||||
const DEFAULT_LOCALE_NAME : &str = "en";
|
||||
const DEFAULT_LOCALE_JSON : &str = include_str!("../../locales/en.json");
|
||||
const DEFAULT_LOCALE_NAME: &str = "en";
|
||||
const DEFAULT_LOCALE_JSON: &str = include_str!("../../locales/en.json");
|
||||
|
||||
impl ConfigStore {
|
||||
/// Create a new instance of the store.
|
||||
@@ -149,7 +149,7 @@ impl ConfigStore {
|
||||
|
||||
/// Re-auth an existing group
|
||||
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, &self.config).await?;
|
||||
|
||||
@@ -203,21 +203,19 @@ impl ConfigStore {
|
||||
}
|
||||
};
|
||||
|
||||
for e in entries {
|
||||
if let Ok(e) = e {
|
||||
let path = e.path();
|
||||
if path.is_file() && path.extension().unwrap_or_default().to_string_lossy() == "json" {
|
||||
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
debug!("Loading locale {}", filename);
|
||||
for e in entries.flatten() {
|
||||
let path = e.path();
|
||||
if path.is_file() && path.extension().unwrap_or_default().to_string_lossy() == "json" {
|
||||
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
debug!("Loading locale {}", filename);
|
||||
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(f) => {
|
||||
let locale_name = path.file_stem().unwrap_or_default().to_string_lossy();
|
||||
self.load_locale(&locale_name, &String::from_utf8_lossy(&f), false);
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to read locale file {}: {}", path.display(), e);
|
||||
}
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(f) => {
|
||||
let locale_name = path.file_stem().unwrap_or_default().to_string_lossy();
|
||||
self.load_locale(&locale_name, &String::from_utf8_lossy(&f), false);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read locale file {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,10 +232,12 @@ impl ConfigStore {
|
||||
for (k, v) in def_tr.entries() {
|
||||
if !tr.translation_exists(k) {
|
||||
if self.config.validate_locales {
|
||||
warn!("locale \"{}\" is missing \"{}\", default: {:?}",
|
||||
warn!(
|
||||
"locale \"{}\" is missing \"{}\", default: {:?}",
|
||||
locale_name,
|
||||
k,
|
||||
def_tr.get_translation_raw(k).unwrap());
|
||||
def_tr.get_translation_raw(k).unwrap()
|
||||
);
|
||||
}
|
||||
tr.add_translation(k, v);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug,Clone,Serialize,Deserialize,Default)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct TranslationTable {
|
||||
#[serde(flatten)]
|
||||
entries: HashMap<String, String>,
|
||||
@@ -15,24 +15,24 @@ impl TranslationTable {
|
||||
}
|
||||
|
||||
/// Iterate all entries
|
||||
pub fn entries(&self) -> impl Iterator<Item=(&String, &String)> {
|
||||
pub fn entries(&self) -> impl Iterator<Item = (&String, &String)> {
|
||||
self.entries.iter()
|
||||
}
|
||||
|
||||
pub fn get_translation_raw(&self, key : &str) -> Option<&str> {
|
||||
pub fn get_translation_raw(&self, key: &str) -> Option<&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());
|
||||
}
|
||||
|
||||
pub fn translation_exists(&self, key : &str) -> bool {
|
||||
pub fn translation_exists(&self, key: &str) -> bool {
|
||||
self.entries.contains_key(key)
|
||||
}
|
||||
|
||||
pub fn subs(&self, key : &str, substitutions: &[&str]) -> String {
|
||||
pub fn subs(&self, key: &str, substitutions: &[&str]) -> String {
|
||||
match self.entries.get(key) {
|
||||
Some(s) => {
|
||||
// TODO optimize
|
||||
@@ -45,7 +45,7 @@ impl TranslationTable {
|
||||
}
|
||||
s
|
||||
}
|
||||
None => key.to_owned()
|
||||
None => key.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn deser_tr_table() {
|
||||
let tr : TranslationTable = serde_json::from_str(r#"{"foo":"bar"}"#).unwrap();
|
||||
let tr: TranslationTable = serde_json::from_str(r#"{"foo":"bar"}"#).unwrap();
|
||||
assert_eq!("bar", tr.subs("foo", &[]));
|
||||
assert_eq!("xxx", tr.subs("xxx", &[]));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn subs() {
|
||||
let mut tr = TranslationTable::new();
|
||||
|
||||
@@ -95,6 +95,8 @@ mod test {
|
||||
pub trait VisExt: Copy {
|
||||
/// Check if is private or direct
|
||||
fn is_private(self) -> bool;
|
||||
|
||||
#[allow(unused)]
|
||||
fn make_unlisted(self) -> Self;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user