wx-bot-sdk 0.1.3

Standalone Weixin Bot SDK in Rust
Documentation
use base64::{Engine as _, engine::general_purpose::STANDARD};
use std::path::PathBuf;

use crate::{
    api::{MessageItem, MessageItemType},
    cdn::{download_and_decrypt_buffer, download_plain_cdn_buffer},
    messaging::send::WeixinInboundMediaOpts,
};

use super::get_mime_from_filename;

pub async fn download_media_from_item_to_file(
    item: &MessageItem,
    cdn_base_url: &str,
    dest_path_for: impl Fn(&str) -> PathBuf,
) -> crate::Result<WeixinInboundMediaOpts> {
    let mut result = WeixinInboundMediaOpts::default();
    match item.item_type {
        Some(x) if x == MessageItemType::Image as i32 => {
            let Some(img) = &item.image_item else {
                return Ok(result);
            };
            let Some(media) = &img.media else {
                return Ok(result);
            };
            if media.encrypt_query_param.is_none() && media.full_url.is_none() {
                return Ok(result);
            }
            let aes_key = if let Some(hex_key) = &img.aeskey {
                Some(STANDARD.encode(hex::decode(hex_key)?))
            } else {
                media.aes_key.clone()
            };
            let buf = if let Some(key) = aes_key {
                download_and_decrypt_buffer(
                    media.encrypt_query_param.as_deref().unwrap_or(""),
                    &key,
                    cdn_base_url,
                    "inbound image",
                    media.full_url.as_deref(),
                )
                .await?
            } else {
                download_plain_cdn_buffer(
                    media.encrypt_query_param.as_deref().unwrap_or(""),
                    cdn_base_url,
                    "inbound image-plain",
                    media.full_url.as_deref(),
                )
                .await?
            };
            let path = dest_path_for("image/jpeg");
            tokio::fs::write(&path, buf).await?;
            result.decrypted_pic_path = Some(path.to_string_lossy().to_string());
        }
        Some(x) if x == MessageItemType::Voice as i32 => {
            let Some(voice) = &item.voice_item else {
                return Ok(result);
            };
            let Some(media) = &voice.media else {
                return Ok(result);
            };
            let Some(key) = &media.aes_key else {
                return Ok(result);
            };
            let buf = download_and_decrypt_buffer(
                media.encrypt_query_param.as_deref().unwrap_or(""),
                key,
                cdn_base_url,
                "inbound voice",
                media.full_url.as_deref(),
            )
            .await?;
            let path = dest_path_for("audio/silk");
            tokio::fs::write(&path, buf).await?;
            result.decrypted_voice_path = Some(path.to_string_lossy().to_string());
            result.voice_media_type = Some("audio/silk".into());
        }
        Some(x) if x == MessageItemType::File as i32 => {
            let Some(file) = &item.file_item else {
                return Ok(result);
            };
            let Some(media) = &file.media else {
                return Ok(result);
            };
            let Some(key) = &media.aes_key else {
                return Ok(result);
            };
            let buf = download_and_decrypt_buffer(
                media.encrypt_query_param.as_deref().unwrap_or(""),
                key,
                cdn_base_url,
                "inbound file",
                media.full_url.as_deref(),
            )
            .await?;
            let mime = get_mime_from_filename(file.file_name.as_deref().unwrap_or("file.bin"));
            let path = dest_path_for(&mime);
            tokio::fs::write(&path, buf).await?;
            result.decrypted_file_path = Some(path.to_string_lossy().to_string());
            result.file_media_type = Some(mime);
        }
        Some(x) if x == MessageItemType::Video as i32 => {
            let Some(video) = &item.video_item else {
                return Ok(result);
            };
            let Some(media) = &video.media else {
                return Ok(result);
            };
            let Some(key) = &media.aes_key else {
                return Ok(result);
            };
            let buf = download_and_decrypt_buffer(
                media.encrypt_query_param.as_deref().unwrap_or(""),
                key,
                cdn_base_url,
                "inbound video",
                media.full_url.as_deref(),
            )
            .await?;
            let path = dest_path_for("video/mp4");
            tokio::fs::write(&path, buf).await?;
            result.decrypted_video_path = Some(path.to_string_lossy().to_string());
        }
        _ => {}
    }
    Ok(result)
}