mirror of
https://git.ondrovo.com/MightyPork/group-actor.git
synced 2024-12-18 05:06:39 +00:00
readme, some fixes
This commit is contained in:
parent
2b84e5eeb0
commit
957f0dbb3b
10 changed files with 118 additions and 59 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,7 +1,16 @@
|
|||
# Changelog
|
||||
|
||||
## v0.2
|
||||
## v0.2.2
|
||||
- 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
|
||||
- More reliable websocket reconnect, workaround for pleroma socket going silent
|
||||
|
||||
## v0.2.0
|
||||
- Add hashtag boosting and back-follow/unfollow
|
||||
- Add hashtag commands
|
||||
- Code reorganization
|
||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -276,7 +276,7 @@ checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
|
|||
[[package]]
|
||||
name = "elefren"
|
||||
version = "0.22.0"
|
||||
source = "git+https://git.ondrovo.com/MightyPork/elefren-fork.git?rev=54a0e55#54a0e55964784368864f36580c5630f730bf72dc"
|
||||
source = "git+https://git.ondrovo.com/MightyPork/elefren-fork.git?rev=a0ebb46#a0ebb46542ede2d235ca6094135a6d6d01d0ecb8"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"doc-comment",
|
||||
|
@ -328,7 +328,7 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
|||
|
||||
[[package]]
|
||||
name = "fedigroups"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "fedigroups"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
@ -10,7 +10,7 @@ build = "build.rs"
|
|||
|
||||
[dependencies]
|
||||
#elefren = { path = "../elefren22-fork" }
|
||||
elefren = { git = "https://git.ondrovo.com/MightyPork/elefren-fork.git", rev = "54a0e55" }
|
||||
elefren = { git = "https://git.ondrovo.com/MightyPork/elefren-fork.git", rev = "a0ebb46" }
|
||||
|
||||
env_logger = "0.9.0"
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ Any user (member in member-only groups) can post to the group by mentioning the
|
|||
|
||||
### Group hashtags
|
||||
|
||||
Admins can add hashtags to the group config (`/add #hashtag`, remove the same way: `/remove #hashtag`).
|
||||
Admins can add hashtags to the group config (`/add #hashtag`, remove the same way: `/remove #hashtag`). Hashtags are case-insensitive.
|
||||
|
||||
When a *group member* posts one of the group hashtags, the group will reblog it. This is a nicer way to share posts, you don't have to mention the group user at all.
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ pub fn parse_status_tags(content: &str) -> Vec<String> {
|
|||
let mut tags = vec![];
|
||||
for c in RE_A_HASHTAG.captures_iter(&content) {
|
||||
if let Some(s) = c.get(1) {
|
||||
tags.push(s.as_str().to_string())
|
||||
tags.push(s.as_str().to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,6 +150,11 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let content = voca_rs::strip::strip_tags(&content);
|
||||
debug!("Stripped tags: {}", content);
|
||||
|
||||
if !content.contains('/') && !content.contains('\\') {
|
||||
// No slash = no command
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// short-circuiting commands
|
||||
|
||||
if RE_IGNORE.is_match(&content) {
|
||||
|
@ -221,7 +226,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let s = s.as_str();
|
||||
let s = s.trim_start_matches('@');
|
||||
debug!("BAN USER: {}", s);
|
||||
commands.push(StatusCommand::BanUser(s.to_owned()));
|
||||
commands.push(StatusCommand::BanUser(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,21 +235,21 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let s = s.as_str();
|
||||
let s = s.trim_start_matches('@');
|
||||
debug!("UNBAN USER: {}", s);
|
||||
commands.push(StatusCommand::UnbanUser(s.to_owned()));
|
||||
commands.push(StatusCommand::UnbanUser(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
for c in RE_BAN_SERVER.captures_iter(&content) {
|
||||
if let Some(s) = c.get(1) {
|
||||
debug!("BAN SERVER: {}", s.as_str());
|
||||
commands.push(StatusCommand::BanServer(s.as_str().to_owned()));
|
||||
commands.push(StatusCommand::BanServer(s.as_str().to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
for c in RE_UNBAN_SERVER.captures_iter(&content) {
|
||||
if let Some(s) = c.get(1) {
|
||||
debug!("UNBAN SERVER: {}", s.as_str());
|
||||
commands.push(StatusCommand::UnbanServer(s.as_str().to_owned()));
|
||||
commands.push(StatusCommand::UnbanServer(s.as_str().to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +258,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let s = s.as_str();
|
||||
let s = s.trim_start_matches('@');
|
||||
debug!("ADD MEMBER: {}", s);
|
||||
commands.push(StatusCommand::AddMember(s.to_owned()));
|
||||
commands.push(StatusCommand::AddMember(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,7 +267,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let s = s.as_str();
|
||||
let s = s.trim_start_matches('@');
|
||||
debug!("REMOVE USER: {}", s);
|
||||
commands.push(StatusCommand::RemoveMember(s.to_owned()));
|
||||
commands.push(StatusCommand::RemoveMember(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,7 +275,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
if let Some(s) = c.get(1) {
|
||||
let s = s.as_str();
|
||||
debug!("ADD TAG: {}", s);
|
||||
commands.push(StatusCommand::AddTag(s.to_owned()));
|
||||
commands.push(StatusCommand::AddTag(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,7 +283,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
if let Some(s) = c.get(1) {
|
||||
let s = s.as_str();
|
||||
debug!("REMOVE TAG: {}", s);
|
||||
commands.push(StatusCommand::RemoveTag(s.to_owned()));
|
||||
commands.push(StatusCommand::RemoveTag(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,7 +292,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let s = s.as_str();
|
||||
let s = s.trim_start_matches('@');
|
||||
debug!("ADD ADMIN: {}", s);
|
||||
commands.push(StatusCommand::GrantAdmin(s.to_owned()));
|
||||
commands.push(StatusCommand::GrantAdmin(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,7 +301,7 @@ pub fn parse_slash_commands(content: &str) -> Vec<StatusCommand> {
|
|||
let s = s.as_str();
|
||||
let s = s.trim_start_matches('@');
|
||||
debug!("REMOVE ADMIN: {}", s);
|
||||
commands.push(StatusCommand::RemoveAdmin(s.to_owned()));
|
||||
commands.push(StatusCommand::RemoveAdmin(s.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,17 @@ impl<'a> ProcessMention<'a> {
|
|||
Err(e.into())
|
||||
}
|
||||
Ok(Ok(res)) => {
|
||||
debug!("Result: {:#?}", res);
|
||||
|
||||
if let Some(item) = res.accounts.into_iter().next() {
|
||||
debug!("Search done, account found");
|
||||
Ok(Some(item.id))
|
||||
let acct_normalized = normalize_acct(&item.acct, &self.group_acct)?;
|
||||
if acct_normalized == acct {
|
||||
debug!("Search done, account found: {}", item.acct);
|
||||
Ok(Some(item.id))
|
||||
} else {
|
||||
warn!("Search done but found wrong account: {}", item.acct);
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
debug!("Search done, nothing found");
|
||||
Ok(None)
|
||||
|
@ -80,12 +88,14 @@ impl<'a> ProcessMention<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn follow_user(&self, id: &str) -> Result<(), GroupError> {
|
||||
async fn follow_user_by_id(&self, id: &str) -> Result<(), GroupError> {
|
||||
debug!("Trying to follow user #{}", id);
|
||||
self.client.follow(id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unfollow_user(&self, id: &str) -> Result<(), GroupError> {
|
||||
async fn unfollow_user_by_id(&self, id: &str) -> Result<(), GroupError> {
|
||||
debug!("Trying to unfollow user #{}", id);
|
||||
self.client.unfollow(id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -373,13 +383,15 @@ impl<'a> ProcessMention<'a> {
|
|||
match self.config.set_member(&u, true) {
|
||||
Ok(_) => {
|
||||
self.add_reply(format!("User {} added to the group!", u));
|
||||
self.follow_user(&self.status_user_id)
|
||||
self.follow_by_acct(&u)
|
||||
.await.log_error("Failed to follow");
|
||||
}
|
||||
Err(e) => {
|
||||
self.add_reply(format!("Failed to add user {} to group: {}", u, e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("User was already a member");
|
||||
}
|
||||
} else {
|
||||
self.add_reply("Only admins can manage members");
|
||||
|
@ -505,49 +517,48 @@ impl<'a> ProcessMention<'a> {
|
|||
self.add_reply("This is a public-access group. ");
|
||||
}
|
||||
|
||||
if self.config.can_write(&self.status_acct) {
|
||||
if self.is_admin {
|
||||
self.add_reply("*You are an admin.*");
|
||||
} else {
|
||||
self.add_reply("*You are a member.*");
|
||||
}
|
||||
if self.is_admin {
|
||||
self.add_reply("*You are an admin.*");
|
||||
} else if self.config.is_member(&self.status_acct) {
|
||||
self.add_reply("*You are a member.*");
|
||||
} else if self.config.is_member_only() {
|
||||
self.add_reply("*You are not a member, ask one of the admins to add you.*");
|
||||
} else {
|
||||
if self.config.is_member_only() {
|
||||
self.add_reply("*You are not a member, ask one of the admins to add you.*");
|
||||
} else {
|
||||
self.add_reply("*You are not a member, follow or use /join to join the group.*");
|
||||
}
|
||||
self.add_reply("*You are not a member, follow or use /join to join the group.*");
|
||||
}
|
||||
|
||||
self.add_reply("\n\
|
||||
To share an original post, mention the group user.\n\
|
||||
To share a post, mention the group user or use one of the group hashtags. \
|
||||
Replies and mentions with commands won't be shared.\n\
|
||||
\n\
|
||||
**Supported commands:**\n\
|
||||
`/boost, /b` - boost the replied-to post into the group\n\
|
||||
`/ignore, /i` - make the group completely ignore the post\n\
|
||||
`/ping` - check that the service is alive\n\
|
||||
`/boost`, `/b` - boost the replied-to post into the group\n\
|
||||
`/ignore`, `/i` - make the group ignore the post\n\
|
||||
`/ping` - check the service is alive\n\
|
||||
`/tags` - show group hashtags\n\
|
||||
`/join` - join the group\n\
|
||||
`/leave` - leave the group");
|
||||
|
||||
if self.config.is_member_only() {
|
||||
self.add_reply("`/members, /who` - show group members / admins");
|
||||
self.add_reply("`/members`, `/who` - show group members / admins");
|
||||
} else {
|
||||
self.add_reply("`/members, /who` - show group admins");
|
||||
self.add_reply("`/members`, `/who` - show group admins");
|
||||
}
|
||||
|
||||
if self.is_admin {
|
||||
self.add_reply("\n\
|
||||
**Admin commands:**\n\
|
||||
`/add user` - add a member (use e-mail style address)\n\
|
||||
`/kick, /remove user` - kick a member\n\
|
||||
`/ban x` - ban a user or a server\n\
|
||||
`/remove user` - remove a member\n\
|
||||
`/add #hashtag` - add a group hashtag\n\
|
||||
`/remove #hashtag` - remove a group hashtag\n\
|
||||
`/ban x` - ban a user or server\n\
|
||||
`/unban x` - lift a ban\n\
|
||||
`/op, /admin user` - grant admin rights\n\
|
||||
`/deop, /deadmin user` - revoke admin rights\n\
|
||||
`/admin user` - grant admin rights\n\
|
||||
`/deadmin user` - revoke admin rights\n\
|
||||
`/opengroup` - make member-only\n\
|
||||
`/closegroup` - make public-access\n\
|
||||
`/announce x` - make a public announcement from the rest of the status");
|
||||
`/announce x` - make a public announcement from the rest of the status (without formatting)");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -575,7 +586,7 @@ impl<'a> ProcessMention<'a> {
|
|||
// admin can leave but that's a bad idea
|
||||
let _ = self.config.set_member(&self.status_acct, false);
|
||||
self.add_reply("You're no longer a group member. Unfollow the group user to stop receiving group messages.");
|
||||
self.unfollow_user(&self.status_user_id).await
|
||||
self.unfollow_user_by_id(&self.status_user_id).await
|
||||
.log_error("Failed to unfollow");
|
||||
}
|
||||
}
|
||||
|
@ -585,7 +596,7 @@ impl<'a> ProcessMention<'a> {
|
|||
debug!("Already member or admin, try to follow-back again");
|
||||
// Already a member, so let's try to follow the user
|
||||
// again, maybe first time it failed
|
||||
self.follow_user(&self.status_user_id).await
|
||||
self.follow_user_by_id(&self.status_user_id).await
|
||||
.log_error("Failed to follow");
|
||||
} else {
|
||||
// Not a member yet
|
||||
|
@ -598,7 +609,7 @@ impl<'a> ProcessMention<'a> {
|
|||
self.append_admin_list_to_reply();
|
||||
} else {
|
||||
// Open access, try to follow back
|
||||
self.follow_user(&self.status_user_id).await
|
||||
self.follow_user_by_id(&self.status_user_id).await
|
||||
.log_error("Failed to follow");
|
||||
|
||||
// This only fails if the user is banned, but that is filtered above
|
||||
|
@ -618,7 +629,15 @@ impl<'a> ProcessMention<'a> {
|
|||
async fn unfollow_by_acct(&self, acct: &str) -> Result<(), GroupError> {
|
||||
// Try to unfollow
|
||||
if let Ok(Some(id)) = self.lookup_acct_id(acct, true).await {
|
||||
self.unfollow_user(&id).await?;
|
||||
self.unfollow_user_by_id(&id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn follow_by_acct(&self, acct: &str) -> Result<(), GroupError> {
|
||||
// Try to unfollow
|
||||
if let Ok(Some(id)) = self.lookup_acct_id(acct, false).await {
|
||||
self.follow_user_by_id(&id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use crate::error::GroupError;
|
|||
use crate::store::ConfigStore;
|
||||
use crate::store::data::GroupConfig;
|
||||
use crate::utils::{LogError, normalize_acct, VisExt};
|
||||
use crate::command::StatusCommand;
|
||||
|
||||
mod handle_mention;
|
||||
|
||||
|
@ -241,7 +242,7 @@ impl GroupHandle {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle a non-mention status
|
||||
/// Handle a non-mention status for tags
|
||||
async fn handle_status(&mut self, s: Status) -> Result<(), GroupError> {
|
||||
debug!("Handling status #{}", s.id);
|
||||
let ts = s.timestamp_millis();
|
||||
|
@ -252,11 +253,22 @@ impl GroupHandle {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if s.in_reply_to_id.is_some() {
|
||||
debug!("Status is a reply, discard");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !s.content.contains('#') {
|
||||
debug!("No tags in status, discard");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let commands = crate::command::parse_slash_commands(&s.content);
|
||||
if commands.contains(&StatusCommand::Ignore) {
|
||||
debug!("Post has IGNORE command, discard");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let group_user = self.config.get_acct();
|
||||
let status_user = normalize_acct(&s.account.acct, group_user)?;
|
||||
|
||||
|
|
|
@ -82,7 +82,13 @@ async fn main() -> anyhow::Result<()> {
|
|||
.await?;
|
||||
|
||||
if let Some(handle) = args.value_of("auth") {
|
||||
let handle = handle.to_lowercase();
|
||||
let acct = handle.trim_start_matches('@');
|
||||
|
||||
if store.group_exists(acct).await {
|
||||
anyhow::bail!("Group already exists in config!");
|
||||
}
|
||||
|
||||
if let Some(server) = acct_to_server(acct) {
|
||||
let g = store
|
||||
.auth_new_group(NewGroupOptions {
|
||||
|
|
|
@ -152,6 +152,10 @@ impl ConfigStore {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub async fn group_exists(&self, acct : &str) -> bool {
|
||||
self.data.read().await.groups.contains_key(acct)
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) async fn get_group_config(&self, group: &str) -> Option<GroupConfig> {
|
||||
let c = self.data.read().await;
|
||||
|
@ -191,10 +195,7 @@ impl ConfigStore {
|
|||
}
|
||||
|
||||
fn make_scopes() -> Scopes {
|
||||
Scopes::read(scopes::Read::Accounts)
|
||||
| Scopes::read(scopes::Read::Notifications)
|
||||
| Scopes::read(scopes::Read::Statuses)
|
||||
| Scopes::read(scopes::Read::Follows)
|
||||
Scopes::read_all()
|
||||
| Scopes::write(scopes::Write::Statuses)
|
||||
| Scopes::write(scopes::Write::Media)
|
||||
| Scopes::write(scopes::Write::Follows)
|
||||
|
|
19
src/utils.rs
19
src/utils.rs
|
@ -1,4 +1,3 @@
|
|||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
|
||||
use elefren::status_builder::Visibility;
|
||||
|
@ -24,14 +23,14 @@ pub(crate) fn acct_to_server(acct: &str) -> Option<&str> {
|
|||
acct.trim_start_matches('@').split('@').nth(1)
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_acct<'a, 'g>(acct: &'a str, group: &'g str) -> Result<Cow<'a, str>, GroupError> {
|
||||
let acct = acct.trim_start_matches('@');
|
||||
if acct_to_server(acct).is_some() {
|
||||
pub(crate) fn normalize_acct(acct: &str, group: &str) -> Result<String, GroupError> {
|
||||
let acct = acct.trim_start_matches('@').to_lowercase();
|
||||
if acct_to_server(&acct).is_some() {
|
||||
// already has server
|
||||
Ok(Cow::Borrowed(acct))
|
||||
Ok(acct)
|
||||
} else if let Some(gs) = acct_to_server(group) {
|
||||
// attach server from the group actor
|
||||
Ok(Cow::Owned(format!("{}@{}", acct, gs)))
|
||||
Ok(format!("{}@{}", acct, gs))
|
||||
} else {
|
||||
Err(GroupError::BadConfig(
|
||||
format!("Group acct {} is missing server!", group).into(),
|
||||
|
@ -81,6 +80,14 @@ mod test {
|
|||
Ok("piggo@piggo.space".into()),
|
||||
normalize_acct("piggo@piggo.space", "uhh")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok("piggo@piggo.space".into()),
|
||||
normalize_acct("piGGgo@pIggo.spaCe", "uhh")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok("piggo@banana.nana".into()),
|
||||
normalize_acct("piGGgo", "foo@baNANA.nana")
|
||||
);
|
||||
assert_eq!(Err(GroupError::BadConfig("_".into())), normalize_acct("piggo", "uhh"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue