use crate::scraper::Link;
use crate::{KODIK_STATE, scraper::Response};
use base64::{Engine as _, engine::general_purpose};
const MIN_SHIFT: u8 = 0;
const MAX_SHIFT: u8 = 26;
impl Response {
pub(crate) fn decode_links(&mut self) -> crate::Result<()> {
log::debug!("Decoding links...");
for link in &mut self.links.p360 {
link.decode_src()?;
if let Some(index) = link.src.rfind(".mp4") {
link.src.replace_range(index - 3..index, "360");
}
}
for link in &mut self.links.p480 {
link.decode_src()?;
if let Some(index) = link.src.rfind(".mp4") {
link.src.replace_range(index - 3..index, "480");
}
}
for link in &mut self.links.p720 {
link.decode_src()?;
if let Some(index) = link.src.rfind(".mp4") {
link.src.replace_range(index - 3..index, "720");
}
}
log::trace!("Decoded links: {:#?}", self.links);
Ok(())
}
}
impl Link {
pub(crate) fn decode_src(&mut self) -> crate::Result<()> {
let shift = KODIK_STATE.shift().clamp(MIN_SHIFT, MAX_SHIFT);
if let Ok(decoded) = try_decode(&self.src, shift) {
self.src = decoded;
return Ok(());
}
for shift in MIN_SHIFT..=MAX_SHIFT {
if let Ok(decoded) = try_decode(&self.src, shift) {
KODIK_STATE.set_shift(shift);
self.src = decoded;
return Ok(());
}
}
Err(crate::Error::LinkCannotBeDecoded(self.src.clone()))
}
}
pub fn try_decode(encoded: &str, shift: u8) -> crate::Result<String> {
let mut decoded_caesar = caesar_cipher(encoded, shift);
while !decoded_caesar.len().is_multiple_of(4) {
decoded_caesar.push('=');
}
let decode_result = decode_base64(&decoded_caesar);
if let Ok(mut decoded) = decode_result {
if !decoded.starts_with("https:") {
decoded.insert_str(0, "https:");
}
return Ok(decoded);
}
decode_result
}
pub fn caesar_cipher(text: &str, shift: u8) -> String {
text.chars()
.map(|c| {
if c.is_ascii_alphabetic() {
let base = if c.is_ascii_lowercase() { b'a' } else { b'A' };
let pos = c as u8 - base;
let new_pos = (pos + MAX_SHIFT - shift) % MAX_SHIFT;
(base + new_pos) as char
} else {
c
}
})
.collect()
}
pub fn decode_base64(input: &str) -> crate::Result<String> {
let decoded_input = general_purpose::STANDARD.decode(input)?;
Ok(String::from_utf8(decoded_input)?)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
mod tests {
use super::*;
use crate::scraper::{Link, Links};
#[test]
fn b64_test() {
let input = "L2Z0b3I=";
let decoded = decode_base64(input).unwrap();
assert_eq!("/ftor", decoded);
}
#[test]
fn caesar_cipher_test() {
let text = "iPZ0kPU6Tg9eUBUck29aj2ZrHO4cG29bT3UdjA9pANQeG0pVVsf5WExqZhsfEsU1muQgmPHiZ05zGus1iuQgUPHsEM5aG25El2RPWEpiAM12EDlRU01FAFlHist0EsZHUNxxULJVD0shBNlRms9MH3ZVms0fBCZrUNtyBBRVms13T2MhVrMhHLJrU2C4GhpuGuYgVLG4UrCgHLHqGrprGuU2VhtuHLUhULQhUhU2VuZuVOC1VONsHEM2HuC0WEU1WLG6UrIgVrI1ULMeUq8hVrIcjFI0WupakhxbGE5xHuDhlK5bU3C4";
let decoded = caesar_cipher(text, 8);
assert_eq!(
"aHR0cHM6Ly9wMTMuc29sb2RjZG4uY29tL3MvbS9hSFIwY0hNNkx5OWpiRzkxWkM1emIyeHZaR05rYmk1amIyMHZkWE5sY25Wd2JHOWhaSE12WVdJM01XSXdZakl0WkRZMFppMDBNV0kzTFdJek9EZ3RNek0xTURjMFlqTTJNek13L2EzNjEzZDBjM2U4YzhmYmQyNDY4MjUyZDZiYjhjYmM2NzlmZDMzMDIzMzM2NmRmNGU1NGFkZWE2ZmU0OWM1ODY6MjAyNjA1MDEwMi8zNjAubXA0OmhsczptYW5pZmVzdC5tM3U4",
decoded
);
}
#[test]
fn try_decoding() {
let src = "iPZ0kPU6Tg9eUBUck29aj2ZrHO4cG29bT3UdjA9pANQeG0pVVsf5WExqZhsfEsU1muQgmPHiZ05zGus1iuQgUPHsEM5aG25El2RPWEpiAM12EDlRU01FAFlHist0EsZHUNxxULJVD0shBNlRms9MH3ZVms0fBCZrUNtyBBRVms13T2MhVrMhHLJrU2C4GhpuGuYgVLG4UrCgHLHqGrprGuU2VhtuHLUhULQhUhU2VuZuVOC1VONsHEM2HuC0WEU1WLG6UrIgVrI1ULMeUq8hVrIcjFI0WupakhxbGE5xHuDhlK5bU3C4";
let decoded = try_decode(src, 8).unwrap();
assert_eq!(
"https://p13.solodcdn.com/s/m/aHR0cHM6Ly9jbG91ZC5zb2xvZGNkbi5jb20vdXNlcnVwbG9hZHMvYWI3MWIwYjItZDY0Zi00MWI3LWIzODgtMzM1MDc0YjM2MzMw/a3613d0c3e8c8fbd2468252d6bb8cbc679fd330233366df4e54adea6fe49c586:2026050102/360.mp4:hls:manifest.m3u8",
decoded
);
}
#[test]
fn decoding_link() {
let mut link = Link { src: "iPZ0kPU6Tg9eUBUck29aj2ZrHO4cG29bT3UdjA9pANQeG0pVVsf5WExqZhsfEsU1muQgmPHiZ05zGus1iuQgUPHsEM5aG25El2RPWEpiAM12EDlRU01FAFlHist0EsZHUNxxULJVD0shBNlRms9MH3ZVms0fBCZrUNtyBBRVms13T2MhVrMhHLJrU2C4GhpuGuYgVLG4UrCgHLHqGrprGuU2VhtuHLUhULQhUhU2VuZuVOC1VONsHEM2HuC0WEU1WLG6UrIgVrI1ULMeUq8hVrIcjFI0WupakhxbGE5xHuDhlK5bU3C4".to_owned(), r#type: "application/x-mpegURL".to_owned() };
link.decode_src().unwrap();
assert_eq!(
"https://p13.solodcdn.com/s/m/aHR0cHM6Ly9jbG91ZC5zb2xvZGNkbi5jb20vdXNlcnVwbG9hZHMvYWI3MWIwYjItZDY0Zi00MWI3LWIzODgtMzM1MDc0YjM2MzMw/a3613d0c3e8c8fbd2468252d6bb8cbc679fd330233366df4e54adea6fe49c586:2026050102/360.mp4:hls:manifest.m3u8",
link.src
);
}
#[test]
fn decoding_links() {
let mut kodik_response = Response {
links: Links {
p360: vec![
Link {
src: "iPZ0kPU6Tg9eUBUck29aj2ZrHO4cG29bT3UdjA9pANQeG0pVVsf5WExqZhsfEsU1muQgmPHiZ05zGus1iuQgUPHsEM5aG25El2RPWEpiAM12EDlRU01FAFlHist0EsZHUNxxULJVD0shBNlRms9MH3ZVms0fBCZrUNtyBBRVms13T2MhVrMhHLJrU2C4GhpuGuYgVLG4UrCgHLHqGrprGuU2VhtuHLUhULQhUhU2VuZuVOC1VONsHEM2HuC0WEU1WLG6UrIgVrI1ULMeUq8hVrIcjFI0WupakhxbGE5xHuDhlK5bU3C4".to_owned(),
r#type: "application/x-mpegURL".to_owned()
},
],
p480: vec![
Link {
src: "iPZ0kPU6Tg9eUBUck29aj2ZrHO4cG29bT3UdjA9pANQeG0pVVsf5WExqZhsfEsU1muQgmPHiZ05zGus1iuQgUPHsEM5aG25El2RPWEpiAM12EDlRU01FAFlHist0EsZHUNxxULJVD0shBNlRms9MH3ZVms0fBCZrUNtyBBRVms13T2MhVrMhHLJrU2C4GhpuGuYgVLG4UrCgHLHqGrprGuU2VhtuHLUhULQhUhU2VuZuVOC1VONsHEM2HuC0WEU1WLG6UrIgVrI1ULMeUq80WLIcjFI0WupakhxbGE5xHuDhlK5bU3C4".to_owned(),
r#type: "application/x-mpegURL".to_owned()
},
],
p720: vec![
Link {
src: "iPZ0kPU6Tg9eUBQck29aj2ZrHO4cG29bT3UdjA9pANQeG0pVVsf5WExqZhsfEsU1muQgmPHiZ05zGus1iuQgUPHsEM5aG25El2RPWEpiAM12EDlRU01FAFlHist0EsZHUNxxULJVD0shBNlRms9MH3ZVms0fBCZrUNtyBBRVms13T2MhVrMhHLJrU2C4GhpuGuYgVLG4UrCgHLHqGrprGuU2VhtuHLUhULQhUhU2VuZuVOC1VONsHEM2HuC0WEU1WLG6UrIgVrI1ULMeUq83UrIcjFI0WupakhxbGE5xHuDhlK5bU3C4".to_owned(),
r#type: "application/x-mpegURL".to_owned()
},
],
},
};
kodik_response.decode_links().unwrap();
assert_eq!(
"https://p13.solodcdn.com/s/m/aHR0cHM6Ly9jbG91ZC5zb2xvZGNkbi5jb20vdXNlcnVwbG9hZHMvYWI3MWIwYjItZDY0Zi00MWI3LWIzODgtMzM1MDc0YjM2MzMw/a3613d0c3e8c8fbd2468252d6bb8cbc679fd330233366df4e54adea6fe49c586:2026050102/360.mp4:hls:manifest.m3u8",
kodik_response.links.p360[0].src
);
assert_eq!(
"https://p13.solodcdn.com/s/m/aHR0cHM6Ly9jbG91ZC5zb2xvZGNkbi5jb20vdXNlcnVwbG9hZHMvYWI3MWIwYjItZDY0Zi00MWI3LWIzODgtMzM1MDc0YjM2MzMw/a3613d0c3e8c8fbd2468252d6bb8cbc679fd330233366df4e54adea6fe49c586:2026050102/480.mp4:hls:manifest.m3u8",
kodik_response.links.p480[0].src
);
assert_eq!(
"https://p12.solodcdn.com/s/m/aHR0cHM6Ly9jbG91ZC5zb2xvZGNkbi5jb20vdXNlcnVwbG9hZHMvYWI3MWIwYjItZDY0Zi00MWI3LWIzODgtMzM1MDc0YjM2MzMw/a3613d0c3e8c8fbd2468252d6bb8cbc679fd330233366df4e54adea6fe49c586:2026050102/720.mp4:hls:manifest.m3u8",
kodik_response.links.p720[0].src
);
}
}