// yt - A fully featured command line YouTube client // // Copyright (C) 2025 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Yt. // // You should have received a copy of the License along with this program. // If not, see . use curl::easy::Easy; use log::{error, info, warn}; use rustpython::vm::{ PyRef, VirtualMachine, builtins::{PyDict, PyStr}, }; use serde::{Deserialize, Serialize}; use crate::{InfoJson, json_get}; use super::PostProcessor; #[derive(Debug, Clone, Copy)] pub struct DeArrowPP; impl PostProcessor for DeArrowPP { fn extractors(&self) -> &'static [&'static str] { &["Youtube"] } fn process(&self, mut info: InfoJson) -> Result { let mut output: DeArrowApi = { let output_bytes = { let mut dst = Vec::new(); let mut easy = Easy::new(); easy.url( format!( "https://sponsor.ajay.app/api/branding?videoID={}", pydict_get!(@vm, info, "id", PyStr).as_str() ) .as_str(), )?; let mut transfer = easy.transfer(); transfer.write_function(|data| { dst.extend_from_slice(data); Ok(data.len()) })?; transfer.perform()?; drop(transfer); dst }; serde_json::from_slice(&output_bytes)? }; // We pop the titles, so we need this vector reversed. output.titles.reverse(); let title_len = output.titles.len(); let selected = loop { let Some(title) = output.titles.pop() else { break false; }; if (title.locked || title.votes < 1) && title_len > 1 { info!( "DeArrow: Skipping title {:#?}, as it is not good enough", title.value ); // Skip titles that are not “good” enough. continue; } update_title(&info, &title.value, vm); break true; }; if !selected && title_len != 0 { // No title was selected, even though we had some titles. // Just pick the first one in this case. update_title(&info, &output.titles[0].value, vm); } Ok(info) } #[derive(Serialize, Deserialize)] /// See: struct DeArrowApi { titles: Vec, thumbnails: Vec<Thumbnail>, #[serde(alias = "randomTime")] random_time: Option<f64>, #[serde(alias = "videoDuration")] video_duration: Option<f64>, #[serde(alias = "casualVotes")] casual_votes: Vec<String>, } #[derive(Serialize, Deserialize)] struct Title { /// Note: Titles will sometimes contain > before a word. /// This tells the auto-formatter to not format a word. /// If you have no auto-formatter, you can ignore this and replace it with an empty string #[serde(alias = "title")] value: String, original: bool, votes: u64, locked: bool, #[serde(alias = "UUID")] uuid: String, /// only present if requested #[serde(alias = "userID")] user_id: Option<String>, } #[derive(Serialize, Deserialize)] struct Thumbnail { // null if original is true timestamp: Option<f64>, original: bool, votes: u64, locked: bool, #[serde(alias = "UUID")] uuid: String, /// only present if requested #[serde(alias = "userID")] user_id: Option<String>, }