Thumbnail

steew/belltoll.git

Clone URL: https://git.buni.party/steew/belltoll.git

Viewing file on branch master

1use std::collections::HashMap;
2use std::collections::VecDeque;
3use std::sync::Arc;
4
5use irc::client::Sender;
6use serenity::all::Cache;
7use serenity::all::ChannelId;
8use serenity::all::ExecuteWebhook;
9use serenity::all::Http;
10use serenity::all::Webhook;
11use serenity::builder;
12use serenity::prelude::*;
13use sha1::{Sha1, Digest};
14use tokio::sync::Notify;
15
16#[derive(Debug)]
17pub enum RelayDirection {
18 INVALID,
19 IRC2DIS(String),
20 DIS2IRC(ChannelId),
21}
22
23pub enum MessageType {
24 Normal,
25 // discord reply, containing the u64 message id that it references, and the author name.
26 ReplyDiscord((String, String)),
27 // irc reply, containing the message base64 hash it references.
28 ReplyIrc(String)
29}
30
31pub struct RelayMessage {
32 pub contents: String,
33 pub direction: RelayDirection,
34 pub author: String,
35 pub message_type: MessageType
36}
37
38pub struct MessageBuffer {
39 pub pending_relay_messages: VecDeque<RelayMessage>,
40}
41
42pub struct RelayNotify {
43 pub notify: Notify,
44}
45
46impl Default for RelayNotify {
47 fn default() -> Self {
48 RelayNotify {
49 notify: Notify::new(),
50 }
51 }
52}
53
54pub struct RelayAssoc {
55 // stores the Discord - IRC channel bridge associations
56 pub bridge_assoc: HashMap<ChannelId, Vec<String>>,
57 // stores the webhook URL for the discord channels
58 pub chid_webhook_assoc: HashMap<ChannelId, String>,
59}
60
61impl RelayAssoc {
62 pub fn find_target(&self, source: RelayDirection) -> RelayDirection {
63 match source {
64 RelayDirection::IRC2DIS(chan) => {
65 for (chid, chan_vec) in self.bridge_assoc.iter() {
66 for c in chan_vec.iter() {
67 if c.eq_ignore_ascii_case(&chan) { return RelayDirection::DIS2IRC(*chid) };
68 }
69 }
70 return RelayDirection::INVALID;
71 },
72 RelayDirection::DIS2IRC(chan) => {
73 if let Some(target) = self.bridge_assoc.get(&chan) {
74 let irc_target = target.first().unwrap();
75 return RelayDirection::IRC2DIS(irc_target.clone());
76 } else { return RelayDirection::INVALID }
77 },
78 _ => { panic!("Invalid source of message! {source:?}") }
79 }
80 }
81}
82
83impl Default for RelayAssoc {
84 fn default() -> Self {
85 RelayAssoc {
86 bridge_assoc: HashMap::new(),
87 chid_webhook_assoc: HashMap::new()
88 }
89 }
90}
91
92impl Default for RelayMessage {
93 fn default() -> Self {
94 RelayMessage {
95 contents: String::new(),
96 direction: RelayDirection::INVALID,
97 author: String::new(),
98 message_type: MessageType::Normal
99 }
100 }
101}
102
103impl Default for MessageBuffer {
104 fn default() -> Self {
105 MessageBuffer {
106 pending_relay_messages: VecDeque::new(),
107 }
108 }
109}
110
111impl TypeMapKey for MessageBuffer {
112 type Value = Arc<RwLock<MessageBuffer>>;
113}
114
115impl TypeMapKey for RelayNotify {
116 type Value = Arc<RelayNotify>;
117}
118
119fn format_name(name: &str) -> String {
120 // XMPP RFC 3174 implementation
121 // get hash
122 let mut hasher = Sha1::new();
123 hasher.update(name);
124 let result = hasher.finalize();
125 // obtain the last 16 bits
126 let last16 = result.last_chunk::<2>().unwrap();
127 let last16_float = f32::from(((last16[0] as u16) << 8) | (last16[1] as u16));
128 let hue = last16_float * 360.0 / 65535.0;
129 let hue = hue.ceil();
130 let hue: u32 = hue.to_bits() % 87;
131 println!("{hue}");
132 // insert invisible character to prevent pings
133 let (split_name_first, split_name_rest) = name.split_at(1);
134
135 format!("\x03{hue:02}{split_name_first}{split_name_rest}\x03")
136}
137
138
139pub async fn relay_consumer(
140 buffer: Arc<RwLock<MessageBuffer>>,
141 notify: Arc<RelayNotify>,
142 http: Arc<Http>,
143 sender: Sender,
144 assoc: RelayAssoc,
145 avatars: HashMap<String, String>
146) {
147 loop {
148 // await for new relay pending events
149 notify.notify.notified().await;
150 let pending: RelayMessage;
151 {
152 let mut buffer_lock = buffer.write().await;
153 pending = buffer_lock.pending_relay_messages.pop_front().unwrap();
154 }
155 println!(
156 "Received message to relay: {}, {:?}",
157 pending.contents, pending.direction
158 );
159 match pending.direction {
160 RelayDirection::IRC2DIS(chan) => {
161 // let chanid = ChannelId::new(591954698664149044);
162 // chanid.say(http.clone(), pending.contents).await.unwrap();
163 let target = assoc.find_target(RelayDirection::IRC2DIS(chan));
164 match target {
165 RelayDirection::DIS2IRC(t) => {
166 let webhook = Webhook::from_url(&http, assoc.chid_webhook_assoc.get(&t).expect("Expected a webhook url for channel {t.get()}")).await.unwrap();
167 let builder: ExecuteWebhook;
168 // let guilds = http.get_guilds(None, None).await.unwrap();
169 let avatar_url: Option<String> = None;
170 // for guild in guilds {
171 // let members = http.get_guild_members(guild.id, None, None).await.unwrap();
172 // for member in members {
173 // println!("Member: {:?}", member.nick);
174 // let mut name = member.user.name.clone();
175 // if let Some(nick) = member.nick.clone() {
176 // name = nick;
177 // }
178 // if pending.author.eq_ignore_ascii_case(&name) {
179 // println!("Found match: {}", name);
180 // avatar_url = member.avatar_url;
181 // println!("Avatar URL: {:?}", avatar_url);
182 // break;
183 // }
184 // }
185 // }
186
187 if let Some(avatar_url) = avatar_url {
188 println!("{avatar_url}");
189 builder = ExecuteWebhook::new().content(pending.contents).username(pending.author).avatar_url(avatar_url);
190 } else {
191 builder = ExecuteWebhook::new().content(pending.contents).username(pending.author);
192 }
193
194 webhook.execute(&http, false, builder).await.expect("Could not execute webhook.");
195 },
196 _ => { panic!("Found no target to send the message to!") }
197 }
198 }
199 RelayDirection::DIS2IRC(chan) => {
200 let source_name = format_name(&pending.author);
201 let target = assoc.find_target(RelayDirection::DIS2IRC(chan));
202 match target {
203 RelayDirection::IRC2DIS(t) => {
204 let response;
205 let message_contents = pending.contents;
206 match pending.message_type {
207 MessageType::ReplyDiscord(tuple) => {
208 let target_reply_user = format_name(&tuple.1);
209 let origin_contents = tuple.0;
210 response = format!("<{source_name} replying to: {target_reply_user}> \"{origin_contents}\"\r\n\t{message_contents}");
211 },
212 MessageType::Normal => {
213 response = format!("<{source_name}> {message_contents}");
214 },
215 _ => {
216 // found invalid message type here!!
217 println!("Invalid reply type found.");
218 return;
219 }
220 }
221 sender.send_privmsg(t, response).unwrap();
222 },
223 _ => { println!("Found no target to send the message to!") }
224 }
225 }
226 _ => {}
227 }
228 }
229}
230
231