Compare commits
2 commits
4b3e895d4f
...
6c41ef2fb2
Author | SHA1 | Date | |
---|---|---|---|
6c41ef2fb2 | |||
89cda7db59 |
5 changed files with 1385 additions and 363 deletions
5
Justfile
5
Justfile
|
@ -49,16 +49,15 @@ release crate="rustypipe":
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
CRATE="{{crate}}"
|
CRATE="{{crate}}"
|
||||||
INCLUDES='--include-path README.md --include-path LICENSE --include-path Cargo.toml'
|
|
||||||
CHANGELOG="CHANGELOG.md"
|
CHANGELOG="CHANGELOG.md"
|
||||||
|
|
||||||
if [ "$CRATE" = "rustypipe" ]; then
|
if [ "$CRATE" = "rustypipe" ]; then
|
||||||
INCLUDES="$INCLUDES --include-path 'src/**' --include-path 'tests/**' --include-path 'testfiles/**'"
|
INCLUDES="--exclude-path 'notes/**' --exclude-path 'cli/**' --exclude-path 'downloader/**'"
|
||||||
else
|
else
|
||||||
if [ ! -d "$CRATE" ]; then
|
if [ ! -d "$CRATE" ]; then
|
||||||
echo "$CRATE does not exist."; exit 1
|
echo "$CRATE does not exist."; exit 1
|
||||||
fi
|
fi
|
||||||
INCLUDES="$INCLUDES --include-path '$CRATE/**'"
|
INCLUDES="--include-path README.md --include-path LICENSE --include-path Cargo.toml --include-path '$CRATE/**'"
|
||||||
CHANGELOG="$CRATE/$CHANGELOG"
|
CHANGELOG="$CRATE/$CHANGELOG"
|
||||||
CRATE="rustypipe-$CRATE" # Add crate name prefix
|
CRATE="rustypipe-$CRATE" # Add crate name prefix
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -26,7 +26,7 @@ SAttributed {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Video {
|
Video {
|
||||||
text: "aespa 에스파 'Black ...",
|
text: "aespa 에스파 'Black Mamba' The Debut Stage",
|
||||||
video_id: "Ky5RT5oGg0w",
|
video_id: "Ky5RT5oGg0w",
|
||||||
start_time: 0,
|
start_time: 0,
|
||||||
vtype: Video,
|
vtype: Video,
|
||||||
|
@ -64,7 +64,7 @@ SAttributed {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Web {
|
Web {
|
||||||
text: "aespa",
|
text: "YouTube: aespa",
|
||||||
url: "https://www.youtube.com/c/aespa",
|
url: "https://www.youtube.com/c/aespa",
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
|
@ -76,7 +76,7 @@ SAttributed {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Web {
|
Web {
|
||||||
text: "https://www.instagram.com/aespa_official",
|
text: "Instagram: aespa_official",
|
||||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbE9FVEtZZkVLUExjdFBnZjZnZ19KNWRYOVZUd3xBQ3Jtc0tsbHpCa1hLTVJ6MEllczlzUEpoVi1IQ2F5NG1jMnlOT3p3bnlFeE80ZzlsaG5CUXlFQnFGTkMtN19DcVYzQkw3bVlVVmNwQlpYQWZnNGNsME45WE1WQ21sR3V1Z3k5RG9DUDE0VTZQTm53Mk9vTWhiOA&q=https%3A%2F%2Fwww.instagram.com%2Faespa_official&v=ZeerrnuLi5E",
|
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbE9FVEtZZkVLUExjdFBnZjZnZ19KNWRYOVZUd3xBQ3Jtc0tsbHpCa1hLTVJ6MEllczlzUEpoVi1IQ2F5NG1jMnlOT3p3bnlFeE80ZzlsaG5CUXlFQnFGTkMtN19DcVYzQkw3bVlVVmNwQlpYQWZnNGNsME45WE1WQ21sR3V1Z3k5RG9DUDE0VTZQTm53Mk9vTWhiOA&q=https%3A%2F%2Fwww.instagram.com%2Faespa_official&v=ZeerrnuLi5E",
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
|
@ -88,7 +88,7 @@ SAttributed {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Web {
|
Web {
|
||||||
text: "https://www.tiktok.com/@aespa_official",
|
text: "TikTok: aespa_official",
|
||||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbVdlSGk3eDd5U0dUVG16VFJCQnhKVFBEUUxMQXxBQ3Jtc0tuX3ZJbENNY1ZSN0FFemdxTFdlcTVvc3AwZE05NEFvRW5nOHpZWDUtZG9ORHBnT1JGc2UySDh3WWl3MU53VjFvbHRSdjdxMUlGM2Z6SmdaLTVaWWxhamJEems0Uld3MGlTT0Z0bkh5Y0hpcnY1aXptSQ&q=https%3A%2F%2Fwww.tiktok.com%2F%40aespa_official&v=ZeerrnuLi5E",
|
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbVdlSGk3eDd5U0dUVG16VFJCQnhKVFBEUUxMQXxBQ3Jtc0tuX3ZJbENNY1ZSN0FFemdxTFdlcTVvc3AwZE05NEFvRW5nOHpZWDUtZG9ORHBnT1JGc2UySDh3WWl3MU53VjFvbHRSdjdxMUlGM2Z6SmdaLTVaWWxhamJEems0Uld3MGlTT0Z0bkh5Y0hpcnY1aXptSQ&q=https%3A%2F%2Fwww.tiktok.com%2F%40aespa_official&v=ZeerrnuLi5E",
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
|
@ -100,7 +100,7 @@ SAttributed {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Web {
|
Web {
|
||||||
text: "https://twitter.com/aespa_Official",
|
text: "Twitter: aespa_official",
|
||||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa3lNcG1lMHkwSzNLQVBrUXFNTXl0N1hNa04wUXxBQ3Jtc0tubm1sQkdaVjNYR04xOHpJV3NxZVBpb3I5V1FVOHVFNC1uWE5vb211ZmZKYzhTZXZfbjlkY09fanBRdHpjUkdRVGJJYS0xZ3NBNkVZQVhWSS0xVDYwRlRzQ0J3ODQxNDE0ODAxd1Q0cG5icVlNWndscw&q=https%3A%2F%2Ftwitter.com%2Faespa_Official&v=ZeerrnuLi5E",
|
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa3lNcG1lMHkwSzNLQVBrUXFNTXl0N1hNa04wUXxBQ3Jtc0tubm1sQkdaVjNYR04xOHpJV3NxZVBpb3I5V1FVOHVFNC1uWE5vb211ZmZKYzhTZXZfbjlkY09fanBRdHpjUkdRVGJJYS0xZ3NBNkVZQVhWSS0xVDYwRlRzQ0J3ODQxNDE0ODAxd1Q0cG5icVlNWndscw&q=https%3A%2F%2Ftwitter.com%2Faespa_Official&v=ZeerrnuLi5E",
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
|
@ -112,7 +112,7 @@ SAttributed {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Web {
|
Web {
|
||||||
text: "https://www.facebook.com/aespa.official",
|
text: "Facebook: aespa.official",
|
||||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbjdBNG5yVEFwU0JMNGZaLUpQZ1ZoeGgwT0xOZ3xBQ3Jtc0tuRFdFNlJNV29PMThRNWo5MHZrREZ1ZU5oZlkxVmE4ZlU5STFCZW1mUFVSdXJ3VUQxUnNVVkUzLWJQMS1uRzVjdkRCV2ZxSWJ6cFNxRVVzejY0SDltZFZPc2xwS3ZPZGIxcFZ6cndIVkMtUjVtZ054cw&q=https%3A%2F%2Fwww.facebook.com%2Faespa.official&v=ZeerrnuLi5E",
|
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbjdBNG5yVEFwU0JMNGZaLUpQZ1ZoeGgwT0xOZ3xBQ3Jtc0tuRFdFNlJNV29PMThRNWo5MHZrREZ1ZU5oZlkxVmE4ZlU5STFCZW1mUFVSdXJ3VUQxUnNVVkUzLWJQMS1uRzVjdkRCV2ZxSWJ6cFNxRVVzejY0SDltZFZPc2xwS3ZPZGIxcFZ6cndIVkMtUjVtZ054cw&q=https%3A%2F%2Fwww.facebook.com%2Faespa.official&v=ZeerrnuLi5E",
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use serde_with::{serde_as, DefaultOnError, DeserializeAs, VecSkipError};
|
use serde_with::{serde_as, DefaultOnError, DeserializeAs, VecSkipError};
|
||||||
|
|
||||||
|
@ -155,12 +153,16 @@ pub(crate) struct AttributedText {
|
||||||
style_runs: Vec<StyleRun>,
|
style_runs: Vec<StyleRun>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct CommandRun {
|
struct CommandRun {
|
||||||
start_index: usize,
|
start_index: usize,
|
||||||
length: usize,
|
length: usize,
|
||||||
on_tap: AttributedTextOnTap,
|
on_tap: AttributedTextOnTap,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde_as(as = "DefaultOnError<_>")]
|
||||||
|
on_tap_options: Option<AttributedTextOnTapOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -200,6 +202,18 @@ struct AttributedTextOnTap {
|
||||||
innertube_command: NavigationEndpoint,
|
innertube_command: NavigationEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct AttributedTextOnTapOptions {
|
||||||
|
accessibility_info: AccessibilityInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct AccessibilityInfo {
|
||||||
|
accessibility_label: String,
|
||||||
|
}
|
||||||
|
|
||||||
struct AttributedTextRun {
|
struct AttributedTextRun {
|
||||||
start_index: usize,
|
start_index: usize,
|
||||||
length: usize,
|
length: usize,
|
||||||
|
@ -207,7 +221,7 @@ struct AttributedTextRun {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AttributedTextRunContent {
|
enum AttributedTextRunContent {
|
||||||
Link(NavigationEndpoint),
|
Link(NavigationEndpoint, Option<String>),
|
||||||
Style(Style),
|
Style(Style),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +244,12 @@ impl From<CommandRun> for AttributedTextRun {
|
||||||
Self {
|
Self {
|
||||||
start_index: value.start_index,
|
start_index: value.start_index,
|
||||||
length: value.length,
|
length: value.length,
|
||||||
content: AttributedTextRunContent::Link(value.on_tap.innertube_command),
|
content: AttributedTextRunContent::Link(
|
||||||
|
value.on_tap.innertube_command,
|
||||||
|
value
|
||||||
|
.on_tap_options
|
||||||
|
.map(|o| o.accessibility_info.accessibility_label),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,7 +400,7 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
|
||||||
components.push(TextComponent::new(txt_before));
|
components.push(TextComponent::new(txt_before));
|
||||||
}
|
}
|
||||||
components.push(match run.content {
|
components.push(match run.content {
|
||||||
AttributedTextRunContent::Link(link) => {
|
AttributedTextRunContent::Link(link, label) => {
|
||||||
// Trim link text:
|
// Trim link text:
|
||||||
// 3xnbsp, (/ •), nbsp, Name, 2xnbsp
|
// 3xnbsp, (/ •), nbsp, Name, 2xnbsp
|
||||||
// Channel: `\u{a0}\u{a0}\u{a0}/\u{a0}aespa\u{a0}\u{a0}`
|
// Channel: `\u{a0}\u{a0}\u{a0}/\u{a0}aespa\u{a0}\u{a0}`
|
||||||
|
@ -391,10 +410,35 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
|
||||||
let txt_link = txt_run.trim();
|
let txt_link = txt_run.trim();
|
||||||
let txt_link = txt_link.replace('\u{a0}', " ");
|
let txt_link = txt_link.replace('\u{a0}', " ");
|
||||||
|
|
||||||
static LINK_PREFIX: Lazy<Regex> = Lazy::new(|| Regex::new("^[/•] *").unwrap());
|
if let Some(txt_link) = txt_link.strip_prefix(['/', '•']) {
|
||||||
let txt_link = LINK_PREFIX.replace(&txt_link, "");
|
let txt_link = txt_link.trim_start();
|
||||||
|
match (&link, label) {
|
||||||
map_text_component(txt_link.to_string(), Style::default(), Some(link))
|
(NavigationEndpoint::Url { .. }, Some(label)) => {
|
||||||
|
// Prefix chip-style web links with the service name from accessibility label
|
||||||
|
// Example: `Twitter: aespa_official`
|
||||||
|
if let Some(first_word) = label.split_whitespace().next() {
|
||||||
|
map_text_component(
|
||||||
|
format!("{first_word}: {txt_link}"),
|
||||||
|
Style::default(),
|
||||||
|
Some(link),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
map_text_component(
|
||||||
|
txt_link.to_owned(),
|
||||||
|
Style::default(),
|
||||||
|
Some(link),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => map_text_component(
|
||||||
|
txt_link.to_owned(),
|
||||||
|
Style::default(),
|
||||||
|
Some(link),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map_text_component(txt_link, Style::default(), Some(link))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AttributedTextRunContent::Style(style) => {
|
AttributedTextRunContent::Style(style) => {
|
||||||
map_text_component(txt_run.to_string(), style, None)
|
map_text_component(txt_run.to_string(), style, None)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2519,7 +2519,7 @@ async fn music_new_videos(rp: RustyPipe) {
|
||||||
assert!(!video.name.is_empty());
|
assert!(!video.name.is_empty());
|
||||||
assert!(!video.cover.is_empty(), "got no cover");
|
assert!(!video.cover.is_empty(), "got no cover");
|
||||||
if let Some(view_count) = video.view_count {
|
if let Some(view_count) = video.view_count {
|
||||||
assert_gte(view_count, 1000, "views");
|
assert_gte(view_count, 500, "views");
|
||||||
} else {
|
} else {
|
||||||
// Podcast episode: shows duration instead of view count
|
// Podcast episode: shows duration instead of view count
|
||||||
assert!(video.duration.is_some(), "no view count or duration");
|
assert!(video.duration.is_some(), "no view count or duration");
|
||||||
|
|
Loading…
Reference in a new issue