char/phoebe
git clone https://git.t4t.associates/char/phoebe
0649ea0
legacy
1use log:: info; 2use serenity::{ async_trait, http:: AttachmentType , model:: prelude:: * , prelude:: * }; 3use tokio:: sync:: mpsc; 4 5use crate ::{ 6 channels:: ChannelReference , 7 message_ast::{ self , format_discord}, 8 messages::{ 9DeletedMessage , EditedMessage , MessageAuthor , MessageEvent , MessageReference , 10SentImageMessage , SentMessage , 11}, 12}; 13 14pub use serenity:: client:: Context ; 15pub use serenity:: model:: id::{ ChannelId , MessageId }; 16 17impl From < & Message > for MessageReference { 18fn from ( message : & Message ) ->Self { 19Self :: Discord ( message. channel_id . 0 , message. id . 0 ) 20} 21} 22 23struct DiscordHandler { 24ctx_tx : mpsc:: UnboundedSender < Context >, 25event_tx : mpsc:: UnboundedSender < MessageEvent >, 26} 27 28async fn get_message_author ( ctx : & Context , message : & Message ) ->MessageAuthor { 29async fn user_tag_color ( ctx : & Context , message : & Message ) ->Option < String > { 30Some ( 31 message 32. member ( ctx) 33. await 34. ok () ? 35. colour ( ctx) 36. await ? 37. hex () 38. to_ascii_lowercase (), 39) 40} 41 42MessageAuthor { 43display_name : message 44. author_nick ( ctx) 45. await 46. unwrap_or_else ( || message. author . name . clone ()), 47avatar_url : message 48. author 49. static_avatar_url () 50. unwrap_or_else ( || message. author . default_avatar_url ()), 51service_name : "discord" . to_string (), 52display_color : user_tag_color ( ctx, message). await , 53} 54} 55 56# [ async_trait ] 57impl EventHandler for DiscordHandler { 58async fn ready ( & self , ctx : Context , _ready : Ready ) { 59let _ =self . ctx_tx . send ( ctx); 60info! ( "Discord ready!" ); 61} 62 63async fn message ( & self , ctx : Context , message : Message ) { 64// TODO: Replace with proper management system for linking channels together 65if let Some ( target) = message. content . strip_prefix ( "phoebe!link " ) { 66if message 67. member ( & ctx) 68. await 69. unwrap () 70. roles ( & ctx) 71. await 72. unwrap () 73. iter () 74. any ( |r| r. name =="Phoebe" ) 75{ 76let _ =self . event_tx . send ( MessageEvent :: AdminLinkChannels ( vec! [ 77ChannelReference :: Discord ( message. channel_id . 0 ), 78ChannelReference :: Matrix ( target. to_string ()), 79])); 80 81 message. reply ( & ctx, "Linking with matrix." ). await . unwrap (); 82return ; 83} 84} 85 86let message_ref =MessageReference :: from ( & message); 87 88let content = discord_message_format:: parse ( & message. content ); 89let content = message_ast:: convert_discord ( & content); 90 91let replies_to = message 92. referenced_message 93. as_ref () 94. map ( |m|MessageReference :: from ( m. as_ref ())); 95 96let _ =self 97. event_tx 98. send ( MessageEvent :: SendText ( Box :: new ( SentMessage { 99source : message_ref, 100 content, 101author : get_message_author ( & ctx, & message). await , 102 replies_to, 103}))); 104 105for attachmentin message. attachments . iter () { 106let _ =self 107. event_tx 108. send ( MessageEvent :: SendImage ( Box :: new ( SentImageMessage { 109source : MessageReference :: from ( & message), 110author : get_message_author ( & ctx, & message). await , 111image_url : attachment. proxy_url . clone (), 112}))); 113} 114} 115 116async fn message_update ( 117& self , 118ctx : Context , 119_old_if_available : Option < Message >, 120new : Option < Message >, 121event : MessageUpdateEvent , 122) { 123if let Ok ( new_message) ={ 124if let Some ( m) = new{ 125Ok ( m) 126} else { 127 event. channel_id . message ( & ctx, event. id ). await 128} 129} { 130let message_ref =MessageReference :: from ( & new_message); 131 132let content = discord_message_format:: parse ( & new_message. content ); 133let content = message_ast:: convert_discord ( & content); 134 135let _ =self 136. event_tx 137. send ( MessageEvent :: EditText ( Box :: new ( EditedMessage { 138replacing : message_ref, 139 content, 140author : get_message_author ( & ctx, & new_message). await , 141}))); 142} 143} 144 145async fn message_delete ( 146& self , 147_ctx : Context , 148channel_id : ChannelId , 149deleted_message_id : MessageId , 150_guild_id : Option < GuildId >, 151) { 152let message_ref =MessageReference :: Discord ( channel_id. 0 , deleted_message_id. 0 ); 153let _ =self 154. event_tx 155. send ( MessageEvent :: Delete ( Box :: new ( DeletedMessage { 156reference : message_ref, 157}))); 158} 159} 160 161async fn get_webhook_for_channel ( discord_ctx : & Context , channel : & ChannelId ) ->Option < Webhook > { 162if let Ok ( webhooks) = channel. webhooks ( discord_ctx). await { 163for webhookin webhooks{ 164if matches! ( webhook. name . as_deref (), Some ( "phoebe" )) { 165return Some ( webhook); 166} 167} 168} 169 170None 171} 172 173async fn get_or_create_webhook_for_channel ( 174discord_ctx : & Context , 175channel : & ChannelId , 176) ->Option < Webhook > { 177if let Some ( webhook) =get_webhook_for_channel ( discord_ctx, channel). await { 178return Some ( webhook); 179} 180 181if let Ok ( webhook) = channel. create_webhook ( discord_ctx, "phoebe" ). await { 182return Some ( webhook); 183} 184 185None 186} 187 188async fn create_webhook_reply_embeds ( 189discord_ctx : & Context , 190reply : Option <( u64 , u64 )>, 191) ->Vec < serde_json:: Value > { 192if let Some (( channel_id, message_id)) = reply{ 193if let Ok ( replied_message) =ChannelId ( channel_id) 194. message ( discord_ctx, MessageId ( message_id)) 195. await 196{ 197let replied_author_name =format! ( 198"{} ↩️" , 199 replied_message 200. author_nick ( discord_ctx) 201. await 202. as_ref () 203. unwrap_or ( & replied_message. author . name ) 204); 205 206let reply_description =format! ( 207"**[Reply to:]({})**\n{}" , 208 replied_message. id . link ( 209ChannelId ( channel_id), 210 discord_ctx 211. cache 212. guild_channel ( channel_id) 213. await 214. map ( |gc| gc. guild_id ) 215), 216& replied_message. content 217); 218 219return vec! [ Embed :: fake ( |e|{ 220 e. author ( |a|{ 221 a. icon_url ( 222& replied_message 223. author 224. static_avatar_url () 225. unwrap_or_else ( || replied_message. author . default_avatar_url ()), 226) 227. name ( replied_author_name) 228}) 229. description ( reply_description) 230})]; 231} 232} 233 234vec! [] 235} 236 237pub async fn forward_to_discord ( 238discord_ctx : & Context , 239channel : ChannelId , 240message : & SentMessage , 241reply : Option <( u64 , u64 )>, 242) ->Option < MessageReference > { 243if let Some ( webhook) =get_or_create_webhook_for_channel ( discord_ctx, & channel). await { 244let reply_embeds =create_webhook_reply_embeds ( discord_ctx, reply). await ; 245 246return webhook 247. execute ( discord_ctx, true , |w|{ 248 w. content ( format_discord ( & message. content )) 249. username ( format! ( 250"{} ({})" , 251& message. author . display_name , & message. author . service_name 252)) 253. avatar_url ( & message. author . avatar_url ) 254. embeds ( reply_embeds) 255}) 256. await 257. ok () 258. flatten () 259. as_ref () 260. map ( MessageReference :: from); 261} 262 263 channel 264. send_message ( discord_ctx, |m|{ 265let content =format! ( 266"{} ({}): {}" , 267& message. author . display_name , 268& message. author . service_name , 269format_discord ( & message. content ) 270); 271 272if let Some (( channel_id, message_id)) = reply{ 273 m. content ( & content) 274. reference_message (( ChannelId ( channel_id), MessageId ( message_id))) 275} else { 276 m. content ( & content) 277} 278}) 279. await 280. as_ref () 281. ok () 282. map ( MessageReference :: from) 283} 284 285pub async fn edit_on_discord ( 286discord_ctx : & Context , 287channel_id : ChannelId , 288message_id : MessageId , 289message : & EditedMessage , 290) ->Option < MessageReference > { 291if let Some ( webhook) =get_or_create_webhook_for_channel ( discord_ctx, & channel_id). await { 292return webhook 293. edit_message ( discord_ctx, message_id, |w|{ 294 w. content ( format_discord ( & message. content )) 295}) 296. await 297. as_ref () 298. ok () 299. map ( MessageReference :: from); 300} 301 302 channel_id 303. edit_message ( & discord_ctx, & message_id, |m|{ 304 m. content ( format_discord ( & message. content )) 305}) 306. await 307. as_ref () 308. ok () 309. map ( MessageReference :: from) 310} 311 312pub async fn delete_on_discord ( 313discord_ctx : & Context , 314channel_id : ChannelId , 315message_id : MessageId , 316_message : & DeletedMessage , 317) ->bool { 318if let Some ( webhook) =get_or_create_webhook_for_channel ( discord_ctx, & channel_id). await { 319return webhook 320. delete_message ( discord_ctx, message_id) 321. await 322. is_ok (); 323} 324 325 channel_id 326. delete_message ( & discord_ctx, & message_id) 327. await 328. is_ok () 329} 330 331pub async fn forward_image_to_discord ( 332discord_ctx : & Context , 333channel_id : ChannelId , 334image : & SentImageMessage , 335) ->Option < MessageReference > { 336if let Some ( webhook) =get_or_create_webhook_for_channel ( discord_ctx, & channel_id). await { 337return webhook 338. execute ( discord_ctx, true , |w|{ 339 w. add_file ( AttachmentType :: Image ( & image. image_url )) 340. username ( format! ( 341"{} ({})" , 342& image. author . display_name , & image. author . service_name 343)) 344. avatar_url ( & image. author . avatar_url ) 345}) 346. await 347. ok () 348. flatten () 349. as_ref () 350. map ( MessageReference :: from); 351} 352 353 channel_id 354. send_message ( discord_ctx, |m|{ 355 m. add_file ( AttachmentType :: Image ( & image. image_url )) 356. content ( format! ( 357"{} ({}):" , 358& image. author . display_name , & image. author . service_name 359)) 360}) 361. await 362. as_ref () 363. ok () 364. map ( MessageReference :: from) 365} 366 367pub async fn create_discord_client ( 368ctx_tx : mpsc:: UnboundedSender < Context >, 369message_tx : mpsc:: UnboundedSender < MessageEvent >, 370token : & str , 371) ->Client { 372let handler =DiscordHandler { 373 ctx_tx, 374event_tx : message_tx, 375}; 376 377info! ( "Discord logging in…" ); 378let client =Client :: builder ( token) 379. event_handler ( handler) 380. await 381. expect ( "Failed to create discord client" ); 382info! ( "Discord starting…" ); 383 384 client 385}