char/phoebe

git clone https://git.t4t.associates/char/phoebe

videogame hackerRefactor: Extract discord attachment creation into its own function602a042

main
6.9 KiB235 linesraw
1use phoebe::{
2    attachments::attachment_to_url,
3    mid_chat::{ChatAttachment, ChatMessage, ChatMessageEdit, ChatMessageReference, ChatReference},
4    prelude::*,
5};
6
7use serenity::{http::AttachmentType, model::prelude::*, prelude::*};
8
9use crate::{chat_conv, discord_reference, DiscordService};
10
11async fn get_or_create_webhook_for_channel(
12    discord: &mut DiscordService,
13    channel: &ChannelId,
14) -> Option<Webhook> {
15    if let Ok(webhooks) = channel.webhooks(&discord.ctx).await {
16        for webhook in webhooks {
17            if matches!(webhook.name.as_deref(), Some("Phoebe")) {
18                return Some(webhook);
19            }
20        }
21    }
22
23    if let Ok(webhook) = channel.create_webhook(&discord.ctx, "Phoebe").await {
24        return Some(webhook);
25    }
26
27    None
28}
29
30async fn create_webhook_reply_embeds(
31    discord_ctx: &Context,
32    channel_id: ChannelId,
33    message_id: MessageId,
34) -> Vec<serde_json::Value> {
35    if let Ok(replied_message) = channel_id.message(discord_ctx, message_id).await {
36        let replied_author_name = format!(
37            "{} ↩️",
38            replied_message
39                .author_nick(discord_ctx)
40                .await
41                .as_ref()
42                .unwrap_or(&replied_message.author.name)
43        );
44
45        let reply_description = format!(
46            "**[Reply to:]({})**\n{}",
47            replied_message.id.link(
48                channel_id,
49                discord_ctx
50                    .cache
51                    .guild_channel(channel_id)
52                    .await
53                    .map(|gc| gc.guild_id)
54            ),
55            &replied_message.content
56        );
57
58        return vec![Embed::fake(|e| {
59            e.author(|a| {
60                a.icon_url(
61                    &replied_message
62                        .author
63                        .static_avatar_url()
64                        .unwrap_or_else(|| replied_message.author.default_avatar_url()),
65                )
66                .name(replied_author_name)
67            })
68            .description(reply_description)
69        })];
70    }
71
72    vec![]
73}
74
75async fn create_discord_attachments(source: &'_ ChatMessage) -> Vec<AttachmentType<'_>> {
76    source
77        .attachments
78        .iter()
79        .map(|a| match a {
80            ChatAttachment::Online {
81                url,
82                media_type: Some(media_type),
83            } => {
84                if media_type.starts_with("image/") {
85                    AttachmentType::Image(url)
86                } else {
87                    todo!("Handle non-image online attachment")
88                }
89            }
90            ChatAttachment::Online { .. } => {
91                todo!("Handle online attachment with no media_type")
92            }
93            ChatAttachment::InMemory {
94                file_name, data, ..
95            } => AttachmentType::Bytes {
96                filename: file_name.clone(),
97                data: data.into(),
98            },
99        })
100        .collect()
101}
102
103pub async fn send_discord_message(
104    discord: &mut DiscordService,
105    source: &ChatMessage,
106    destination_channel: ChatReference,
107) -> Result<ChatMessageReference> {
108    let channel_id = destination_channel.id.parse::<ChannelId>()?;
109
110    let discord_reply = if let Some(reply) = &source.replying {
111        if let Some(reply_ref) = discord
112            .lookup_message(reply, |r| future::ready(r.channel == destination_channel))
113            .await
114        {
115            assert_eq!(reply_ref.channel.service, "discord");
116            let channel_id: ChannelId = reply_ref.channel.id.parse().unwrap();
117            let message_id: MessageId = reply_ref.message_id.parse::<u64>().unwrap().into();
118            Some((channel_id, message_id))
119        } else {
120            None
121        }
122    } else {
123        None
124    };
125
126    let files = create_discord_attachments(source).await;
127
128    if let Some(webhook) = get_or_create_webhook_for_channel(&mut *discord, &channel_id).await {
129        let reply_embeds = if let Some((channel, message)) = discord_reply {
130            create_webhook_reply_embeds(&discord.ctx, channel, message).await
131        } else {
132            vec![]
133        };
134
135        let avatar_url = attachment_to_url(&source.author.avatar).await;
136
137        if let Some(sent_message) = webhook
138            .execute(&discord.ctx, true, |w| {
139                w.content(chat_conv::format(&source.content))
140                    .username(format!(
141                        "{} ({})",
142                        &source.author.display_name, &source.author.reference.service
143                    ))
144                    .avatar_url(&avatar_url)
145                    .embeds(reply_embeds)
146                    .add_files(files.clone())
147            })
148            .await?
149        {
150            return Ok(ChatMessageReference::new(
151                discord_reference(sent_message.channel_id),
152                sent_message.id,
153            ));
154        }
155    }
156
157    let content = format!(
158        "{} ({}): {}",
159        source.author.display_name,
160        source.author.reference.service,
161        chat_conv::format(&source.content)
162    );
163
164    let sent_message = channel_id
165        .send_message(&discord.ctx, move |m| {
166            let m = m.content(content);
167            let m = m.add_files(files);
168            if let Some(reply) = discord_reply {
169                m.reference_message(reply)
170            } else {
171                m
172            }
173        })
174        .await?;
175
176    Ok(ChatMessageReference::new(
177        discord_reference(sent_message.channel_id),
178        sent_message.id,
179    ))
180}
181
182pub async fn delete_discord_message(
183    discord: &mut DiscordService,
184    message: &ChatMessageReference,
185) -> Result<()> {
186    let channel_id = message.channel.id.parse::<ChannelId>()?;
187    let message_id: MessageId = message.message_id.parse::<u64>()?.into();
188
189    if let Some(webhook) = get_or_create_webhook_for_channel(&mut *discord, &channel_id).await {
190        if webhook
191            .delete_message(&discord.ctx, message_id)
192            .await
193            .is_ok()
194        {
195            return Ok(());
196        }
197    }
198
199    channel_id.delete_message(&discord.ctx, message_id).await?;
200    Ok(())
201}
202
203pub async fn edit_discord_message(
204    discord: &mut DiscordService,
205    prev_origin: &ChatMessageReference,
206    edit: &ChatMessageEdit,
207) -> Result<()> {
208    let channel_id = prev_origin.channel.id.parse::<ChannelId>()?;
209    let message_id: MessageId = prev_origin.message_id.parse::<u64>()?.into();
210
211    if let Some(webhook) = get_or_create_webhook_for_channel(&mut *discord, &channel_id).await {
212        if webhook
213            .edit_message(&discord.ctx, message_id, |w| {
214                w.content(chat_conv::format(&edit.new_content))
215            })
216            .await
217            .is_ok()
218        {
219            return Ok(());
220        }
221    }
222
223    let content = format!(
224        "{} ({}): {}",
225        edit.author.display_name,
226        edit.author.reference.service,
227        chat_conv::format(&edit.new_content)
228    );
229
230    channel_id
231        .edit_message(&discord.ctx, message_id, |m| m.content(content))
232        .await?;
233
234    Ok(())
235}