feat: add direct view shortcuts and category search
This commit is contained in:
+44
-10
@@ -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
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user