mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-23 23:32:05 +00:00
style: cargo fmt across the workspace
Apply rustfmt to the 11 files that had drifted (86 hunks): x-media site modules (bsky/pixiv/twitter) and xmedia-bot (config/main/ photo/send). Formatting only - no semantic changes; full test suite still green.
This commit is contained in:
@@ -10,8 +10,8 @@ use crate::site::FetchError;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const AUTH_TOKEN_URL: &str = "https://oauth.secure.pixiv.net/auth/token";
|
||||
@@ -127,7 +127,9 @@ impl PixivAPI {
|
||||
pub async fn illust_detail(&self, illust_id: u64) -> Result<IllustrationModel, PixivError> {
|
||||
let access_token = self.get_access_token().await?;
|
||||
let response = crate::site::CLIENT
|
||||
.get(format!("{APP_API_URL}/v1/illust/detail?illust_id={illust_id}"))
|
||||
.get(format!(
|
||||
"{APP_API_URL}/v1/illust/detail?illust_id={illust_id}"
|
||||
))
|
||||
.header("app-os", "ios")
|
||||
.header("app-os-version", "14.6")
|
||||
.header("User-Agent", APP_USER_AGENT)
|
||||
@@ -175,7 +177,9 @@ impl PixivAPI {
|
||||
pub async fn ugoira_metadata(&self, illust_id: u64) -> Result<UgoiraMetadataModel, PixivError> {
|
||||
let access_token = self.get_access_token().await?;
|
||||
let response = crate::site::CLIENT
|
||||
.get(format!("{APP_API_URL}/v1/ugoira/metadata?illust_id={illust_id}"))
|
||||
.get(format!(
|
||||
"{APP_API_URL}/v1/ugoira/metadata?illust_id={illust_id}"
|
||||
))
|
||||
.header("app-os", "ios")
|
||||
.header("app-os-version", "14.6")
|
||||
.header("User-Agent", APP_USER_AGENT)
|
||||
@@ -218,87 +222,89 @@ impl PixivAPI {
|
||||
let Some(zip_url) = zip_url else {
|
||||
return Ok(None);
|
||||
};
|
||||
let zip_bytes = crate::site::download_media(&zip_url).await.map_err(|e| match e {
|
||||
FetchError::Http(e) => PixivError::Http(e),
|
||||
other => PixivError::Api(format!("frame zip download failed: {other}")),
|
||||
})?;
|
||||
let zip_bytes = crate::site::download_media(&zip_url)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
FetchError::Http(e) => PixivError::Http(e),
|
||||
other => PixivError::Api(format!("frame zip download failed: {other}")),
|
||||
})?;
|
||||
let frame_delays = metadata.frames.iter().map(|f| f.delay).collect::<Vec<_>>();
|
||||
let result = tokio::task::spawn_blocking(
|
||||
move || -> Result<(String, tempfile::TempDir), String> {
|
||||
let frames_dir = tempfile::tempdir().map_err(|e| e.to_string())?;
|
||||
let out_dir = tempfile::tempdir().map_err(|e| e.to_string())?;
|
||||
let result =
|
||||
tokio::task::spawn_blocking(move || -> Result<(String, tempfile::TempDir), String> {
|
||||
let frames_dir = tempfile::tempdir().map_err(|e| e.to_string())?;
|
||||
let out_dir = tempfile::tempdir().map_err(|e| e.to_string())?;
|
||||
|
||||
// Extract frames to canonical zero-padded names; pixiv ugoira
|
||||
// frames are uniformly jpg or png per artwork.
|
||||
let mut archive = zip::ZipArchive::new(Cursor::new(zip_bytes))
|
||||
.map_err(|e| format!("unzip: {e}"))?;
|
||||
// pixiv ugoira frames are uniformly jpg or png per artwork; take
|
||||
// the extension from the first entry.
|
||||
let extension = if archive.len() > 0 {
|
||||
let first_name = archive
|
||||
.by_index(0)
|
||||
.map_err(|e| e.to_string())?
|
||||
.name()
|
||||
.to_string();
|
||||
first_name
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.unwrap_or("jpg")
|
||||
.to_string()
|
||||
} else {
|
||||
"jpg".to_string()
|
||||
};
|
||||
let mut count = 0usize;
|
||||
for i in 0..archive.len() {
|
||||
let mut entry = archive.by_index(i).map_err(|e| e.to_string())?;
|
||||
let mut bytes = Vec::new();
|
||||
entry.read_to_end(&mut bytes).map_err(|e| e.to_string())?;
|
||||
let path = frames_dir.path().join(format!("img_{count:05}.{extension}"));
|
||||
std::fs::write(&path, bytes).map_err(|e| e.to_string())?;
|
||||
count += 1;
|
||||
}
|
||||
if count == 0 {
|
||||
return Err("empty frame zip".to_string());
|
||||
}
|
||||
// Extract frames to canonical zero-padded names; pixiv ugoira
|
||||
// frames are uniformly jpg or png per artwork.
|
||||
let mut archive = zip::ZipArchive::new(Cursor::new(zip_bytes))
|
||||
.map_err(|e| format!("unzip: {e}"))?;
|
||||
// pixiv ugoira frames are uniformly jpg or png per artwork; take
|
||||
// the extension from the first entry.
|
||||
let extension = if archive.len() > 0 {
|
||||
let first_name = archive
|
||||
.by_index(0)
|
||||
.map_err(|e| e.to_string())?
|
||||
.name()
|
||||
.to_string();
|
||||
first_name.rsplit('.').next().unwrap_or("jpg").to_string()
|
||||
} else {
|
||||
"jpg".to_string()
|
||||
};
|
||||
let mut count = 0usize;
|
||||
for i in 0..archive.len() {
|
||||
let mut entry = archive.by_index(i).map_err(|e| e.to_string())?;
|
||||
let mut bytes = Vec::new();
|
||||
entry.read_to_end(&mut bytes).map_err(|e| e.to_string())?;
|
||||
let path = frames_dir
|
||||
.path()
|
||||
.join(format!("img_{count:05}.{extension}"));
|
||||
std::fs::write(&path, bytes).map_err(|e| e.to_string())?;
|
||||
count += 1;
|
||||
}
|
||||
if count == 0 {
|
||||
return Err("empty frame zip".to_string());
|
||||
}
|
||||
|
||||
// Constant rate from the median frame delay (ms).
|
||||
let mut delays = frame_delays;
|
||||
delays.sort_unstable();
|
||||
let median = delays[delays.len() / 2].max(1);
|
||||
let framerate = 1000.0 / median as f64;
|
||||
// Constant rate from the median frame delay (ms).
|
||||
let mut delays = frame_delays;
|
||||
delays.sort_unstable();
|
||||
let median = delays[delays.len() / 2].max(1);
|
||||
let framerate = 1000.0 / median as f64;
|
||||
|
||||
let output = out_dir.path().join("ugoira.mp4");
|
||||
let status = std::process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-y",
|
||||
"-framerate",
|
||||
&framerate.to_string(),
|
||||
"-i",
|
||||
&frames_dir.path().join(format!("img_%05d.{extension}")).to_string_lossy(),
|
||||
// libx264 needs even dimensions; pixiv ugoira frames can
|
||||
// be odd-sized (e.g. 277x405).
|
||||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-movflags",
|
||||
"+faststart",
|
||||
&output.to_string_lossy(),
|
||||
])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.status()
|
||||
.map_err(|e| format!("ffmpeg spawn failed: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err(format!("ffmpeg exited with {status}"));
|
||||
}
|
||||
Ok((output.to_string_lossy().into_owned(), out_dir))
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("ugoira encode worker panicked");
|
||||
let output = out_dir.path().join("ugoira.mp4");
|
||||
let status = std::process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-y",
|
||||
"-framerate",
|
||||
&framerate.to_string(),
|
||||
"-i",
|
||||
&frames_dir
|
||||
.path()
|
||||
.join(format!("img_%05d.{extension}"))
|
||||
.to_string_lossy(),
|
||||
// libx264 needs even dimensions; pixiv ugoira frames can
|
||||
// be odd-sized (e.g. 277x405).
|
||||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-movflags",
|
||||
"+faststart",
|
||||
&output.to_string_lossy(),
|
||||
])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.status()
|
||||
.map_err(|e| format!("ffmpeg spawn failed: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err(format!("ffmpeg exited with {status}"));
|
||||
}
|
||||
Ok((output.to_string_lossy().into_owned(), out_dir))
|
||||
})
|
||||
.await
|
||||
.expect("ugoira encode worker panicked");
|
||||
match result {
|
||||
Ok(pair) => Ok(Some(pair)),
|
||||
Err(message) => {
|
||||
@@ -332,9 +338,8 @@ fn log_once_ffmpeg_missing() {
|
||||
}
|
||||
|
||||
/// pixiv3-rs replacement: `None` when `PIXIV_REFRESH_TOKEN` is unset.
|
||||
static PIXIV_CLIENT: LazyLock<Option<PixivAPI>> = LazyLock::new(|| {
|
||||
env::var("PIXIV_REFRESH_TOKEN").ok().map(PixivAPI::new)
|
||||
});
|
||||
static PIXIV_CLIENT: LazyLock<Option<PixivAPI>> =
|
||||
LazyLock::new(|| env::var("PIXIV_REFRESH_TOKEN").ok().map(PixivAPI::new));
|
||||
|
||||
/// Set at startup when the login validation fails; pixiv stays disabled until
|
||||
/// the next process start.
|
||||
|
||||
@@ -82,12 +82,15 @@ impl Illustration {
|
||||
// keeps media empty when encoding fails or ffmpeg is missing.
|
||||
} else if model.page_count > 1 {
|
||||
media.extend(model.meta_pages.iter().filter_map(|page| {
|
||||
page.image_urls.original.clone().map(|original| Media::Illustration {
|
||||
title: None,
|
||||
url: original,
|
||||
thumbnail_url: Some(page.image_urls.medium.clone()),
|
||||
fallback_url: Some(page.image_urls.large.clone()),
|
||||
})
|
||||
page.image_urls
|
||||
.original
|
||||
.clone()
|
||||
.map(|original| Media::Illustration {
|
||||
title: None,
|
||||
url: original,
|
||||
thumbnail_url: Some(page.image_urls.medium.clone()),
|
||||
fallback_url: Some(page.image_urls.large.clone()),
|
||||
})
|
||||
}));
|
||||
} else if let Some(original) = model
|
||||
.meta_single_page
|
||||
@@ -147,8 +150,8 @@ impl From<Illustration> for Fetched {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::model::IllustrationModel;
|
||||
use super::*;
|
||||
|
||||
fn illust_json(
|
||||
type_: &str,
|
||||
@@ -203,8 +206,14 @@ mod tests {
|
||||
("https://pixiv.net/artworks/123456", "123456"),
|
||||
("https://www.pixiv.net/en/artworks/123456", "123456"),
|
||||
("https://www.pixiv.net/i/123456", "123456"),
|
||||
("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=123456", "123456"),
|
||||
("https://www.pixiv.net/en/member_illust.php?illust_id=123456", "123456"),
|
||||
(
|
||||
"https://www.pixiv.net/member_illust.php?mode=medium&illust_id=123456",
|
||||
"123456",
|
||||
),
|
||||
(
|
||||
"https://www.pixiv.net/en/member_illust.php?illust_id=123456",
|
||||
"123456",
|
||||
),
|
||||
];
|
||||
for (url, id) in cases {
|
||||
let caps = PATTERN.captures(url).unwrap_or_else(|| panic!("{url}"));
|
||||
@@ -225,7 +234,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn ugoira_yields_empty_media() {
|
||||
let v = illust_json("ugoira", 1, Some("https://i.pximg.net/orig.jpg"), None, vec![], 0);
|
||||
let v = illust_json(
|
||||
"ugoira",
|
||||
1,
|
||||
Some("https://i.pximg.net/orig.jpg"),
|
||||
None,
|
||||
vec![],
|
||||
0,
|
||||
);
|
||||
let illustration = parse(v);
|
||||
let fetched: Fetched = illustration.into();
|
||||
assert!(fetched.media.is_empty());
|
||||
@@ -296,7 +312,12 @@ mod tests {
|
||||
let fetched: Fetched = parse(v).into();
|
||||
assert_eq!(fetched.media.len(), 1);
|
||||
match &fetched.media[0] {
|
||||
Media::Illustration { url, thumbnail_url, fallback_url, .. } => {
|
||||
Media::Illustration {
|
||||
url,
|
||||
thumbnail_url,
|
||||
fallback_url,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(url, "https://i.pximg.net/p2.jpg");
|
||||
assert_eq!(thumbnail_url.as_deref(), Some("m2.jpg"));
|
||||
assert_eq!(fallback_url.as_deref(), Some("l2.jpg"));
|
||||
@@ -307,7 +328,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn caption_with_escapes_format_and_substitutes() {
|
||||
let v = illust_json("illust", 1, Some("https://i.pximg.net/o.jpg"), None, vec![], 0);
|
||||
let v = illust_json(
|
||||
"illust",
|
||||
1,
|
||||
Some("https://i.pximg.net/o.jpg"),
|
||||
None,
|
||||
vec![],
|
||||
0,
|
||||
);
|
||||
let fetched: Fetched = parse(v).into();
|
||||
// Format string is escaped in full, then placeholders substituted.
|
||||
let out = fetched.caption_with("{title} by {author} <script> {tags}");
|
||||
@@ -330,7 +358,14 @@ mod tests {
|
||||
#[test]
|
||||
fn ai_work_gets_leading_ai_tag() {
|
||||
// illust_ai_type == 2 is the only AI marker.
|
||||
let v = illust_json("illust", 1, Some("https://i.pximg.net/o.jpg"), None, vec![], 2);
|
||||
let v = illust_json(
|
||||
"illust",
|
||||
1,
|
||||
Some("https://i.pximg.net/o.jpg"),
|
||||
None,
|
||||
vec![],
|
||||
2,
|
||||
);
|
||||
let fetched: Fetched = parse(v).into();
|
||||
assert!(
|
||||
fetched.caption.contains("#AI #tag1 #tag2"),
|
||||
@@ -338,14 +373,25 @@ mod tests {
|
||||
fetched.caption
|
||||
);
|
||||
// The {tags} placeholder reflects the tag array too.
|
||||
assert!(fetched.caption_with("{tags}").starts_with("#AI "), "got: {}", fetched.caption_with("{tags}"));
|
||||
assert!(
|
||||
fetched.caption_with("{tags}").starts_with("#AI "),
|
||||
"got: {}",
|
||||
fetched.caption_with("{tags}")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_ai_work_has_no_ai_tag() {
|
||||
// 1 = explicitly not AI, 0 = undefined: neither gets the #AI tag.
|
||||
for ai_type in [0, 1] {
|
||||
let v = illust_json("illust", 1, Some("https://i.pximg.net/o.jpg"), None, vec![], ai_type);
|
||||
let v = illust_json(
|
||||
"illust",
|
||||
1,
|
||||
Some("https://i.pximg.net/o.jpg"),
|
||||
None,
|
||||
vec![],
|
||||
ai_type,
|
||||
);
|
||||
let fetched: Fetched = parse(v).into();
|
||||
assert!(
|
||||
!fetched.caption.contains("#AI"),
|
||||
@@ -357,7 +403,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn caption_escapes_and_links() {
|
||||
let v = illust_json("illust", 1, Some("https://i.pximg.net/o.jpg"), None, vec![], 0);
|
||||
let v = illust_json(
|
||||
"illust",
|
||||
1,
|
||||
Some("https://i.pximg.net/o.jpg"),
|
||||
None,
|
||||
vec![],
|
||||
0,
|
||||
);
|
||||
let fetched: Fetched = parse(v).into();
|
||||
assert!(
|
||||
fetched
|
||||
@@ -367,9 +420,6 @@ mod tests {
|
||||
fetched.caption
|
||||
);
|
||||
assert!(fetched.caption.contains("#tag1 #tag2"));
|
||||
assert_eq!(
|
||||
fetched.source_url,
|
||||
"https://www.pixiv.net/artworks/123"
|
||||
);
|
||||
assert_eq!(fetched.source_url, "https://www.pixiv.net/artworks/123");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@ mod interface;
|
||||
mod model;
|
||||
|
||||
pub use api::{PixivAPI, PixivError, disable, fetch, validate};
|
||||
pub use interface::{PATTERN, Illustration, enabled, fetch_from_url};
|
||||
pub use interface::{Illustration, PATTERN, enabled, fetch_from_url};
|
||||
|
||||
Reference in New Issue
Block a user