feat: add direct view shortcuts and category search

This commit is contained in:
2026-10-12 01:41:19 +02:00
parent b15fa23b8a
commit 28c94eb70b
4 changed files with 498 additions and 108 deletions
+44 -10
View File
@@ -379,8 +379,34 @@ struct Extraction {
completed: Option<String>,
}
fn feed_commands(channels: &[String], requests: &[FeedRequest]) -> io::Result<Vec<Command>> {
fn encode_query(query: &str) -> String {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let unreserved =
|byte: u8| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~');
let capacity = query
.bytes()
.map(|byte| if unreserved(byte) { 1 } else { 3 })
.sum();
let mut encoded = String::with_capacity(capacity);
for byte in query.bytes() {
if unreserved(byte) {
encoded.push(char::from(byte));
} else {
encoded.push('%');
encoded.push(char::from(HEX[usize::from(byte >> 4)]));
encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
}
encoded
}
fn feed_commands(
channels: &[String],
requests: &[FeedRequest],
query: &str,
) -> io::Result<Vec<Command>> {
validate_feed_requests(requests)?;
let query = (!query.is_empty()).then(|| encode_query(query));
let mut commands = Vec::new();
let mut bounds = None;
let batch_size = requests.len().div_ceil(MAX_EXTRACTORS);
@@ -417,10 +443,10 @@ fn feed_commands(channels: &[String], requests: &[FeedRequest]) -> io::Result<Ve
bounds = Some((request.start, request.count));
batch_len = 0;
}
commands
.last_mut()
.unwrap()
.arg(format!("{channel}/videos"));
commands.last_mut().unwrap().arg(match &query {
Some(query) => format!("{channel}/search?query={query}"),
None => format!("{channel}/videos"),
});
batch_len += 1;
}
Ok(commands)
@@ -442,8 +468,8 @@ impl Job {
]))
}
pub fn feed(channels: &[String], requests: &[FeedRequest]) -> io::Result<Self> {
Self::spawn_all(feed_commands(channels, requests)?)
pub fn feed(channels: &[String], requests: &[FeedRequest], query: &str) -> io::Result<Self> {
Self::spawn_all(feed_commands(channels, requests, query)?)
}
pub fn channel(id: &str) -> io::Result<Self> {
@@ -1583,6 +1609,14 @@ mod tests {
assert_eq!(exhausted.feed_progress[0].count, 0);
}
#[test]
fn channel_search_query_preserves_utf8_and_isolates_url_parameters() {
assert_eq!(
encode_query("café 🦀 &next=1#?/+%20\t\n\0 AZaz09-._~"),
"caf%C3%A9%20%F0%9F%A6%80%20%26next%3D1%23%3F%2F%2B%2520%09%0A%00%20AZaz09-._~"
);
}
#[test]
fn page_offsets_reject_zero_and_overflow_before_spawning() {
assert_eq!(page_end(1).unwrap(), PAGE_SIZE);
@@ -1595,12 +1629,12 @@ mod tests {
Err(error) if error.kind() == io::ErrorKind::InvalidInput
));
assert!(matches!(
Job::feed(&channels, &[FeedRequest { start, ..ONE_CHANNEL[0] }]),
Job::feed(&channels, &[FeedRequest { start, ..ONE_CHANNEL[0] }], ""),
Err(error) if error.kind() == io::ErrorKind::InvalidInput
));
}
assert!(matches!(
Job::feed(&[], &ONE_CHANNEL),
Job::feed(&[], &ONE_CHANNEL, ""),
Err(error) if error.kind() == io::ErrorKind::InvalidInput
));
}
@@ -1660,7 +1694,7 @@ mod tests {
],
] {
assert!(matches!(
Job::feed(&channels, &requests),
Job::feed(&channels, &requests, ""),
Err(error) if error.kind() == io::ErrorKind::InvalidInput
));
}