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:
2026-08-07 16:12:01 +08:00
parent 063e910473
commit 3d6f8548c3
11 changed files with 457 additions and 302 deletions
+88 -83
View File
@@ -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.
+70 -20
View File
@@ -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");
}
}
+1 -1
View File
@@ -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};