Skip to content

Commit 98f379a

Browse files
committed
track reposts and add repost count
1 parent 79701cb commit 98f379a

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::pass::Pass;
77
pub struct Config {
88
pub token: String,
99
pub reply_cache_size: usize,
10+
pub seen_cache_size: Option<usize>,
1011
#[serde(default)]
1112
pub ignored_users: Vec<Id<UserMarker>>,
1213
#[serde(default)]

src/main.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::future::IntoFuture;
33
use std::sync::{Arc, RwLock};
44
use std::time::Duration;
55

6+
use anyhow::Ok;
7+
use cache::SeenCache;
8+
use repost::add_repost_counts;
69
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt as _};
710
use twilight_http::Client;
811
use twilight_model::channel::message::{AllowedMentions, MessageFlags};
@@ -18,11 +21,13 @@ use crate::{cache::ReplyCache, config::Config};
1821
mod cache;
1922
mod config;
2023
mod pass;
24+
mod repost;
2125

22-
struct State {
26+
pub struct State {
2327
config: Config,
2428
rest: Client,
2529
replies: RwLock<ReplyCache>,
30+
seen: RwLock<SeenCache>,
2631
}
2732

2833
#[tokio::main]
@@ -38,8 +43,12 @@ async fn main() -> Result<(), anyhow::Error> {
3843
Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT,
3944
);
4045

46+
// Use config size if it exists, otherwise default to reply cache size
47+
let seen_size = config.seen_cache_size.unwrap_or(config.reply_cache_size);
48+
4149
let state = Arc::new(State {
4250
replies: RwLock::new(ReplyCache::with_capacity(config.reply_cache_size)),
51+
seen: RwLock::new(SeenCache::with_capacity(seen_size)),
4352
config,
4453
rest,
4554
});
@@ -118,12 +127,23 @@ async fn dispatch_event(state: Arc<State>, event: Event) -> Result<(), anyhow::E
118127
.await?;
119128

120129
state.replies.write().unwrap().insert(token, reply.id);
130+
if let Some(new_content) = add_repost_counts(&state, reply.id, &reply.content) {
131+
state.rest
132+
.update_message(reply.channel_id, reply.id)
133+
.content(Some(new_content).as_deref())
134+
.allowed_mentions(Some(&AllowedMentions::default()))
135+
.await?;
136+
}
121137
}
122138
}
123139
}
124140

125141
// UPDATE: Edit our reply when someone edits a link in/out
126142
Event::MessageUpdate(message) => {
143+
// Ignore non-content edits like removing embeds
144+
if message.edited_timestamp == None {
145+
return Ok(());
146+
}
127147
let entry = state.replies.read().unwrap().get_entry(message.id);
128148
let Some(entry) = entry else {
129149
return Ok(());
@@ -143,11 +163,12 @@ async fn dispatch_event(state: Arc<State>, event: Event) -> Result<(), anyhow::E
143163
if let CacheEntry::Filled(reply_id) = entry {
144164
if !message.content.is_empty() {
145165
if let Some(content) = Pass::apply_all(&state.config.passes, &message.content) {
166+
let content = add_repost_counts(&state, reply_id, &content);
146167
state
147168
.rest
148169
.update_message(message.channel_id, reply_id)
149170
.allowed_mentions(Some(&AllowedMentions::default()))
150-
.content(Some(&content))
171+
.content(content.as_deref())
151172
.await?;
152173
} else {
153174
state

src/repost.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::sync::Arc;
2+
3+
use regex::{Captures, Regex};
4+
use twilight_model::id::{marker::MessageMarker, Id};
5+
6+
use crate::State;
7+
8+
/// Pattern that matches urls which have been transformed by [pass]
9+
const URL_REGEX: &str = "\\[`\\w+`\\]\\((?P<url>.+)\\)";
10+
11+
/// Counts the number of times that a url has been seen
12+
fn check_repost(state: &Arc<State>, embed_url: &str) -> usize {
13+
let existing_posts = state.seen.read().unwrap().search_by_value(embed_url);
14+
existing_posts.len()
15+
}
16+
17+
/// Takes a string and inserts the number of times it has been seen
18+
pub fn add_repost_counts(state: &Arc<State>, reply_id: Id<MessageMarker>, content: &str) -> Option<String> {
19+
let seen = state.seen.read().unwrap().get_entry(reply_id);
20+
if let Some(_seen) = seen {
21+
return Some(content.to_owned());
22+
}
23+
let mut new_content = content.to_owned();
24+
for url in find_urls(content) {
25+
let times = check_repost(state, url);
26+
let token = state.seen.write().unwrap().file_pending(reply_id);
27+
if let Some(token) = token {
28+
if times == 0 {
29+
state.seen.write().unwrap().insert(token, url.to_owned());
30+
continue;
31+
}
32+
new_content = add_repost_count(content, times);
33+
state.seen.write().unwrap().insert(token, url.to_owned());
34+
}
35+
}
36+
if new_content == content {
37+
return None
38+
}
39+
Some(new_content)
40+
}
41+
42+
/// Returns a vector of URLs that exist in a string
43+
fn find_urls(content: &str) -> Vec<&str> {
44+
Regex::new(URL_REGEX)
45+
.unwrap()
46+
.captures_iter(content)
47+
.map(|e| e.name("url").unwrap().as_str())
48+
.collect()
49+
}
50+
51+
/// Adds the number of times a link has been reposted to a string
52+
fn add_repost_count(content: &str, repost_count: usize) -> String {
53+
let regex = Regex::new(URL_REGEX).unwrap();
54+
regex.replace(content, |caps: &Captures|
55+
format!("{} Posted {} time(s) ", &caps[0], repost_count)
56+
).into_owned()
57+
}

0 commit comments

Comments
 (0)