xmtv_api 0.2.2

一个封装好用来解析XMTV的API
Documentation
use anyhow::Result;
use indicatif::{ProgressBar, ProgressStyle};
use log::{error, info, warn};
use rand::Rng;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use url::Url;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoUrl {
    pub title: String,
    pub name: String,
    pub url: String,
    pub time: u128,
}

impl PartialEq for VideoUrl {
    fn eq(&self, other: &Self) -> bool {
        self.title == other.title && self.name == other.name && self.time == other.time
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Video {
    pub title: String,
    pub range: Vec<VideoUrl>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexPicture {
    host: String,
    dir: String,
    path: String,
    filepath: String,
    filename: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentUrls {
    pub www: String,
    pub h5: String,
    pub share: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoExtra {
    pub site_name: String,
    pub is_top: u8,
    pub is_hot: u8,
    pub is_slide: u8,
    pub is_headline: u8,
    pub status: u64,
    pub label_ids: Vec<u64>,
    pub order_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoInfo {
    pub id: u64,
    pub site_id: u64,
    pub module_id: String,
    pub bundle_id: String,
    pub r#type: String,
    pub title: String,
    pub content_id: u64,
    pub content_from_id: u64,
    pub detail_id: u64,
    pub create_time: u128,
    pub indexpic: IndexPicture,
    pub publish_time: u128,
    pub is_publish: u8,
    pub column_id: u64,
    pub main_column: u64,
    pub parents_column: Vec<String>,
    pub content_urls: ContentUrls,
    pub extra: VideoExtra,
    pub brief: Option<String>,
    pub source: String,
    pub outlink: String,
    pub publish_time_stamp: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchBody {
    pub total: usize,
    pub data: Vec<VideoInfo>,
}

pub async fn get_search_body() -> Result<SearchBody> {
    let url = Url::parse("https://mapi1.kxm.xmtv.cn/api/open/xiamen/web_search_list.php?count=10000&search_text=%E6%96%97%E9%98%B5%E6%9D%A5%E7%9C%8B%E6%88%8F&offset=0&bundle_id=livmedia&order_by=publish_time&time=0&with_count=1")?;
    info!("获取视频列表 url = {:?}", &url);

    let res = Client::new().get(url).send().await?;
    let body = res.json::<SearchBody>().await?;
    assert!(body.data.len() == body.total, "解析数量必须和请求数量相同");
    Ok(body)
}

pub async fn get() -> Result<Vec<VideoUrl>> {
    let body = get_search_body().await?;

    let mut ret: Vec<VideoUrl> = Vec::new();
    let data = body.data;
    info!("获取到视频列表 data = {:?}", &data);

    for ele in data {
        let name = ele.title;
        let position = match name.find("斗阵来看戏") {
            Some(ret) => ret,
            _ => name.len(),
        };
        let title = name[0..position]
            .replace('', "(")
            .split('(')
            .collect::<Vec<_>>()[0]
            .replace(' ', "");
        let url_into_share = ele.content_urls.share;
        let position = name.find("斗阵来看戏").unwrap_or(0) + "斗阵来看戏".len();
        let t: &str = &name[position..];
        let t = t.split(' ').collect::<Vec<_>>();
        let t = if t.len() >= 2 {
            t[1].replace(['.', '-'], "")
        } else {
            match url_into_share.find('-') {
                Some(_) => {
                    let t = url_into_share.split('/').collect::<Vec<_>>();
                    let t = t[4];
                    t.replace(['.', '-'], "")
                }
                _ => {
                    error!("存在一些无法识别的组别已经忽略,下面是一些信息或许有助于修复");
                    warn!("titile = {:?}", &title);
                    warn!("name = {:?}", &name);
                    warn!("url_into_share = {:?}", &url_into_share);
                    continue;
                }
            }
        };
        let t = t.parse()?;
        let video = VideoUrl {
            title,
            name,
            url: url_into_share,
            time: t,
        };
        info!("获取到单个视频信息 video = {:?}", &video);
        ret.push(video);
    }
    Ok(ret)
}

pub async fn get_video_url(url: &str) -> Result<String> {
    let url_into_share = Url::parse(url)?;
    info!("获取视频页面 url = {url:?}");

    let res = Client::new().get(url_into_share.clone()).send().await?;
    let text = res.text().await?;
    let text = text[(text.find("<source src=").unwrap_or(0) + 13)..].to_string();
    let download_url = text[..(text.find('\"').unwrap_or(0))].to_string();
    info!("从 {:?} 获取到视频源地址 {:?}", &url, &download_url);
    Ok(download_url)
}

pub fn sort_by_title(urls: Vec<VideoUrl>) -> Vec<Video> {
    let mut videos: Vec<Video> = Vec::new();
    for url in urls {
        let mut exists = false;
        for video in &mut videos {
            if url.title == video.title {
                exists = true;
                video.range.push(url.clone());
            }
        }
        if !exists {
            let mut video = Video {
                title: url.title.clone(),
                range: Vec::new(),
            };
            video.range.push(url.clone());
            videos.push(video);
        }
    }
    for video in &mut videos {
        video.range.sort_by(|a, b| a.time.cmp(&b.time));
    }
    videos
}

pub fn resort(videos: Vec<Video>) -> Vec<VideoUrl> {
    let mut urls = Vec::new();
    for video in videos {
        urls.extend(video.range);
    }
    urls
}

#[derive(Debug)]
pub struct Videoplay {
    pub name: String,
    pub url: String,
}

pub async fn get_video_to_url(mut videos: Vec<VideoUrl>) -> Result<Vec<VideoUrl>> {
    let len = videos.len().try_into()?;
    let pb = ProgressBar::new(len);
    pb.set_style(ProgressStyle::default_bar()
    .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({per_sec}, {eta})")?);
    for video in &mut videos {
        video.url = loop {
            if video.url.ends_with(".mp4") {
                warn!("检测到已获得地址");
                break video.url.clone();
            }
            match get_video_url(&video.url).await {
                Ok(ret) => {
                    warn!("成功获取 ret = {:?}", &ret);
                    break ret;
                }
                Err(_) => {
                    error!("获取源url失败 video = {:?}", &video);
                }
            }
        };
        pb.inc(1);
    }
    pb.finish_with_message("源url获取完成");
    Ok(videos)
}

pub fn get_random_url_list(videos: &[Video]) -> Result<Vec<Videoplay>> {
    let mut rng = rand::thread_rng();
    let randnumber = rng.gen_range(0..videos.len());
    let randone = &videos[randnumber];
    let mut ret = Vec::with_capacity(12);
    for i in &randone.range {
        let name = i.name.clone();
        let url = i.url.clone();
        let one = Videoplay { name, url };
        ret.push(one);
    }
    Ok(ret)
}