tuwunel_admin/media/
delete_by_event.rs1use ruma::{CanonicalJsonObject, CanonicalJsonValue, Mxc, OwnedEventId};
2use tuwunel_core::{Err, Result, debug, err, info, warn};
3
4use crate::admin_command;
5
6#[admin_command]
7pub(super) async fn delete_by_event(&self, event_id: OwnedEventId) -> Result {
8 let event_json = self
9 .services
10 .timeline
11 .get_pdu_json(&event_id)
12 .await
13 .map_err(|_| err!("Event ID does not exist or is not known to us."))?;
14
15 let content = event_json
16 .get("content")
17 .and_then(CanonicalJsonValue::as_object)
18 .ok_or_else(|| {
19 err!(
20 "Event ID does not have a \"content\" key, this is not a message or an event \
21 type that contains media.",
22 )
23 })?;
24
25 let mut mxc_urls = Vec::with_capacity(3);
26 mxc_urls.extend(url_mxc_from_content(content));
27 mxc_urls.extend(thumbnail_mxc_from_content(content));
28 mxc_urls.extend(file_mxc_from_content(content));
29
30 if mxc_urls.is_empty() {
31 return Err!("Parsed event ID but found no MXC URLs.",);
32 }
33
34 let mut mxc_deletion_count: usize = 0;
35
36 for mxc_url in mxc_urls {
37 if !mxc_url.starts_with("mxc://") {
38 warn!("Ignoring non-mxc url {mxc_url}");
39 continue;
40 }
41
42 let mxc: Mxc<'_> = mxc_url.as_str().try_into()?;
43
44 match self.services.media.delete(&mxc).await {
45 | Ok(()) => {
46 info!("Successfully deleted {mxc_url} from filesystem and database");
47 mxc_deletion_count = mxc_deletion_count.saturating_add(1);
48 },
49 | Err(e) => {
50 warn!("Failed to delete {mxc_url}, ignoring error and skipping: {e}");
51 },
52 }
53 }
54
55 write!(
56 self,
57 "Deleted {mxc_deletion_count} total MXCs from our database and the filesystem from \
58 event ID {event_id}."
59 )
60 .await
61}
62
63fn url_mxc_from_content(content: &CanonicalJsonObject) -> Option<String> {
64 debug!("Attempting to go into \"url\" key for main media file");
65 let url = content
66 .get("url")
67 .and_then(CanonicalJsonValue::as_str)?;
68
69 debug!("Got main media URL: {url}");
70 Some(url.to_owned())
71}
72
73fn thumbnail_mxc_from_content(content: &CanonicalJsonObject) -> Option<String> {
74 debug!("Attempting to go into \"info\" key for thumbnails");
75 let thumbnail_url = content
76 .get("info")
77 .and_then(CanonicalJsonValue::as_object)
78 .and_then(|info| info.get("thumbnail_url"))
79 .and_then(CanonicalJsonValue::as_str)?;
80
81 debug!("Found a thumbnail_url in info key: {thumbnail_url}");
82 Some(thumbnail_url.to_owned())
83}
84
85fn file_mxc_from_content(content: &CanonicalJsonObject) -> Option<String> {
86 debug!("Attempting to go into \"file\" key");
87 let url = content
88 .get("file")
89 .and_then(CanonicalJsonValue::as_object)
90 .and_then(|file| file.get("url"))
91 .and_then(CanonicalJsonValue::as_str)?;
92
93 debug!("Found url in file key: {url}");
94 Some(url.to_owned())
95}