mirror of
https://git.ondrovo.com/MightyPork/group-actor.git
synced 2024-11-21 15:41:03 +00:00
Merge branch 'smart-split'
This commit is contained in:
commit
6c9041eedd
3 changed files with 238 additions and 34 deletions
|
@ -262,21 +262,8 @@ impl<'a> ProcessMention<'a> {
|
|||
apply_trailing_hashtag_pleroma_bug_workaround(&mut msg);
|
||||
}
|
||||
|
||||
if let Ok(post) = StatusBuilder::new()
|
||||
.status(format!("@{user} {msg}", user = self.status_acct, msg = msg))
|
||||
.content_type(if self.want_markdown {
|
||||
"text/markdown"
|
||||
} else {
|
||||
"text/plain"
|
||||
})
|
||||
.visibility(Visibility::Direct)
|
||||
.build()
|
||||
{
|
||||
let _ = self.client.new_status(post)
|
||||
.await.log_error("Failed to post");
|
||||
// Sleep a bit to avoid throttling
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
let mention = format!("@{user} ", user = self.status_acct);
|
||||
self.send_reply_multipart(mention, msg).await?;
|
||||
}
|
||||
|
||||
if !self.announcements.is_empty() {
|
||||
|
@ -287,15 +274,51 @@ impl<'a> ProcessMention<'a> {
|
|||
apply_trailing_hashtag_pleroma_bug_workaround(&mut msg);
|
||||
}
|
||||
|
||||
let msg = format!("**📢 Group announcement**\n{msg}", msg = msg);
|
||||
self.send_announcement_multipart(&msg).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_reply_multipart(&self, mention : String, msg : String) -> Result<(), GroupError> {
|
||||
let parts = smart_split(&msg, Some(mention), self.config.get_character_limit());
|
||||
for p in parts {
|
||||
if let Ok(post) = StatusBuilder::new()
|
||||
.status(p)
|
||||
.content_type(if self.want_markdown {
|
||||
"text/markdown"
|
||||
} else {
|
||||
"text/plain"
|
||||
})
|
||||
.visibility(Visibility::Direct)
|
||||
.build()
|
||||
{
|
||||
self.client.new_status(post).await?;
|
||||
}
|
||||
|
||||
// Sleep a bit to avoid throttling
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_announcement_multipart(&self, msg : &str) -> Result<(), GroupError> {
|
||||
let parts = smart_split(msg, None, self.config.get_character_limit());
|
||||
|
||||
for p in parts {
|
||||
let post = StatusBuilder::new()
|
||||
.status(format!("**📢 Group announcement**\n{msg}", msg = msg))
|
||||
.status(p)
|
||||
.content_type("text/markdown")
|
||||
.visibility(Visibility::Public)
|
||||
.build()
|
||||
.expect("error build status");
|
||||
|
||||
let _ = self.client.new_status(post)
|
||||
.await.log_error("Failed to post");
|
||||
self.client.new_status(post).await?;
|
||||
|
||||
// Sleep a bit to avoid throttling
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -772,3 +795,193 @@ fn apply_trailing_hashtag_pleroma_bug_workaround(msg: &mut String) {
|
|||
msg.push_str(" .");
|
||||
}
|
||||
}
|
||||
|
||||
fn smart_split(msg : &str, prefix: Option<String>, limit: usize) -> Vec<String> {
|
||||
let prefix = prefix.unwrap_or_default();
|
||||
|
||||
if msg.len() + prefix.len() < limit {
|
||||
return vec![format!("{}{}", prefix, msg)];
|
||||
}
|
||||
|
||||
let mut parts_to_send = vec![];
|
||||
let mut this_piece = prefix.clone();
|
||||
for l in msg.split("\n") {
|
||||
println!("* Line: {:?}", l);
|
||||
if this_piece.len() + l.len() == limit {
|
||||
println!("exactly fits within limit");
|
||||
// this line exactly reaches the limit
|
||||
this_piece.push_str(l);
|
||||
parts_to_send.push(std::mem::take(&mut this_piece).trim().to_owned());
|
||||
this_piece.push_str(&prefix);
|
||||
} else if this_piece.len() + l.len() > limit {
|
||||
println!("too long to append (already {} + new {})", this_piece.len(), l.len());
|
||||
// line too long to append
|
||||
if this_piece != prefix {
|
||||
let trimmed = this_piece.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!("flush buffer: {:?}", trimmed);
|
||||
parts_to_send.push(trimmed.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
// start new piece with the line. If the line is too long, break it up.
|
||||
this_piece = format!("{}{}", prefix, l);
|
||||
|
||||
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(' ') {
|
||||
println!("line split at word boundary");
|
||||
let mut p = this_piece.split_off(last_space + 1);
|
||||
std::mem::swap(&mut p, &mut this_piece);
|
||||
p
|
||||
} else {
|
||||
println!("line split at exact len (no word boundary found)");
|
||||
let mut p = this_piece.split_off(limit);
|
||||
std::mem::swap(&mut p, &mut this_piece);
|
||||
p
|
||||
};
|
||||
let part_trimmed = to_send.trim();
|
||||
println!("flush buffer: {:?}", part_trimmed);
|
||||
parts_to_send.push(part_trimmed.to_owned());
|
||||
this_piece = format!("{}{}", prefix, this_piece.trim());
|
||||
}
|
||||
this_piece.push('\n');
|
||||
} else {
|
||||
println!("append line");
|
||||
// this line still fits comfortably
|
||||
this_piece.push_str(l);
|
||||
this_piece.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if this_piece != prefix {
|
||||
let leftover_trimmed = this_piece.trim();
|
||||
if !leftover_trimmed.is_empty() {
|
||||
println!("flush buffer: {:?}", leftover_trimmed);
|
||||
parts_to_send.push(leftover_trimmed.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
parts_to_send
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_smart_split_lines() {
|
||||
let to_split = "a234567890\nb234567890\nc234567890\nd234\n67890\ne234567890\n";
|
||||
|
||||
let parts = super::smart_split(to_split, None, 10);
|
||||
assert_eq!(vec![
|
||||
"a234567890".to_string(),
|
||||
"b234567890".to_string(),
|
||||
"c234567890".to_string(),
|
||||
"d234\n67890".to_string(),
|
||||
"e234567890".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_nosplit() {
|
||||
let to_split = "foo\nbar\nbaz";
|
||||
|
||||
let parts = super::smart_split(to_split, None, 1000);
|
||||
assert_eq!(vec![
|
||||
"foo\nbar\nbaz".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_nosplit_prefix() {
|
||||
let to_split = "foo\nbar\nbaz";
|
||||
let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 1000);
|
||||
assert_eq!(vec![
|
||||
"PREFIXfoo\nbar\nbaz".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_prefix_each() {
|
||||
let to_split = "1234\n56\n7";
|
||||
let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 10);
|
||||
assert_eq!(vec![
|
||||
"PREFIX1234".to_string(),
|
||||
"PREFIX56\n7".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_words() {
|
||||
let to_split = "one two three four five six seven eight nine ten";
|
||||
let parts = super::smart_split(to_split, None, 10);
|
||||
assert_eq!(vec![
|
||||
"one two".to_string(),
|
||||
"three four".to_string(),
|
||||
"five six".to_string(),
|
||||
"seven".to_string(),
|
||||
"eight nine".to_string(),
|
||||
"ten".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_words_multispace() {
|
||||
let to_split = "one two three four five six seven eight nine ten ";
|
||||
let parts = super::smart_split(to_split, None, 10);
|
||||
assert_eq!(vec![
|
||||
"one two".to_string(),
|
||||
"three four".to_string(),
|
||||
"five six".to_string(),
|
||||
"seven".to_string(),
|
||||
"eight nine".to_string(),
|
||||
"ten".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_words_longword() {
|
||||
let to_split = "one two threefourfive six";
|
||||
let parts = super::smart_split(to_split, None, 10);
|
||||
assert_eq!(vec![
|
||||
"one two".to_string(),
|
||||
"threefourf".to_string(),
|
||||
"ive six".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_words_prefix() {
|
||||
let to_split = "one two three four five six seven eight nine ten";
|
||||
let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 15);
|
||||
assert_eq!(vec![
|
||||
"PREFIXone two".to_string(),
|
||||
"PREFIXthree".to_string(),
|
||||
"PREFIXfour five".to_string(),
|
||||
"PREFIXsix seven".to_string(),
|
||||
"PREFIXeight".to_string(),
|
||||
"PREFIXnine ten".to_string(),
|
||||
], parts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_split_realistic() {
|
||||
let to_split = "\
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\
|
||||
Aenean venenatis libero ac ex suscipit, nec efficitur arcu convallis.\n\
|
||||
Nulla ante neque, efficitur nec fermentum a, fermentum nec nisl.\n\
|
||||
Sed dolor ex, vestibulum at malesuada ut, faucibus ac ante.\n\
|
||||
Nullam scelerisque magna dui, id tempor purus faucibus sit amet.\n\
|
||||
Curabitur pretium condimentum pharetra.\n\
|
||||
Aenean dictum, tortor et ultrices fermentum, mauris erat vehicula lectus.\n\
|
||||
Nec varius mauris sem sollicitudin dolor. Nunc porta in urna nec vulputate.";
|
||||
let parts = super::smart_split(to_split, Some("@pepa@pig.club ".to_string()), 140);
|
||||
assert_eq!(vec![
|
||||
"@pepa@pig.club Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
|
||||
"@pepa@pig.club Aenean venenatis libero ac ex suscipit, nec efficitur arcu convallis.".to_string(),
|
||||
"@pepa@pig.club Nulla ante neque, efficitur nec fermentum a, fermentum nec nisl.\nSed dolor ex, vestibulum at malesuada ut, faucibus ac ante.".to_string(),
|
||||
"@pepa@pig.club Nullam scelerisque magna dui, id tempor purus faucibus sit amet.\nCurabitur pretium condimentum pharetra.".to_string(),
|
||||
"@pepa@pig.club Aenean dictum, tortor et ultrices fermentum, mauris erat vehicula lectus.".to_string(),
|
||||
"@pepa@pig.club Nec varius mauris sem sollicitudin dolor. Nunc porta in urna nec vulputate.".to_string(),
|
||||
], parts);
|
||||
}
|
||||
}
|
|
@ -33,6 +33,8 @@ pub(crate) struct GroupConfig {
|
|||
acct: String,
|
||||
/// elefren data
|
||||
appdata: AppData,
|
||||
/// Server's character limit
|
||||
character_limit: usize,
|
||||
/// Hashtags the group will auto-boost from it's members
|
||||
group_tags: HashSet<String>,
|
||||
/// List of admin account "acct" names, e.g. piggo@piggo.space
|
||||
|
@ -67,6 +69,7 @@ impl Default for GroupConfig {
|
|||
redirect: Default::default(),
|
||||
token: Default::default(),
|
||||
},
|
||||
character_limit: 5000,
|
||||
group_tags: Default::default(),
|
||||
admin_users: Default::default(),
|
||||
member_users: Default::default(),
|
||||
|
@ -90,6 +93,10 @@ impl GroupConfig {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_character_limit(&self) -> usize {
|
||||
self.character_limit
|
||||
}
|
||||
|
||||
pub(crate) fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
|
|
@ -232,19 +232,3 @@ fn make_scopes() -> Scopes {
|
|||
| Scopes::write(scopes::Write::Media)
|
||||
| Scopes::write(scopes::Write::Follows)
|
||||
}
|
||||
|
||||
// trait TapOk<T> {
|
||||
// fn tap_ok<F: FnOnce(&T)>(self, f: F) -> Self;
|
||||
// }
|
||||
//
|
||||
// impl<T, E> TapOk<T> for Result<T, E> {
|
||||
// fn tap_ok<F: FnOnce(&T)>(self, f: F) -> Self {
|
||||
// match self {
|
||||
// Ok(v) => {
|
||||
// f(&v);
|
||||
// Ok(v)
|
||||
// }
|
||||
// Err(e) => Err(e)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
Loading…
Reference in a new issue