Compare commits
No commits in common. "513bf1dc9c78dbfe9cf7c8539e983dc49ff7ae58" and "5b8c3d646af15ba42e33c7a4767b7af4e1a3c930" have entirely different histories.
513bf1dc9c
...
5b8c3d646a
41 changed files with 9340 additions and 58352 deletions
|
@ -29,7 +29,6 @@ futures = "0.3.21"
|
||||||
indicatif = "0.17.0"
|
indicatif = "0.17.0"
|
||||||
filenamify = "0.1.0"
|
filenamify = "0.1.0"
|
||||||
ress = "0.11.4"
|
ress = "0.11.4"
|
||||||
phf = "0.11.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
|
@ -38,6 +37,3 @@ rstest = "0.15.0"
|
||||||
temp_testdir = "0.2.3"
|
temp_testdir = "0.2.3"
|
||||||
insta = "1.17.1"
|
insta = "1.17.1"
|
||||||
velcro = "0.5.3"
|
velcro = "0.5.3"
|
||||||
unic-langid = "0.9.0"
|
|
||||||
intl_pluralrules = "7.0.1"
|
|
||||||
phf_codegen = "0.11.1"
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ async fn download_single_video(
|
||||||
|
|
||||||
let res = async {
|
let res = async {
|
||||||
let player_data = rt
|
let player_data = rt
|
||||||
.get_player(video_id.as_str(), ClientType::TvHtml5Embed)
|
.get_player(video_id.as_str(), ClientType::Desktop)
|
||||||
.await
|
.await
|
||||||
.context(format!(
|
.context(format!(
|
||||||
"Failed to fetch player data for video {}",
|
"Failed to fetch player data for video {}",
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
10899
notes/next/next_mv.json
10899
notes/next/next_mv.json
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
@ -26,25 +26,3 @@ Throttling issue: Y8JFxS1HlDo
|
||||||
495 Songs: PL5dDx681T4bR7ZF1IuWzOv1omlRbE7PiJ
|
495 Songs: PL5dDx681T4bR7ZF1IuWzOv1omlRbE7PiJ
|
||||||
78 Videos: PL2_OBreMn7FpDFj9lWfoZ8OQJvZkQa3yG
|
78 Videos: PL2_OBreMn7FpDFj9lWfoZ8OQJvZkQa3yG
|
||||||
66 Videos: PL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe
|
66 Videos: PL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe
|
||||||
4.657 Songs: PLI_eFW8NAFzYAXZ5DrU6E6mQ_XfhaLBUX
|
|
||||||
186 Songs: PLbZIPy20-1pN7mqjckepWF78ndb6ci_qi
|
|
||||||
|
|
||||||
Playlist update dates (05.09.2022):
|
|
||||||
today: RDCLAK5uy_kj3rhiar1LINmyDcuFnXihEO0K1NQa2jI
|
|
||||||
yesterday: PL4C44E2875308A280
|
|
||||||
2 days ago: PL7zsB-C3aNu2yRY2869T0zj1FhtRIu5am
|
|
||||||
5 days ago: PL3-sRm8xAzY9sDilvaWjCwCI0TkUzYdOG
|
|
||||||
7 days ago: PLHr0jWPfopte182N54r1ra7tkRJC1fmPu
|
|
||||||
|
|
||||||
Jan PL1J-6JOckZtHxTA3hN5SK7gBQaFfKzeXr 01.01.2016
|
|
||||||
Feb PL1J-6JOckZtETrbzwZE7mRIIK6BzWNLAs 07.02.2016
|
|
||||||
Mar PL1J-6JOckZtG3AVdvBXhMO64mB2k3BtKi 09.03.2015
|
|
||||||
Apr PL1J-6JOckZtE_rUpK24S6X5hOE4eQoprN 02.04.2017
|
|
||||||
May PL1J-6JOckZtG1ThBxoSLFL-Jg4sa2iX_a 22.05.2014
|
|
||||||
Jun PL1J-6JOckZtF_wSzkXBl91pit9d6Fh0QF 28.07.2014
|
|
||||||
Jul PL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe 02.07.2014
|
|
||||||
Aug PL1J-6JOckZtFFQeWx-ZC0ubpJCEWmGWRx 23.08.2015
|
|
||||||
Sep PL1J-6JOckZtHVs0JhBW_qfsW-dtXuM0mQ 16.09.2018
|
|
||||||
Oct PL1J-6JOckZtE4g-XgZkL_N0kkoKui5Eys 31.10.2014
|
|
||||||
Nov PL1J-6JOckZtEzjMUEyPyPpG836pjeIapw 03.11.2016
|
|
||||||
Dec PL1J-6JOckZtHo91uApeb10Qlf2XhkfM-9 24.12.2021
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use reqwest::Method;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use super::{response, ClientType, ContextYT, RustyTube};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct QChannel {
|
|
||||||
context: ContextYT,
|
|
||||||
browse_id: String,
|
|
||||||
params: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustyTube {
|
|
||||||
async fn get_channel_response(&self, channel_id: &str) -> Result<response::Channel> {
|
|
||||||
let client = self.get_ytclient(ClientType::Desktop);
|
|
||||||
let context = client.get_context(true).await;
|
|
||||||
|
|
||||||
let request_body = QChannel {
|
|
||||||
context,
|
|
||||||
browse_id: channel_id.to_owned(),
|
|
||||||
params: "EgZ2aWRlb3PyBgQKAjoA".to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.request_builder(Method::POST, "browse")
|
|
||||||
.await
|
|
||||||
.json(&request_body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
Ok(resp.json::<response::Channel>().await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod playlist;
|
pub mod playlist;
|
||||||
pub mod video;
|
|
||||||
|
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -17,7 +15,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cache::{Cache, ClientData},
|
cache::{Cache, ClientData},
|
||||||
model::{Country, Language},
|
model::Locale,
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -70,8 +68,10 @@ struct ClientInfo {
|
||||||
platform: String,
|
platform: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
original_url: Option<String>,
|
original_url: Option<String>,
|
||||||
hl: Language,
|
/// Language (`en`, `de`)
|
||||||
gl: Country,
|
hl: String,
|
||||||
|
/// Country (`US`, `DE`)
|
||||||
|
gl: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
@ -127,7 +127,7 @@ const ANDROID_API_KEY: &str = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w";
|
||||||
const IOS_API_KEY: &str = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc";
|
const IOS_API_KEY: &str = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc";
|
||||||
const IOS_DEVICE_MODEL: &str = "iPhone14,5";
|
const IOS_DEVICE_MODEL: &str = "iPhone14,5";
|
||||||
|
|
||||||
static CLIENT_VERSION_REGEXES: Lazy<[Regex; 3]> = Lazy::new(|| {
|
const CLIENT_VERSION_REGEXES: Lazy<[Regex; 3]> = Lazy::new(|| {
|
||||||
[
|
[
|
||||||
Regex::new("INNERTUBE_CONTEXT_CLIENT_VERSION\":\"([0-9\\.]+?)\"").unwrap(),
|
Regex::new("INNERTUBE_CONTEXT_CLIENT_VERSION\":\"([0-9\\.]+?)\"").unwrap(),
|
||||||
Regex::new("innertube_context_client_version\":\"([0-9\\.]+?)\"").unwrap(),
|
Regex::new("innertube_context_client_version\":\"([0-9\\.]+?)\"").unwrap(),
|
||||||
|
@ -136,7 +136,7 @@ static CLIENT_VERSION_REGEXES: Lazy<[Regex; 3]> = Lazy::new(|| {
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct RustyTube {
|
pub struct RustyTube {
|
||||||
localization: Arc<Localization>,
|
pub locale: Arc<Locale>,
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
desktop_client: Arc<DesktopClient>,
|
desktop_client: Arc<DesktopClient>,
|
||||||
desktop_music_client: Arc<DesktopMusicClient>,
|
desktop_music_client: Arc<DesktopMusicClient>,
|
||||||
|
@ -145,30 +145,17 @@ pub struct RustyTube {
|
||||||
tvhtml5embed_client: Arc<TvHtml5EmbedClient>,
|
tvhtml5embed_client: Arc<TvHtml5EmbedClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Localization {
|
|
||||||
language: Language,
|
|
||||||
content_country: Country,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustyTube {
|
impl RustyTube {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::new_with_ua(
|
Self::new_with_ua("en", "US", Some("rusty-tube.json".to_owned()))
|
||||||
Language::En,
|
|
||||||
Country::Us,
|
|
||||||
Some("rusty-tube.json".to_owned()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new_with_ua(
|
pub fn new_with_ua(lang: &str, country: &str, cache_file: Option<String>) -> Self {
|
||||||
language: Language,
|
let locale = Arc::new(Locale {
|
||||||
content_country: Country,
|
lang: lang.to_owned(),
|
||||||
cache_file: Option<String>,
|
country: country.to_owned(),
|
||||||
) -> Self {
|
|
||||||
let loc = Arc::new(Localization {
|
|
||||||
language,
|
|
||||||
content_country,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let cache = match cache_file.as_ref() {
|
let cache = match cache_file.as_ref() {
|
||||||
|
@ -177,17 +164,17 @@ impl RustyTube {
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
localization: loc.clone(),
|
locale: locale.clone(),
|
||||||
cache: cache.clone(),
|
cache: cache.clone(),
|
||||||
desktop_client: Arc::new(DesktopClient::new(loc.clone(), cache.clone())),
|
desktop_client: Arc::new(DesktopClient::new(locale.clone(), cache.clone())),
|
||||||
desktop_music_client: Arc::new(DesktopMusicClient::new(loc.clone(), cache)),
|
desktop_music_client: Arc::new(DesktopMusicClient::new(locale.clone(), cache)),
|
||||||
android_client: Arc::new(AndroidClient::new(loc.clone())),
|
android_client: Arc::new(AndroidClient::new(locale.clone())),
|
||||||
ios_client: Arc::new(IosClient::new(loc.clone())),
|
ios_client: Arc::new(IosClient::new(locale.clone())),
|
||||||
tvhtml5embed_client: Arc::new(TvHtml5EmbedClient::new(loc)),
|
tvhtml5embed_client: Arc::new(TvHtml5EmbedClient::new(locale)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_ytclient(&self, client_type: ClientType) -> Arc<dyn YTClient> {
|
fn get_ytclient(&self, client_type: ClientType) -> Arc<dyn YTClient> {
|
||||||
match client_type {
|
match client_type {
|
||||||
ClientType::Desktop => self.desktop_client.clone(),
|
ClientType::Desktop => self.desktop_client.clone(),
|
||||||
ClientType::DesktopMusic => self.desktop_music_client.clone(),
|
ClientType::DesktopMusic => self.desktop_music_client.clone(),
|
||||||
|
@ -215,7 +202,7 @@ async fn exec_request_text(http: Client, request: Request) -> Result<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DesktopClient {
|
pub struct DesktopClient {
|
||||||
localization: Arc<Localization>,
|
locale: Arc<Locale>,
|
||||||
http: Client,
|
http: Client,
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
consent_cookie: String,
|
consent_cookie: String,
|
||||||
|
@ -233,12 +220,12 @@ impl YTClient for DesktopClient {
|
||||||
platform: "DESKTOP".to_owned(),
|
platform: "DESKTOP".to_owned(),
|
||||||
original_url: Some("https://www.youtube.com/".to_owned()),
|
original_url: Some("https://www.youtube.com/".to_owned()),
|
||||||
hl: match localized {
|
hl: match localized {
|
||||||
true => self.localization.language,
|
true => self.locale.lang.to_owned(),
|
||||||
false => Language::En,
|
false => "en".to_owned(),
|
||||||
},
|
},
|
||||||
gl: match localized {
|
gl: match localized {
|
||||||
true => self.localization.content_country,
|
true => self.locale.country.to_owned(),
|
||||||
false => Country::Us,
|
false => "US".to_owned(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: Some(RequestYT::default()),
|
request: Some(RequestYT::default()),
|
||||||
|
@ -273,7 +260,7 @@ impl YTClient for DesktopClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopClient {
|
impl DesktopClient {
|
||||||
fn new(localization: Arc<Localization>, cache: Cache) -> Self {
|
fn new(locale: Arc<Locale>, cache: Cache) -> Self {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let http = ClientBuilder::new()
|
let http = ClientBuilder::new()
|
||||||
|
@ -284,7 +271,7 @@ impl DesktopClient {
|
||||||
.expect("unable to build the HTTP client");
|
.expect("unable to build the HTTP client");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
localization,
|
locale,
|
||||||
http,
|
http,
|
||||||
cache,
|
cache,
|
||||||
consent_cookie: format!(
|
consent_cookie: format!(
|
||||||
|
@ -342,7 +329,7 @@ impl DesktopClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AndroidClient {
|
pub struct AndroidClient {
|
||||||
localization: Arc<Localization>,
|
locale: Arc<Locale>,
|
||||||
http: Client,
|
http: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,12 +345,12 @@ impl YTClient for AndroidClient {
|
||||||
platform: "MOBILE".to_owned(),
|
platform: "MOBILE".to_owned(),
|
||||||
original_url: None,
|
original_url: None,
|
||||||
hl: match localized {
|
hl: match localized {
|
||||||
true => self.localization.language,
|
true => self.locale.lang.to_owned(),
|
||||||
false => Language::En,
|
false => "en".to_owned(),
|
||||||
},
|
},
|
||||||
gl: match localized {
|
gl: match localized {
|
||||||
true => self.localization.content_country,
|
true => self.locale.country.to_owned(),
|
||||||
false => Country::Us,
|
false => "US".to_owned(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: None,
|
request: None,
|
||||||
|
@ -397,22 +384,22 @@ impl YTClient for AndroidClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AndroidClient {
|
impl AndroidClient {
|
||||||
fn new(localization: Arc<Localization>) -> Self {
|
fn new(locale: Arc<Locale>) -> Self {
|
||||||
let http = ClientBuilder::new()
|
let http = ClientBuilder::new()
|
||||||
.user_agent(format!(
|
.user_agent(format!(
|
||||||
"com.google.android.youtube/{} (Linux; U; Android 12; {}) gzip",
|
"com.google.android.youtube/{} (Linux; U; Android 12; {}) gzip",
|
||||||
MOBILE_CLIENT_VERSION, localization.content_country
|
MOBILE_CLIENT_VERSION, locale.country
|
||||||
))
|
))
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
.build()
|
.build()
|
||||||
.expect("unable to build the HTTP client");
|
.expect("unable to build the HTTP client");
|
||||||
|
|
||||||
Self { localization, http }
|
Self { locale, http }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IosClient {
|
pub struct IosClient {
|
||||||
localization: Arc<Localization>,
|
locale: Arc<Locale>,
|
||||||
http: Client,
|
http: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,12 +415,12 @@ impl YTClient for IosClient {
|
||||||
platform: "MOBILE".to_owned(),
|
platform: "MOBILE".to_owned(),
|
||||||
original_url: None,
|
original_url: None,
|
||||||
hl: match localized {
|
hl: match localized {
|
||||||
true => self.localization.language,
|
true => self.locale.lang.to_owned(),
|
||||||
false => Language::En,
|
false => "en".to_owned(),
|
||||||
},
|
},
|
||||||
gl: match localized {
|
gl: match localized {
|
||||||
true => self.localization.content_country,
|
true => self.locale.country.to_owned(),
|
||||||
false => Country::Us,
|
false => "US".to_owned(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: None,
|
request: None,
|
||||||
|
@ -464,22 +451,22 @@ impl YTClient for IosClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IosClient {
|
impl IosClient {
|
||||||
fn new(localization: Arc<Localization>) -> Self {
|
fn new(locale: Arc<Locale>) -> Self {
|
||||||
let http = ClientBuilder::new()
|
let http = ClientBuilder::new()
|
||||||
.user_agent(format!(
|
.user_agent(format!(
|
||||||
"com.google.ios.youtube/{} ({}; U; CPU iOS 15_4 like Mac OS X; {})",
|
"com.google.ios.youtube/{} ({}; U; CPU iOS 15_4 like Mac OS X; {})",
|
||||||
MOBILE_CLIENT_VERSION, IOS_DEVICE_MODEL, localization.content_country
|
MOBILE_CLIENT_VERSION, IOS_DEVICE_MODEL, locale.country
|
||||||
))
|
))
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
.build()
|
.build()
|
||||||
.expect("unable to build the HTTP client");
|
.expect("unable to build the HTTP client");
|
||||||
|
|
||||||
Self { localization, http }
|
Self { locale, http }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TvHtml5EmbedClient {
|
pub struct TvHtml5EmbedClient {
|
||||||
localization: Arc<Localization>,
|
locale: Arc<Locale>,
|
||||||
http: Client,
|
http: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,12 +482,12 @@ impl YTClient for TvHtml5EmbedClient {
|
||||||
platform: "TV".to_owned(),
|
platform: "TV".to_owned(),
|
||||||
original_url: None,
|
original_url: None,
|
||||||
hl: match localized {
|
hl: match localized {
|
||||||
true => self.localization.language,
|
true => self.locale.lang.to_owned(),
|
||||||
false => Language::En,
|
false => "en".to_owned(),
|
||||||
},
|
},
|
||||||
gl: match localized {
|
gl: match localized {
|
||||||
true => self.localization.content_country,
|
true => self.locale.country.to_owned(),
|
||||||
false => Country::Us,
|
false => "US".to_owned(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: Some(RequestYT::default()),
|
request: Some(RequestYT::default()),
|
||||||
|
@ -536,7 +523,7 @@ impl YTClient for TvHtml5EmbedClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TvHtml5EmbedClient {
|
impl TvHtml5EmbedClient {
|
||||||
fn new(localization: Arc<Localization>) -> Self {
|
fn new(locale: Arc<Locale>) -> Self {
|
||||||
let http = ClientBuilder::new()
|
let http = ClientBuilder::new()
|
||||||
.user_agent(DEFAULT_UA)
|
.user_agent(DEFAULT_UA)
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
|
@ -544,12 +531,12 @@ impl TvHtml5EmbedClient {
|
||||||
.build()
|
.build()
|
||||||
.expect("unable to build the HTTP client");
|
.expect("unable to build the HTTP client");
|
||||||
|
|
||||||
Self { localization, http }
|
Self { locale, http }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DesktopMusicClient {
|
pub struct DesktopMusicClient {
|
||||||
localization: Arc<Localization>,
|
locale: Arc<Locale>,
|
||||||
http: Client,
|
http: Client,
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
consent_cookie: String,
|
consent_cookie: String,
|
||||||
|
@ -567,12 +554,12 @@ impl YTClient for DesktopMusicClient {
|
||||||
platform: "DESKTOP".to_owned(),
|
platform: "DESKTOP".to_owned(),
|
||||||
original_url: Some("https://music.youtube.com/".to_owned()),
|
original_url: Some("https://music.youtube.com/".to_owned()),
|
||||||
hl: match localized {
|
hl: match localized {
|
||||||
true => self.localization.language,
|
true => self.locale.lang.to_owned(),
|
||||||
false => Language::En,
|
false => "en".to_owned(),
|
||||||
},
|
},
|
||||||
gl: match localized {
|
gl: match localized {
|
||||||
true => self.localization.content_country,
|
true => self.locale.country.to_owned(),
|
||||||
false => Country::Us,
|
false => "US".to_owned(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: Some(RequestYT::default()),
|
request: Some(RequestYT::default()),
|
||||||
|
@ -610,7 +597,7 @@ impl YTClient for DesktopMusicClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopMusicClient {
|
impl DesktopMusicClient {
|
||||||
fn new(localization: Arc<Localization>, cache: Cache) -> Self {
|
fn new(locale: Arc<Locale>, cache: Cache) -> Self {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let http = ClientBuilder::new()
|
let http = ClientBuilder::new()
|
||||||
|
@ -621,7 +608,7 @@ impl DesktopMusicClient {
|
||||||
.expect("unable to build the HTTP client");
|
.expect("unable to build the HTTP client");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
localization,
|
locale,
|
||||||
http,
|
http,
|
||||||
cache,
|
cache,
|
||||||
consent_cookie: format!(
|
consent_cookie: format!(
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use chrono::{NaiveDateTime, NaiveTime, TimeZone, Utc};
|
use chrono::{DateTime, NaiveDateTime, NaiveTime, Utc};
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -16,6 +16,8 @@ use url::Url;
|
||||||
use super::{response, ClientType, ContextYT, RustyTube, YTClient};
|
use super::{response, ClientType, ContextYT, RustyTube, YTClient};
|
||||||
use crate::{client::response::player, deobfuscate::Deobfuscator, model::*, util};
|
use crate::{client::response::player, deobfuscate::Deobfuscator, model::*, util};
|
||||||
|
|
||||||
|
// REQUEST
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QPlayer {
|
struct QPlayer {
|
||||||
|
@ -367,7 +369,7 @@ fn map_player_data(response: response::Player, deobf: &Deobfuscator) -> Result<V
|
||||||
},
|
},
|
||||||
publish_date: microformat.as_ref().map(|m| {
|
publish_date: microformat.as_ref().map(|m| {
|
||||||
let ndt = NaiveDateTime::new(m.publish_date, NaiveTime::from_hms(0, 0, 0));
|
let ndt = NaiveDateTime::new(m.publish_date, NaiveTime::from_hms(0, 0, 0));
|
||||||
Utc.from_local_datetime(&ndt).unwrap()
|
DateTime::from_utc(ndt, Utc)
|
||||||
}),
|
}),
|
||||||
view_count: video_details.view_count,
|
view_count: video_details.view_count,
|
||||||
keywords: video_details
|
keywords: video_details
|
||||||
|
@ -530,9 +532,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assert equality within 10% margin
|
/// Assert equality within 10% margin
|
||||||
fn assert_approx(left: f64, right: f64) {
|
fn assert_approx(left: u32, right: u32) {
|
||||||
if left != right {
|
if left != right {
|
||||||
let f = left / right;
|
let f = left as f64 / right as f64;
|
||||||
assert!(
|
assert!(
|
||||||
0.9 < f && f < 1.1,
|
0.9 < f && f < 1.1,
|
||||||
"{} not within 10% margin of {}",
|
"{} not within 10% margin of {}",
|
||||||
|
@ -593,7 +595,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Bitrates may change between requests
|
// Bitrates may change between requests
|
||||||
assert_approx(video.bitrate as f64, 1507068.0);
|
assert_approx(video.bitrate, 1507068);
|
||||||
assert_eq!(video.average_bitrate, 1345149);
|
assert_eq!(video.average_bitrate, 1345149);
|
||||||
assert_eq!(video.size, 43553412);
|
assert_eq!(video.size, 43553412);
|
||||||
assert_eq!(video.width, 1280);
|
assert_eq!(video.width, 1280);
|
||||||
|
@ -605,7 +607,7 @@ mod tests {
|
||||||
assert_eq!(video.format, VideoFormat::Webm);
|
assert_eq!(video.format, VideoFormat::Webm);
|
||||||
assert_eq!(video.codec, VideoCodec::Vp9);
|
assert_eq!(video.codec, VideoCodec::Vp9);
|
||||||
|
|
||||||
assert_approx(audio.bitrate as f64, 130685.0);
|
assert_approx(audio.bitrate, 130685);
|
||||||
assert_eq!(audio.average_bitrate, 129496);
|
assert_eq!(audio.average_bitrate, 129496);
|
||||||
assert_eq!(audio.size, 4193863);
|
assert_eq!(audio.size, 4193863);
|
||||||
assert_eq!(audio.mime, "audio/mp4; codecs=\"mp4a.40.2\"");
|
assert_eq!(audio.mime, "audio/mp4; codecs=\"mp4a.40.2\"");
|
||||||
|
@ -623,9 +625,9 @@ mod tests {
|
||||||
.find(|s| s.itag == 251)
|
.find(|s| s.itag == 251)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_approx(video.bitrate as f64, 1340829.0);
|
assert_approx(video.bitrate, 1340829);
|
||||||
assert_approx(video.average_bitrate as f64, 1233444.0);
|
assert_eq!(video.average_bitrate, 1233444);
|
||||||
assert_approx(video.size as f64, 39936630.0);
|
assert_eq!(video.size, 39936630);
|
||||||
assert_eq!(video.width, 1280);
|
assert_eq!(video.width, 1280);
|
||||||
assert_eq!(video.height, 720);
|
assert_eq!(video.height, 720);
|
||||||
assert_eq!(video.fps, 30);
|
assert_eq!(video.fps, 30);
|
||||||
|
@ -636,9 +638,9 @@ mod tests {
|
||||||
assert_eq!(video.codec, VideoCodec::Av01);
|
assert_eq!(video.codec, VideoCodec::Av01);
|
||||||
assert_eq!(video.throttled, false);
|
assert_eq!(video.throttled, false);
|
||||||
|
|
||||||
assert_approx(audio.bitrate as f64, 142718.0);
|
assert_approx(audio.bitrate, 142718);
|
||||||
assert_approx(audio.average_bitrate as f64, 130708.0);
|
assert_eq!(audio.average_bitrate, 130708);
|
||||||
assert_approx(audio.size as f64, 4232344.0);
|
assert_eq!(audio.size, 4232344);
|
||||||
assert_eq!(audio.mime, "audio/webm; codecs=\"opus\"");
|
assert_eq!(audio.mime, "audio/webm; codecs=\"opus\"");
|
||||||
assert_eq!(audio.format, AudioFormat::Webm);
|
assert_eq!(audio.format, AudioFormat::Webm);
|
||||||
assert_eq!(audio.codec, AudioCodec::Opus);
|
assert_eq!(audio.codec, AudioCodec::Opus);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// REQUEST
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -16,11 +18,10 @@ struct QPlaylist {
|
||||||
browse_id: String,
|
browse_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
pub struct TmpEntry {
|
||||||
struct QPlaylistCont {
|
pub title: String,
|
||||||
context: ContextYT,
|
pub video_id: String,
|
||||||
continuation: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RustyTube {
|
impl RustyTube {
|
||||||
|
@ -45,51 +46,6 @@ impl RustyTube {
|
||||||
|
|
||||||
map_playlist(&playlist_response)
|
map_playlist(&playlist_response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_playlist_cont(&self, playlist: &mut Playlist) -> Result<()> {
|
|
||||||
match &playlist.ctoken {
|
|
||||||
Some(ctoken) => {
|
|
||||||
let client = self.get_ytclient(ClientType::Desktop);
|
|
||||||
let context = client.get_context(true).await;
|
|
||||||
|
|
||||||
let request_body = QPlaylistCont {
|
|
||||||
context,
|
|
||||||
continuation: ctoken.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.request_builder(Method::POST, "browse")
|
|
||||||
.await
|
|
||||||
.json(&request_body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
let cont_response = resp.json::<response::playlist::PlaylistCont>().await?;
|
|
||||||
|
|
||||||
let action = some_or_bail!(
|
|
||||||
cont_response
|
|
||||||
.on_response_received_actions
|
|
||||||
.iter()
|
|
||||||
.find(|a| a.append_continuation_items_action.target_id == playlist.id),
|
|
||||||
Err(anyhow!("no continuation action"))
|
|
||||||
);
|
|
||||||
|
|
||||||
let (mut videos, ctoken) =
|
|
||||||
map_playlist_items(&action.append_continuation_items_action.continuation_items);
|
|
||||||
|
|
||||||
playlist.videos.append(&mut videos);
|
|
||||||
playlist.ctoken = ctoken;
|
|
||||||
|
|
||||||
if playlist.ctoken.is_none() {
|
|
||||||
playlist.n_videos = playlist.videos.len() as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => Err(anyhow!("no ctoken")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_playlist(response: &response::Playlist) -> Result<Playlist> {
|
fn map_playlist(response: &response::Playlist) -> Result<Playlist> {
|
||||||
|
@ -118,7 +74,49 @@ fn map_playlist(response: &response::Playlist) -> Result<Playlist> {
|
||||||
.playlist_video_list_renderer
|
.playlist_video_list_renderer
|
||||||
.contents;
|
.contents;
|
||||||
|
|
||||||
let (videos, ctoken) = map_playlist_items(video_items);
|
let mut ctoken: Option<String> = None;
|
||||||
|
let videos = video_items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|it| match it {
|
||||||
|
response::playlist::PlaylistVideoItem::PlaylistVideoRenderer { video } => {
|
||||||
|
match &video.channel {
|
||||||
|
TextLink::Browse {
|
||||||
|
text,
|
||||||
|
page_type,
|
||||||
|
browse_id,
|
||||||
|
} => match page_type {
|
||||||
|
PageType::Channel => Some(Video {
|
||||||
|
id: video.video_id.to_owned(),
|
||||||
|
title: video.title.to_owned(),
|
||||||
|
length: video.length_seconds,
|
||||||
|
thumbnails: video
|
||||||
|
.thumbnail
|
||||||
|
.thumbnails
|
||||||
|
.iter()
|
||||||
|
.map(|t| Thumbnail {
|
||||||
|
url: t.url.to_owned(),
|
||||||
|
width: t.width,
|
||||||
|
height: t.height,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
channel: Channel {
|
||||||
|
id: browse_id.to_string(),
|
||||||
|
name: text.to_owned(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response::playlist::PlaylistVideoItem::ContinuationItemRenderer {
|
||||||
|
continuation_endpoint,
|
||||||
|
} => {
|
||||||
|
ctoken = Some(continuation_endpoint.continuation_command.token.to_owned());
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let thumbnail_renderer = some_or_bail!(
|
let thumbnail_renderer = some_or_bail!(
|
||||||
response
|
response
|
||||||
|
@ -153,7 +151,7 @@ fn map_playlist(response: &response::Playlist) -> Result<Playlist> {
|
||||||
match &response.header.playlist_header_renderer.num_videos_text {
|
match &response.header.playlist_header_renderer.num_videos_text {
|
||||||
Text::Multiple { runs } =>
|
Text::Multiple { runs } =>
|
||||||
if runs.len() == 2 && runs[1] == " videos" {
|
if runs.len() == 2 && runs[1] == " videos" {
|
||||||
runs[0].replace(",", "").replace(".", "").parse().ok()
|
runs[0].parse().ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
@ -177,11 +175,6 @@ fn map_playlist(response: &response::Playlist) -> Result<Playlist> {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let id = response
|
|
||||||
.header
|
|
||||||
.playlist_header_renderer
|
|
||||||
.playlist_id
|
|
||||||
.to_owned();
|
|
||||||
let name = response.header.playlist_header_renderer.title.to_owned();
|
let name = response.header.playlist_header_renderer.title.to_owned();
|
||||||
let description = response
|
let description = response
|
||||||
.header
|
.header
|
||||||
|
@ -208,65 +201,16 @@ fn map_playlist(response: &response::Playlist) -> Result<Playlist> {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Playlist {
|
Ok(Playlist {
|
||||||
id,
|
|
||||||
name,
|
|
||||||
videos,
|
videos,
|
||||||
n_videos,
|
n_videos,
|
||||||
ctoken,
|
ctoken,
|
||||||
|
name,
|
||||||
thumbnails,
|
thumbnails,
|
||||||
description,
|
description,
|
||||||
channel,
|
channel,
|
||||||
last_update: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_playlist_items(
|
|
||||||
items: &Vec<response::VideoListItem<response::playlist::PlaylistVideo>>,
|
|
||||||
) -> (Vec<Video>, Option<String>) {
|
|
||||||
let mut ctoken: Option<String> = None;
|
|
||||||
let videos = items
|
|
||||||
.iter()
|
|
||||||
.filter_map(|it| match it {
|
|
||||||
response::VideoListItem::GridVideoRenderer { video } => match &video.channel {
|
|
||||||
TextLink::Browse {
|
|
||||||
text,
|
|
||||||
page_type,
|
|
||||||
browse_id,
|
|
||||||
} => match page_type {
|
|
||||||
PageType::Channel => Some(Video {
|
|
||||||
id: video.video_id.to_owned(),
|
|
||||||
title: video.title.to_owned(),
|
|
||||||
length: video.length_seconds,
|
|
||||||
thumbnails: video
|
|
||||||
.thumbnail
|
|
||||||
.thumbnails
|
|
||||||
.iter()
|
|
||||||
.map(|t| Thumbnail {
|
|
||||||
url: t.url.to_owned(),
|
|
||||||
width: t.width,
|
|
||||||
height: t.height,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
channel: Channel {
|
|
||||||
id: browse_id.to_string(),
|
|
||||||
name: text.to_owned(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
response::VideoListItem::ContinuationItemRenderer {
|
|
||||||
continuation_endpoint,
|
|
||||||
} => {
|
|
||||||
ctoken = Some(continuation_endpoint.continuation_command.token.to_owned());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
(videos, ctoken)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs::File, io::BufReader, path::Path};
|
use std::{fs::File, io::BufReader, path::Path};
|
||||||
|
@ -355,7 +299,6 @@ mod tests {
|
||||||
let rt = RustyTube::new();
|
let rt = RustyTube::new();
|
||||||
let playlist = rt.get_playlist(id).await.unwrap();
|
let playlist = rt.get_playlist(id).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(playlist.id, id);
|
|
||||||
assert_eq!(playlist.name, name);
|
assert_eq!(playlist.name, name);
|
||||||
assert!(!playlist.videos.is_empty());
|
assert!(!playlist.videos.is_empty());
|
||||||
assert_eq!(playlist.ctoken.is_some(), is_long);
|
assert_eq!(playlist.ctoken.is_some(), is_long);
|
||||||
|
@ -380,19 +323,4 @@ mod tests {
|
||||||
let playlist_data = map_playlist(&playlist).unwrap();
|
let playlist_data = map_playlist(&playlist).unwrap();
|
||||||
insta::assert_yaml_snapshot!(format!("map_playlist_data_{}", name), playlist_data);
|
insta::assert_yaml_snapshot!(format!("map_playlist_data_{}", name), playlist_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test(tokio::test)]
|
|
||||||
async fn t_playlist_cont() {
|
|
||||||
let rt = RustyTube::new();
|
|
||||||
let mut playlist = rt
|
|
||||||
.get_playlist("PLbZIPy20-1pN7mqjckepWF78ndb6ci_qi")
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
while playlist.ctoken.is_some() {
|
|
||||||
rt.get_playlist_cont(&mut playlist).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(playlist.videos.len() > 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_with::serde_as;
|
|
||||||
use serde_with::VecSkipError;
|
|
||||||
|
|
||||||
use super::TimeOverlay;
|
|
||||||
use super::{ContentRenderer, ContentsRenderer, Thumbnails, VideoListItem};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Channel {
|
|
||||||
pub contents: Contents,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Contents {
|
|
||||||
pub two_column_browse_results_renderer: TabsRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TabsRenderer {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub tabs: Vec<TabRendererWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TabRendererWrap {
|
|
||||||
pub tab_renderer: ContentRenderer<SectionListRendererWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SectionListRendererWrap {
|
|
||||||
pub section_list_renderer: ContentsRenderer<ItemSectionRendererWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ItemSectionRendererWrap {
|
|
||||||
pub item_section_renderer: ContentsRenderer<GridRendererWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GridRendererWrap {
|
|
||||||
pub grid_renderer: GridRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GridRenderer {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub items: Vec<VideoListItem<ChannelVideo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChannelVideo {
|
|
||||||
pub video_id: String,
|
|
||||||
pub thumbnail: Thumbnails,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub title: String,
|
|
||||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
|
||||||
pub published_time_text: Option<String>,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub view_count_text: String,
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub thumbnail_overlays: Vec<TimeOverlay>,
|
|
||||||
}
|
|
|
@ -1,16 +1,10 @@
|
||||||
pub mod channel;
|
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod playlist;
|
pub mod playlist;
|
||||||
pub mod playlist_music;
|
pub mod playlist_music;
|
||||||
pub mod video;
|
|
||||||
|
|
||||||
pub use channel::Channel;
|
|
||||||
pub use player::Player;
|
pub use player::Player;
|
||||||
pub use playlist::Playlist;
|
pub use playlist::Playlist;
|
||||||
pub use playlist_music::PlaylistMusic;
|
pub use playlist_music::PlaylistMusic;
|
||||||
pub use video::Video;
|
|
||||||
pub use video::VideoComments;
|
|
||||||
pub use video::VideoRecommendations;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
||||||
|
@ -50,19 +44,10 @@ pub struct Thumbnail {
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum VideoListItem<T> {
|
pub struct ContinuationItemRenderer {
|
||||||
#[serde(alias = "playlistVideoRenderer", alias = "compactVideoRenderer")]
|
pub continuation_endpoint: ContinuationEndpoint,
|
||||||
GridVideoRenderer {
|
|
||||||
#[serde(flatten)]
|
|
||||||
video: T,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ContinuationItemRenderer {
|
|
||||||
continuation_endpoint: ContinuationEndpoint,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -77,77 +62,6 @@ pub struct ContinuationCommand {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Icon {
|
|
||||||
pub icon_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoOwner {
|
|
||||||
pub video_owner_renderer: VideoOwnerRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoOwnerRenderer {
|
|
||||||
#[serde_as(as = "crate::serializer::text::TextLink")]
|
|
||||||
pub title: TextLink,
|
|
||||||
pub thumbnail: Thumbnails,
|
|
||||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
|
||||||
pub subscriber_count_text: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub badges: Vec<UserBadge>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct UserBadge {
|
|
||||||
pub metadata_badge_renderer: UserBadgeRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct UserBadgeRenderer {
|
|
||||||
pub style: UserBadgeStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum UserBadgeStyle {
|
|
||||||
BadgeStyleTypeVerified,
|
|
||||||
BadgeStyleTypeVerifiedArtist,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TimeOverlay {
|
|
||||||
pub thumbnail_overlay_time_status_renderer: TimeOverlayRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TimeOverlayRenderer {
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub text: String,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
|
||||||
pub style: TimeOverlayStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum TimeOverlayStyle {
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
Live,
|
|
||||||
Shorts,
|
|
||||||
}
|
|
||||||
|
|
||||||
// YouTube Music
|
// YouTube Music
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
@ -158,8 +72,10 @@ pub struct MusicItem {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||||
pub playlist_item_data: Option<PlaylistItemData>,
|
pub playlist_item_data: Option<PlaylistItemData>,
|
||||||
|
#[serde(default)]
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub flex_columns: Vec<MusicColumn>,
|
pub flex_columns: Vec<MusicColumn>,
|
||||||
|
#[serde(default)]
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub fixed_columns: Vec<MusicColumn>,
|
pub fixed_columns: Vec<MusicColumn>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,27 @@ pub enum PlayabilityStatus {
|
||||||
Ok { live_streamability: Option<Empty> },
|
Ok { live_streamability: Option<Empty> },
|
||||||
/// Video cant be played because of DRM / Geoblock
|
/// Video cant be played because of DRM / Geoblock
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
Unplayable { reason: String },
|
Unplayable {
|
||||||
|
reason: String,
|
||||||
|
// error_screen: Option<ErrorScreen>,
|
||||||
|
},
|
||||||
/// Age limit / Private video
|
/// Age limit / Private video
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
LoginRequired { reason: String },
|
LoginRequired {
|
||||||
|
reason: String,
|
||||||
|
// error_screen: Option<ErrorScreen>
|
||||||
|
},
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
LiveStreamOffline { reason: String },
|
LiveStreamOffline {
|
||||||
|
reason: String,
|
||||||
|
// error_screen: Option<ErrorScreen>
|
||||||
|
},
|
||||||
/// Video was censored / deleted
|
/// Video was censored / deleted
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
Error { reason: String },
|
Error {
|
||||||
|
reason: String,
|
||||||
|
// error_screen: Option<ErrorScreen>
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
|
|
@ -4,9 +4,7 @@ use serde_with::{json::JsonString, DefaultOnError, VecSkipError};
|
||||||
|
|
||||||
use crate::serializer::text::{Text, TextLink};
|
use crate::serializer::text::{Text, TextLink};
|
||||||
|
|
||||||
use super::{
|
use super::{ContentRenderer, ContentsRenderer, ContinuationEndpoint, Thumbnails, ThumbnailsWrap};
|
||||||
ContentRenderer, ContentsRenderer, Thumbnails, ThumbnailsWrap, VideoListItem, VideoOwner,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -16,14 +14,6 @@ pub struct Playlist {
|
||||||
pub sidebar: Sidebar,
|
pub sidebar: Sidebar,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PlaylistCont {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Contents {
|
pub struct Contents {
|
||||||
|
@ -59,7 +49,21 @@ pub struct PlaylistVideoListRenderer {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistVideoList {
|
pub struct PlaylistVideoList {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub contents: Vec<VideoListItem<PlaylistVideo>>,
|
pub contents: Vec<PlaylistVideoItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum PlaylistVideoItem {
|
||||||
|
PlaylistVideoRenderer {
|
||||||
|
#[serde(flatten)]
|
||||||
|
video: PlaylistVideo,
|
||||||
|
},
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
ContinuationItemRenderer {
|
||||||
|
continuation_endpoint: ContinuationEndpoint,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
@ -75,6 +79,7 @@ pub struct PlaylistVideo {
|
||||||
pub channel: TextLink,
|
pub channel: TextLink,
|
||||||
#[serde_as(as = "JsonString")]
|
#[serde_as(as = "JsonString")]
|
||||||
pub length_seconds: u32,
|
pub length_seconds: u32,
|
||||||
|
pub is_playable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -123,7 +128,7 @@ pub enum SidebarRendererItem {
|
||||||
// stats: Vec<Text>,
|
// stats: Vec<Text>,
|
||||||
},
|
},
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
PlaylistSidebarSecondaryInfoRenderer { video_owner: VideoOwner },
|
PlaylistSidebarSecondaryInfoRenderer { video_owner: VideoOwnerWrap },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -136,15 +141,14 @@ pub struct PlaylistThumbnailRenderer {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OnResponseReceivedAction {
|
pub struct VideoOwnerWrap {
|
||||||
pub append_continuation_items_action: AppendAction,
|
pub video_owner_renderer: VideoOwner,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppendAction {
|
pub struct VideoOwner {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "crate::serializer::text::TextLink")]
|
||||||
pub continuation_items: Vec<VideoListItem<PlaylistVideo>>,
|
pub title: TextLink,
|
||||||
pub target_id: String,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,432 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_with::serde_as;
|
|
||||||
use serde_with::{DefaultOnError, VecSkipError};
|
|
||||||
|
|
||||||
use crate::serializer::text::TextLink;
|
|
||||||
|
|
||||||
use super::{ContinuationEndpoint, Icon, Thumbnails, VideoListItem, VideoOwner};
|
|
||||||
|
|
||||||
/// Video info response
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Video {
|
|
||||||
pub contents: Contents,
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub engagement_panels: Vec<EngagementPanel>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Video recommendations response
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoRecommendations {
|
|
||||||
pub on_response_received_endpoints: Vec<RecommendationsContItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Video comments response
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoComments {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub on_response_received_endpoints: Vec<CommentsContItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Contents {
|
|
||||||
pub two_column_watch_next_results: TwoColumnWatchNextResults,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TwoColumnWatchNextResults {
|
|
||||||
pub results: VideoResultsWrap,
|
|
||||||
pub secondary_results: RecommendationResultsWrap,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoResultsWrap {
|
|
||||||
pub results: VideoResults,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoResults {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub contents: Vec<VideoResultsItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum VideoResultsItem {
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
VideoPrimaryInfoRenderer {
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
title: String,
|
|
||||||
view_count: ViewCountWrap,
|
|
||||||
video_actions: VideoActions,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
date_text: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
VideoSecondaryInfoRenderer {
|
|
||||||
owner: VideoOwner,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
description: String,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
|
||||||
metadata_row_container: Option<MetadataRowContainer>,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ItemSectionRenderer {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
contents: Vec<ItemSection>,
|
|
||||||
section_identifier: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ViewCountWrap {
|
|
||||||
pub video_view_count_renderer: ViewCount,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ViewCount {
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub view_count: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoActions {
|
|
||||||
pub menu_renderer: VideoActionsMenu,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoActionsMenu {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub top_level_buttons: Vec<ToggleButtonWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ToggleButtonWrap {
|
|
||||||
pub toggle_button_renderer: ToggleButton,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ToggleButton {
|
|
||||||
pub default_icon: Icon,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub default_text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct MetadataRowContainer {
|
|
||||||
pub metadata_row_container_renderer: MetadataRowContainerRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct MetadataRowContainerRenderer {
|
|
||||||
pub rows: Vec<MetadataRow>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct MetadataRow {
|
|
||||||
pub metadata_row_renderer: MetadataRowRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct MetadataRowRenderer {
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub title: String,
|
|
||||||
#[serde_as(as = "Vec<crate::serializer::text::TextLinks>")]
|
|
||||||
pub contents: Vec<Vec<TextLink>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum ItemSection {
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
CommentsEntryPointHeaderRenderer {
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
header_text: String,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
comment_count: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ContinuationItemRenderer {
|
|
||||||
continuation_endpoint: ContinuationEndpoint,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RecommendationResultsWrap {
|
|
||||||
pub secondary_results: RecommendationResults,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RecommendationResults {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub results: Vec<VideoListItem<RecommendedVideo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RecommendedVideo {
|
|
||||||
pub video_id: String,
|
|
||||||
pub thumbnail: Thumbnails,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub title: String,
|
|
||||||
#[serde(rename = "shortBylineText")]
|
|
||||||
#[serde_as(as = "crate::serializer::text::TextLink")]
|
|
||||||
pub channel: TextLink,
|
|
||||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
|
||||||
pub length_text: Option<String>,
|
|
||||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
|
||||||
pub published_time_text: Option<String>,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub view_count_text: String,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub badges: Vec<VideoBadge>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoBadge {
|
|
||||||
pub metadata_badge_renderer: VideoBadgeRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoBadgeRenderer {
|
|
||||||
pub style: VideoBadgeStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum VideoBadgeStyle {
|
|
||||||
BadgeStyleTypeLiveNow,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanel {
|
|
||||||
pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanelRenderer {
|
|
||||||
pub header: EngagementPanelHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanelHeader {
|
|
||||||
pub engagement_panel_title_header_renderer: EngagementPanelHeaderRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanelHeaderRenderer {
|
|
||||||
pub menu: EngagementPanelMenu,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanelMenu {
|
|
||||||
pub sort_filter_sub_menu_renderer: EngagementPanelMenuRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanelMenuRenderer {
|
|
||||||
pub sub_menu_items: Vec<EngagementPanelMenuItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EngagementPanelMenuItem {
|
|
||||||
pub service_endpoint: ContinuationEndpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RecommendationsContItem {
|
|
||||||
pub append_continuation_items_action: AppendRecommendations,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AppendRecommendations {
|
|
||||||
pub continuation_items: Vec<VideoListItem<RecommendedVideo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CommentsContItem {
|
|
||||||
#[serde(alias = "reloadContinuationItemsCommand")]
|
|
||||||
pub append_continuation_items_action: AppendComments,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AppendComments {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub continuation_items: Vec<CommentListItem>,
|
|
||||||
pub target_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum CommentListItem {
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
CommentThreadRenderer {
|
|
||||||
comment: Comment,
|
|
||||||
#[serde(default)]
|
|
||||||
replies: Replies,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
|
||||||
rendering_priority: CommentPriority,
|
|
||||||
},
|
|
||||||
CommentRenderer {
|
|
||||||
#[serde(flatten)]
|
|
||||||
comment: CommentRenderer,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ContinuationItemRenderer {
|
|
||||||
continuation_endpoint: ContinuationEndpoint,
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: TMP
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
CommentsHeaderRenderer { count_text: Option<String> },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Comment {
|
|
||||||
pub comment_renderer: CommentRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CommentRenderer {
|
|
||||||
// There may be comments with missing authors (possibly deleted users?)
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "DefaultOnError<Option<crate::serializer::text::Text>>")]
|
|
||||||
pub author_text: Option<String>,
|
|
||||||
pub author_thumbnail: Thumbnails,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "DefaultOnError")]
|
|
||||||
pub author_endpoint: Option<AuthorEndpoint>,
|
|
||||||
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub content_text: String,
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
pub published_time_text: String,
|
|
||||||
pub comment_id: String,
|
|
||||||
pub author_is_channel_owner: bool,
|
|
||||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
|
||||||
pub vote_count: Option<String>,
|
|
||||||
pub author_comment_badge: Option<AuthorCommentBadge>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub reply_count: u32,
|
|
||||||
pub action_buttons: CommentActionButtons,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AuthorEndpoint {
|
|
||||||
pub browse_endpoint: BrowseEndpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct BrowseEndpoint {
|
|
||||||
pub browse_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum CommentPriority {
|
|
||||||
#[default]
|
|
||||||
RenderingPriorityUnknown,
|
|
||||||
RenderingPriorityPinnedComment,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Replies {
|
|
||||||
pub comment_replies_renderer: RepliesRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Default, Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RepliesRenderer {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub contents: Vec<CommentListItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CommentActionButtons {
|
|
||||||
pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CommentActionButtonsRenderer {
|
|
||||||
pub creator_heart: Option<CreatorHeart>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CreatorHeart {
|
|
||||||
pub creator_heart_renderer: CreatorHeartRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CreatorHeartRenderer {
|
|
||||||
pub is_hearted: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AuthorCommentBadge {
|
|
||||||
pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct AuthorCommentBadgeRenderer {
|
|
||||||
pub icon: Icon,
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@
|
||||||
source: src/client/playlist.rs
|
source: src/client/playlist.rs
|
||||||
expression: playlist_data
|
expression: playlist_data
|
||||||
---
|
---
|
||||||
id: PL5dDx681T4bR7ZF1IuWzOv1omlRbE7PiJ
|
|
||||||
name: Die schönsten deutschen Lieder | Beliebteste Lieder | Beste Deutsche Musik 2022
|
|
||||||
videos:
|
videos:
|
||||||
- id: Bkj3IVIO2Os
|
- id: Bkj3IVIO2Os
|
||||||
title: Stereoact feat. Kerstin Ott - Die Immer Lacht (Official Video HD)
|
title: Stereoact feat. Kerstin Ott - Die Immer Lacht (Official Video HD)
|
||||||
|
@ -1907,6 +1905,7 @@ videos:
|
||||||
name: KMNGANG
|
name: KMNGANG
|
||||||
n_videos: 495
|
n_videos: 495
|
||||||
ctoken: 4qmFsgJhEiRWTFBMNWREeDY4MVQ0YlI3WkYxSXVXek92MW9tbFJiRTdQaUoaFENBRjZCbEJVT2tOSFdRJTNEJTNEmgIiUEw1ZER4NjgxVDRiUjdaRjFJdVd6T3Yxb21sUmJFN1BpSg%3D%3D
|
ctoken: 4qmFsgJhEiRWTFBMNWREeDY4MVQ0YlI3WkYxSXVXek92MW9tbFJiRTdQaUoaFENBRjZCbEJVT2tOSFdRJTNEJTNEmgIiUEw1ZER4NjgxVDRiUjdaRjFJdVd6T3Yxb21sUmJFN1BpSg%3D%3D
|
||||||
|
name: Die schönsten deutschen Lieder | Beliebteste Lieder | Beste Deutsche Musik 2022
|
||||||
thumbnails:
|
thumbnails:
|
||||||
- url: "https://i.ytimg.com/vi/Bkj3IVIO2Os/hqdefault.jpg?sqp=-oaymwEWCKgBEF5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLBShlXfy9oWvTy2ntoHxmuhBlZP3g"
|
- url: "https://i.ytimg.com/vi/Bkj3IVIO2Os/hqdefault.jpg?sqp=-oaymwEWCKgBEF5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLBShlXfy9oWvTy2ntoHxmuhBlZP3g"
|
||||||
width: 168
|
width: 168
|
||||||
|
@ -1924,5 +1923,4 @@ description: ~
|
||||||
channel:
|
channel:
|
||||||
id: UCIekuFeMaV78xYfvpmoCnPg
|
id: UCIekuFeMaV78xYfvpmoCnPg
|
||||||
name: Best Music
|
name: Best Music
|
||||||
last_update: ~
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
source: src/client/playlist.rs
|
source: src/client/playlist.rs
|
||||||
expression: playlist_data
|
expression: playlist_data
|
||||||
---
|
---
|
||||||
id: PL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe
|
|
||||||
name: Minecraft SHINE
|
|
||||||
videos:
|
videos:
|
||||||
- id: X82TrticM4A
|
- id: X82TrticM4A
|
||||||
title: Minecraft SHINE (Trailer)
|
title: Minecraft SHINE (Trailer)
|
||||||
|
@ -1261,6 +1259,7 @@ videos:
|
||||||
name: Chaosflo44
|
name: Chaosflo44
|
||||||
n_videos: 66
|
n_videos: 66
|
||||||
ctoken: ~
|
ctoken: ~
|
||||||
|
name: Minecraft SHINE
|
||||||
thumbnails:
|
thumbnails:
|
||||||
- url: "https://i.ytimg.com/vi/X82TrticM4A/hqdefault.jpg?sqp=-oaymwEWCKgBEF5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLBeSNDZEaBoP0KX9_Ayn3VO3X8rkw"
|
- url: "https://i.ytimg.com/vi/X82TrticM4A/hqdefault.jpg?sqp=-oaymwEWCKgBEF5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLBeSNDZEaBoP0KX9_Ayn3VO3X8rkw"
|
||||||
width: 168
|
width: 168
|
||||||
|
@ -1278,5 +1277,4 @@ description: "SHINE - Survival Hardcore in New Environment: Auf einem Server mac
|
||||||
channel:
|
channel:
|
||||||
id: UCQM0bS4_04-Y4JuYrgmnpZQ
|
id: UCQM0bS4_04-Y4JuYrgmnpZQ
|
||||||
name: Chaosflo44
|
name: Chaosflo44
|
||||||
last_update: ~
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
source: src/client/playlist.rs
|
source: src/client/playlist.rs
|
||||||
expression: playlist_data
|
expression: playlist_data
|
||||||
---
|
---
|
||||||
id: RDCLAK5uy_kFQXdnqMaQCVx2wpUM4ZfbsGCDibZtkJk
|
|
||||||
name: Easy Pop
|
|
||||||
videos:
|
videos:
|
||||||
- id: psuRGfAaju4
|
- id: psuRGfAaju4
|
||||||
title: Owl City - Fireflies (Official Music Video)
|
title: Owl City - Fireflies (Official Music Video)
|
||||||
|
@ -1850,6 +1848,7 @@ videos:
|
||||||
name: Diaven - Topic
|
name: Diaven - Topic
|
||||||
n_videos: 97
|
n_videos: 97
|
||||||
ctoken: ~
|
ctoken: ~
|
||||||
|
name: Easy Pop
|
||||||
thumbnails:
|
thumbnails:
|
||||||
- url: "https://i9.ytimg.com/s_p/RDCLAK5uy_kFQXdnqMaQCVx2wpUM4ZfbsGCDibZtkJk/mqdefault.jpg?sqp=CPDmr5gGir7X7AMGCLaHmpgG&rs=AOn4CLDg9xLeJDPeMtbWfp19VFd6vCQzqQ&v=1661371318"
|
- url: "https://i9.ytimg.com/s_p/RDCLAK5uy_kFQXdnqMaQCVx2wpUM4ZfbsGCDibZtkJk/mqdefault.jpg?sqp=CPDmr5gGir7X7AMGCLaHmpgG&rs=AOn4CLDg9xLeJDPeMtbWfp19VFd6vCQzqQ&v=1661371318"
|
||||||
width: 180
|
width: 180
|
||||||
|
@ -1862,5 +1861,4 @@ thumbnails:
|
||||||
height: 1200
|
height: 1200
|
||||||
description: ~
|
description: ~
|
||||||
channel: ~
|
channel: ~
|
||||||
last_update: ~
|
|
||||||
|
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use reqwest::Method;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use super::{response, ClientType, ContextYT, RustyTube};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
struct QVideo {
|
|
||||||
context: ContextYT,
|
|
||||||
/// YouTube video ID
|
|
||||||
video_id: String,
|
|
||||||
/// Set to true to allow extraction of streams with sensitive content
|
|
||||||
content_check_ok: bool,
|
|
||||||
/// Probably refers to allowing sensitive content, too
|
|
||||||
racy_check_ok: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
struct QVideoCont {
|
|
||||||
context: ContextYT,
|
|
||||||
continuation: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustyTube {
|
|
||||||
pub async fn get_video_response(&self, video_id: &str) -> Result<response::Video> {
|
|
||||||
let client = self.get_ytclient(ClientType::Desktop);
|
|
||||||
let context = client.get_context(true).await;
|
|
||||||
let request_body = QVideo {
|
|
||||||
context,
|
|
||||||
video_id: video_id.to_owned(),
|
|
||||||
content_check_ok: true,
|
|
||||||
racy_check_ok: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.request_builder(Method::POST, "next")
|
|
||||||
.await
|
|
||||||
.json(&request_body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
Ok(resp.json::<response::Video>().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_comments_response(&self, ctoken: &str) -> Result<response::VideoComments> {
|
|
||||||
let client = self.get_ytclient(ClientType::Desktop);
|
|
||||||
let context = client.get_context(true).await;
|
|
||||||
let request_body = QVideoCont {
|
|
||||||
context,
|
|
||||||
continuation: ctoken.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.request_builder(Method::POST, "next")
|
|
||||||
.await
|
|
||||||
.json(&request_body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
Ok(resp.json::<response::VideoComments>().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_recommendations_response(
|
|
||||||
&self,
|
|
||||||
ctoken: &str,
|
|
||||||
) -> Result<response::VideoRecommendations> {
|
|
||||||
let client = self.get_ytclient(ClientType::Desktop);
|
|
||||||
let context = client.get_context(true).await;
|
|
||||||
let request_body = QVideoCont {
|
|
||||||
context,
|
|
||||||
continuation: ctoken.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.request_builder(Method::POST, "next")
|
|
||||||
.await
|
|
||||||
.json(&request_body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
Ok(resp.json::<response::VideoRecommendations>().await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn t_get_video_response() {
|
|
||||||
let rt = RustyTube::new();
|
|
||||||
// rt.get_video("ZeerrnuLi5E").await.unwrap();
|
|
||||||
dbg!(rt.get_video_response("iQfSvIgIs_M").await.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn t_get_comments_response() {
|
|
||||||
let rt = RustyTube::new();
|
|
||||||
// rt.get_comments("Eg0SC2lRZlN2SWdJc19NGAYyJSIRIgtpUWZTdklnSXNfTTAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D").await.unwrap();
|
|
||||||
dbg!(rt.get_comments_response("Eg0SC2lRZlN2SWdJc19NGAYychpFEhRVZ2lnVGJVTEZ6Qk5FWGdDb0FFQyICCAAqGFVDWFgwUldPSUJqdDRvM3ppSHUtNmE1QTILaVFmU3ZJZ0lzX01AAUgKQiljb21tZW50LXJlcGxpZXMtaXRlbS1VZ2lnVGJVTEZ6Qk5FWGdDb0FFQw%3D%3D").await.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn t_get_recommendations_response() {
|
|
||||||
let rt = RustyTube::new();
|
|
||||||
dbg!(rt.get_recommendations_response("CBQSExILaVFmU3ZJZ0lzX03AAQHIAQEYACqkBjJzNkw2d3pVQkFyUkJBb0Q4ajRBQ2c3Q1Bnc0lvWXlRejhLZnRZUGNBUW9EOGo0QUNnN0NQZ3NJeElEX2w0YjFtNnUtQVFvRDhqNEFDZzNDUGdvSXg5Ykx3WUNKenFwX0NnUHlQZ0FLRGNJLUNnaW83T2pqZzVPTHZEOEtBX0ktQUFvTndqNEtDTE9venZmQThybVhXd29EOGo0QUNnM0NQZ29JdzZETV9vSFk0cHRCQ2dQeVBnQUtEc0ktQ3dqbW9QbURpcHVPel80QkNnUHlQZ0FLRGNJLUNnalY4THpEazlfOTRCWUtBX0ktQUFvT3dqNExDTXVZNU9YZzE3ejV2d0VLQV9JLUFBb053ajRLQ1A3eHZiSGswTnVuYWdvRDhqNEFDZzdDUGdzSXFQYVU5ZGp2Ml96S0FRb0Q4ajRBQ2c3Q1Bnc0lfSW1acUtQOTlfQ09BUW9EOGo0QUNnM0NQZ29JeGRtNzlZS3prcUFqQ2dQeVBnQUtEY0ktQ2dpZ3FJMkg0UENRX2s0S0FfSS1BQW9Pd2o0TENQV0V5NV9ZeDhERl9nRUtBX0ktQUFvT3dqNExDTzJid3VuV3BPX3ppd0VLQV9JLUFBb2gwajRlQ2h4U1JFTk5WVU5ZV0RCU1YwOUpRbXAwTkc4emVtbElkUzAyWVRWQkNnUHlQZ0FLRGNJLUNnaXpqcXZwcDh5MWwwMEtBX0ktQUFvTndqNEtDTFhWbl83dHhfWDJOUW9EOGo0QUNnN0NQZ3NJNWR5ZWc1NjZyUGUwQVJJVUFBSUVCZ2dLREE0UUVoUVdHQm9jSGlBaUpDWWFCQWdBRUFFYUJBZ0NFQU1hQkFnRUVBVWFCQWdHRUFjYUJBZ0lFQWthQkFnS0VBc2FCQWdNRUEwYUJBZ09FQThhQkFnUUVCRWFCQWdTRUJNYUJBZ1VFQlVhQkFnV0VCY2FCQWdZRUJrYUJBZ2FFQnNhQkFnY0VCMGFCQWdlRUI4YUJBZ2dFQ0VhQkFnaUVDTWFCQWdrRUNVYUJBZ21FQ2NxRkFBQ0JBWUlDZ3dPRUJJVUZoZ2FIQjRnSWlRbWoPd2F0Y2gtbmV4dC1mZWVk").await.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
#![cfg(test)]
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::BTreeMap,
|
|
||||||
fmt::Debug,
|
|
||||||
fs::File,
|
|
||||||
io::{BufReader},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{model::Language, timeago::TimeUnit};
|
|
||||||
use fancy_regex::Regex;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
const DICT_PATH: &str = "testfiles/date/dictionary.json";
|
|
||||||
const TARGET_FILE: &str = "src/dictionary.rs";
|
|
||||||
|
|
||||||
type Dictionary = BTreeMap<Language, DictEntry>;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct DictEntry {
|
|
||||||
#[serde(default)]
|
|
||||||
equivalent: Vec<Language>,
|
|
||||||
#[serde(default)]
|
|
||||||
by_char: bool,
|
|
||||||
timeago_tokens: BTreeMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_tu(tu: &str) -> (u8, Option<TimeUnit>) {
|
|
||||||
static TU_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\d*)(\w?)$").unwrap());
|
|
||||||
match TU_PATTERN.captures(tu).unwrap() {
|
|
||||||
Some(cap) => (
|
|
||||||
cap.get(1).unwrap().as_str().parse().unwrap_or(1),
|
|
||||||
match cap.get(2).unwrap().as_str() {
|
|
||||||
"s" => Some(TimeUnit::Second),
|
|
||||||
"m" => Some(TimeUnit::Minute),
|
|
||||||
"h" => Some(TimeUnit::Hour),
|
|
||||||
"D" => Some(TimeUnit::Day),
|
|
||||||
"W" => Some(TimeUnit::Week),
|
|
||||||
"M" => Some(TimeUnit::Month),
|
|
||||||
"Y" => Some(TimeUnit::Year),
|
|
||||||
"" => None,
|
|
||||||
_ => panic!("invalid time unit: {}", tu),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
None => panic!("invalid time unit: {}", tu),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_dict() -> Dictionary {
|
|
||||||
let json_file = File::open(DICT_PATH).unwrap();
|
|
||||||
serde_json::from_reader(BufReader::new(json_file)).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
fn generate_dictionary() {
|
|
||||||
let dict = read_dict();
|
|
||||||
|
|
||||||
let code_head = r#"// This file is automatically generated. DO NOT EDIT.
|
|
||||||
use crate::{
|
|
||||||
model::Language,
|
|
||||||
timeago::{TaToken, TimeUnit},
|
|
||||||
};
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let mut code_timeago_tokens = r#"#[rustfmt::skip]
|
|
||||||
pub(crate) fn get_timeago_tokens(lang: Language) -> phf::Map<&'static str, TaToken> {
|
|
||||||
match lang {
|
|
||||||
"#
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
dict.iter().for_each(|(lang, entry)| {
|
|
||||||
// Create a map for the language
|
|
||||||
let mut map = phf_codegen::Map::<&str>::new();
|
|
||||||
|
|
||||||
entry.timeago_tokens.iter().for_each(|(txt, tu_str)| {
|
|
||||||
let (n, unit) = parse_tu(&tu_str);
|
|
||||||
match unit {
|
|
||||||
Some(unit) => map.entry(
|
|
||||||
&txt,
|
|
||||||
&format!("TaToken {{ n: {}, unit: Some(TimeUnit::{:?}) }}", n, unit),
|
|
||||||
),
|
|
||||||
None => map.entry(&txt, &format!("TaToken {{ n: {}, unit: None }}", n)),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut selector = format!("Language::{:?}", lang);
|
|
||||||
entry.equivalent.iter().for_each(|eq| {
|
|
||||||
selector += &format!(" | Language::{:?}", eq);
|
|
||||||
});
|
|
||||||
|
|
||||||
let code_map = &map.build().to_string().replace('\n', "\n ");
|
|
||||||
|
|
||||||
code_timeago_tokens += &format!("{} => {},\n ", selector, code_map);
|
|
||||||
});
|
|
||||||
|
|
||||||
code_timeago_tokens = code_timeago_tokens.trim_end().to_owned() + "\n }\n}\n";
|
|
||||||
|
|
||||||
let code = format!("{}\n{}", code_head, code_timeago_tokens);
|
|
||||||
|
|
||||||
std::fs::write(TARGET_FILE, code).unwrap();
|
|
||||||
}
|
|
|
@ -1,351 +0,0 @@
|
||||||
#![cfg(test)]
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use reqwest::Method;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::serde_as;
|
|
||||||
use serde_with::VecSkipError;
|
|
||||||
|
|
||||||
use crate::client::{ClientType, ContextYT, RustyTube};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct QLanguageMenu {
|
|
||||||
context: ContextYT,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct LanguageMenu {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
actions: Vec<ActionWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ActionWrap {
|
|
||||||
open_popup_action: OpenPopupAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct OpenPopupAction {
|
|
||||||
popup: Popup,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Popup {
|
|
||||||
multi_page_menu_renderer: MultiPageMenuRenderer<MenuSectionRenderer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct MultiPageMenuRenderer<T> {
|
|
||||||
sections: Vec<MenuSectionRendererWrap<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct MenuSectionRendererWrap<T> {
|
|
||||||
multi_page_menu_section_renderer: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct MenuSectionRenderer {
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
items: Vec<CompactLinkRendererWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct CompactLinkRendererWrap {
|
|
||||||
compact_link_renderer: CompactLinkRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct CompactLinkRenderer {
|
|
||||||
icon: Icon,
|
|
||||||
service_endpoint: ServiceEndpoint<MenuAction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Icon {
|
|
||||||
pub icon_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ServiceEndpoint<T> {
|
|
||||||
signal_service_endpoint: SignalServiceEndpoint<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct SignalServiceEndpoint<T> {
|
|
||||||
actions: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct MenuAction {
|
|
||||||
get_multi_page_menu_action: MultiPageMenuAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct MultiPageMenuAction {
|
|
||||||
menu: Menu,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Menu {
|
|
||||||
multi_page_menu_renderer: MultiPageMenuRenderer<ItemSectionRenderer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ItemSectionRenderer {
|
|
||||||
items: Vec<LanguageItemWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct LanguageItemWrap {
|
|
||||||
compact_link_renderer: LanguageItem,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct LanguageItem {
|
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
|
||||||
title: String,
|
|
||||||
service_endpoint: ServiceEndpoint<LanguageCountryAction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct LanguageCountryAction {
|
|
||||||
#[serde(alias = "selectCountryCommand")]
|
|
||||||
select_language_command: LanguageCountryCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
struct LanguageCountryCommand {
|
|
||||||
#[serde(alias = "gl")]
|
|
||||||
hl: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test_log::test(tokio::test)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
async fn generate_locales() {
|
|
||||||
let (languages, countries) = get_locales().await;
|
|
||||||
|
|
||||||
let code_head = r#"// This file is automatically generated. DO NOT EDIT.
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let code_foot = r#"impl Display for Language {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(
|
|
||||||
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Country {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(
|
|
||||||
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Language {
|
|
||||||
type Err = serde_json::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
serde_json::from_str(&format!("\"{}\"", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Country {
|
|
||||||
type Err = serde_json::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
serde_json::from_str(&format!("\"{}\"", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let mut code_langs = r#"#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum Language {
|
|
||||||
"#.to_owned();
|
|
||||||
|
|
||||||
let mut code_countries = r#"#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
|
||||||
pub enum Country {
|
|
||||||
"#.to_owned();
|
|
||||||
|
|
||||||
let mut code_lang_array = format!("pub const LANGUAGES: [Language; {}] = [\n", languages.len());
|
|
||||||
let mut code_country_array = format!("pub const COUNTRIES: [Country; {}] = [\n", countries.len());
|
|
||||||
|
|
||||||
let mut code_lang_names = r#"impl Language {
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
"#
|
|
||||||
.to_owned();
|
|
||||||
let mut code_country_names = r#"impl Country {
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
"#
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
languages.iter().for_each(|(c, n)| {
|
|
||||||
let enum_name = c
|
|
||||||
.split('-')
|
|
||||||
.map(|c| {
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
c[0..1].to_owned().to_uppercase(),
|
|
||||||
c[1..].to_owned().to_lowercase()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
// Language enum
|
|
||||||
code_langs += &format!(" /// {}\n ", n);
|
|
||||||
if c.contains('-') {
|
|
||||||
code_langs += &format!("#[serde(rename = \"{}\")]\n ", c);
|
|
||||||
}
|
|
||||||
code_langs += &enum_name;
|
|
||||||
code_langs += ",\n";
|
|
||||||
|
|
||||||
// Language array
|
|
||||||
code_lang_array += &format!(" Language::{},\n", enum_name);
|
|
||||||
|
|
||||||
// Language names
|
|
||||||
code_lang_names += &format!(" Language::{} => \"{}\",\n", enum_name, n);
|
|
||||||
});
|
|
||||||
code_langs += "}\n";
|
|
||||||
|
|
||||||
countries.iter().for_each(|(c, n)| {
|
|
||||||
let enum_name = c[0..1].to_owned().to_uppercase() + &c[1..].to_owned().to_lowercase();
|
|
||||||
|
|
||||||
// Country enum
|
|
||||||
code_countries += &format!(" /// {}\n", n);
|
|
||||||
code_countries += &format!(" {},\n", enum_name);
|
|
||||||
|
|
||||||
// Country array
|
|
||||||
code_country_array += &format!(" Country::{},\n", enum_name);
|
|
||||||
|
|
||||||
// Country names
|
|
||||||
code_country_names += &format!(" Country::{} => \"{}\",\n", enum_name, n);
|
|
||||||
});
|
|
||||||
code_countries += "}\n";
|
|
||||||
|
|
||||||
code_lang_array += "];\n";
|
|
||||||
code_country_array += "];\n";
|
|
||||||
code_lang_names += " }\n }\n}\n";
|
|
||||||
code_country_names += " }\n }\n}\n";
|
|
||||||
|
|
||||||
let code = format!(
|
|
||||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
|
||||||
code_head,
|
|
||||||
code_langs,
|
|
||||||
code_countries,
|
|
||||||
code_lang_array,
|
|
||||||
code_country_array,
|
|
||||||
code_lang_names,
|
|
||||||
code_country_names,
|
|
||||||
code_foot,
|
|
||||||
);
|
|
||||||
|
|
||||||
let locale_path = Path::new("src/model/locale.rs");
|
|
||||||
std::fs::write(locale_path, code).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_locales() -> (BTreeMap<String, String>, BTreeMap<String, String>) {
|
|
||||||
let rt = RustyTube::new();
|
|
||||||
let client = rt.get_ytclient(ClientType::Desktop);
|
|
||||||
let context = client.get_context(true).await;
|
|
||||||
|
|
||||||
let request_body = QLanguageMenu { context };
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.request_builder(Method::POST, "account/account_menu")
|
|
||||||
.await
|
|
||||||
.json(&request_body)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.error_for_status()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let language_menu = resp.json::<LanguageMenu>().await.unwrap();
|
|
||||||
|
|
||||||
let lm_section = &language_menu.actions[0]
|
|
||||||
.open_popup_action
|
|
||||||
.popup
|
|
||||||
.multi_page_menu_renderer
|
|
||||||
.sections
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.multi_page_menu_section_renderer.items.len() >= 2)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let lang_section = lm_section
|
|
||||||
.multi_page_menu_section_renderer
|
|
||||||
.items
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.compact_link_renderer.icon.icon_type == "TRANSLATE")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let country_section = lm_section
|
|
||||||
.multi_page_menu_section_renderer
|
|
||||||
.items
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.compact_link_renderer.icon.icon_type == "LANGUAGE")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let languages = map_language_section(lang_section);
|
|
||||||
let countries = map_language_section(country_section);
|
|
||||||
|
|
||||||
(languages, countries)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_language_section(section: &CompactLinkRendererWrap) -> BTreeMap<String, String> {
|
|
||||||
section
|
|
||||||
.compact_link_renderer
|
|
||||||
.service_endpoint
|
|
||||||
.signal_service_endpoint
|
|
||||||
.actions[0]
|
|
||||||
.get_multi_page_menu_action
|
|
||||||
.menu
|
|
||||||
.multi_page_menu_renderer
|
|
||||||
.sections[0]
|
|
||||||
.multi_page_menu_section_renderer
|
|
||||||
.items
|
|
||||||
.iter()
|
|
||||||
.map(|i| {
|
|
||||||
(
|
|
||||||
i.compact_link_renderer
|
|
||||||
.service_endpoint
|
|
||||||
.signal_service_endpoint
|
|
||||||
.actions[0]
|
|
||||||
.select_language_command
|
|
||||||
.hl
|
|
||||||
.to_owned(),
|
|
||||||
i.compact_link_renderer.title.to_owned(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<BTreeMap<_, _>>()
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
#![cfg(test)]
|
|
||||||
mod gen_dictionary;
|
|
||||||
mod gen_locales;
|
|
|
@ -103,10 +103,9 @@ fn get_sig_fn(player_js: &str) -> Result<String> {
|
||||||
.as_str()
|
.as_str()
|
||||||
+ ";";
|
+ ";";
|
||||||
|
|
||||||
static HELPER_OBJECT_NAME_PATTERN: Lazy<Regex> =
|
let helper_object_name_pattern = Regex::new(";([A-Za-z0-9_\\$]{2})\\...\\(").unwrap();
|
||||||
Lazy::new(|| Regex::new(";([A-Za-z0-9_\\$]{2})\\...\\(").unwrap());
|
|
||||||
let helper_object_name = some_or_bail!(
|
let helper_object_name = some_or_bail!(
|
||||||
HELPER_OBJECT_NAME_PATTERN
|
helper_object_name_pattern
|
||||||
.captures(&deobfuscate_function)
|
.captures(&deobfuscate_function)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten(),
|
.flatten(),
|
||||||
|
@ -146,13 +145,12 @@ fn deobfuscate_sig(sig: &str, sig_fn: &str) -> Result<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
||||||
static FUNCTION_NAME_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
let function_name_pattern =
|
||||||
Regex::new("\\.get\\(\"n\"\\)\\)&&\\(b=([a-zA-Z0-9$]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9]\\)")
|
Regex::new("\\.get\\(\"n\"\\)\\)&&\\(b=([a-zA-Z0-9$]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9]\\)")
|
||||||
.unwrap()
|
.unwrap();
|
||||||
});
|
|
||||||
|
|
||||||
let fname_match = some_or_bail!(
|
let fname_match = some_or_bail!(
|
||||||
FUNCTION_NAME_PATTERN.captures(player_js).ok().flatten(),
|
function_name_pattern.captures(player_js).ok().flatten(),
|
||||||
Err(anyhow!("could not find n_deobf function"))
|
Err(anyhow!("could not find n_deobf function"))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -317,12 +315,11 @@ async fn get_player_js_url(http: &Client) -> Result<String> {
|
||||||
.error_for_status()?;
|
.error_for_status()?;
|
||||||
let text = resp.text().await?;
|
let text = resp.text().await?;
|
||||||
|
|
||||||
static PLAYER_HASH_PATTERN: Lazy<Regex> = Lazy::new(|| {
|
let player_hash_pattern =
|
||||||
Regex::new(r#"https:\\\/\\\/www\.youtube\.com\\\/s\\\/player\\\/([a-z0-9]{8})\\\/"#)
|
Regex::new(r#"https:\\\/\\\/www\.youtube\.com\\\/s\\\/player\\\/([a-z0-9]{8})\\\/"#)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
});
|
|
||||||
let player_hash = some_or_bail!(
|
let player_hash = some_or_bail!(
|
||||||
PLAYER_HASH_PATTERN.captures(&text)?,
|
player_hash_pattern.captures(&text)?,
|
||||||
Err(anyhow!("could not find player hash"))
|
Err(anyhow!("could not find player hash"))
|
||||||
)
|
)
|
||||||
.get(1)
|
.get(1)
|
||||||
|
@ -341,11 +338,10 @@ async fn get_response(http: &Client, url: &str) -> Result<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sts(player_js: &str) -> Result<String> {
|
fn get_sts(player_js: &str) -> Result<String> {
|
||||||
static STS_PATTERN: Lazy<Regex> =
|
let sts_pattern = Regex::new("signatureTimestamp[=:](\\d+)").unwrap();
|
||||||
Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap());
|
|
||||||
|
|
||||||
Ok(some_or_bail!(
|
Ok(some_or_bail!(
|
||||||
STS_PATTERN.captures(&player_js)?,
|
sts_pattern.captures(&player_js)?,
|
||||||
Err(anyhow!("could not find sts"))
|
Err(anyhow!("could not find sts"))
|
||||||
)
|
)
|
||||||
.get(1)
|
.get(1)
|
||||||
|
@ -361,7 +357,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
|
|
||||||
static TEST_JS: Lazy<String> = Lazy::new(|| {
|
const TEST_JS: Lazy<String> = Lazy::new(|| {
|
||||||
let js_path = Path::new("testfiles/deobf/dummy_player.js");
|
let js_path = Path::new("testfiles/deobf/dummy_player.js");
|
||||||
std::fs::read_to_string(js_path).unwrap()
|
std::fs::read_to_string(js_path).unwrap()
|
||||||
});
|
});
|
||||||
|
|
1682
src/dictionary.rs
1682
src/dictionary.rs
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{stream_filter::Filter, AudioCodec, FileFormat, VideoCodec, VideoPlayer},
|
model::{stream_filter::Filter, AudioCodec, FileFormat, VideoPlayer, VideoCodec},
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,11 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod codegen;
|
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
mod deobfuscate;
|
mod deobfuscate;
|
||||||
mod dictionary;
|
|
||||||
mod serializer;
|
mod serializer;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod download;
|
pub mod download;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod timeago;
|
|
||||||
|
|
|
@ -1,842 +0,0 @@
|
||||||
// This file is automatically generated. DO NOT EDIT.
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum Language {
|
|
||||||
/// Afrikaans
|
|
||||||
Af,
|
|
||||||
/// አማርኛ
|
|
||||||
Am,
|
|
||||||
/// العربية
|
|
||||||
Ar,
|
|
||||||
/// অসমীয়া
|
|
||||||
As,
|
|
||||||
/// Azərbaycan
|
|
||||||
Az,
|
|
||||||
/// Беларуская
|
|
||||||
Be,
|
|
||||||
/// Български
|
|
||||||
Bg,
|
|
||||||
/// বাংলা
|
|
||||||
Bn,
|
|
||||||
/// Bosanski
|
|
||||||
Bs,
|
|
||||||
/// Català
|
|
||||||
Ca,
|
|
||||||
/// Čeština
|
|
||||||
Cs,
|
|
||||||
/// Dansk
|
|
||||||
Da,
|
|
||||||
/// Deutsch
|
|
||||||
De,
|
|
||||||
/// Ελληνικά
|
|
||||||
El,
|
|
||||||
/// English (US)
|
|
||||||
En,
|
|
||||||
/// English (UK)
|
|
||||||
#[serde(rename = "en-GB")]
|
|
||||||
EnGb,
|
|
||||||
/// English (India)
|
|
||||||
#[serde(rename = "en-IN")]
|
|
||||||
EnIn,
|
|
||||||
/// Español (España)
|
|
||||||
Es,
|
|
||||||
/// Español (Latinoamérica)
|
|
||||||
#[serde(rename = "es-419")]
|
|
||||||
Es419,
|
|
||||||
/// Español (US)
|
|
||||||
#[serde(rename = "es-US")]
|
|
||||||
EsUs,
|
|
||||||
/// Eesti
|
|
||||||
Et,
|
|
||||||
/// Euskara
|
|
||||||
Eu,
|
|
||||||
/// فارسی
|
|
||||||
Fa,
|
|
||||||
/// Suomi
|
|
||||||
Fi,
|
|
||||||
/// Filipino
|
|
||||||
Fil,
|
|
||||||
/// Français
|
|
||||||
Fr,
|
|
||||||
/// Français (Canada)
|
|
||||||
#[serde(rename = "fr-CA")]
|
|
||||||
FrCa,
|
|
||||||
/// Galego
|
|
||||||
Gl,
|
|
||||||
/// ગુજરાતી
|
|
||||||
Gu,
|
|
||||||
/// हिन्दी
|
|
||||||
Hi,
|
|
||||||
/// Hrvatski
|
|
||||||
Hr,
|
|
||||||
/// Magyar
|
|
||||||
Hu,
|
|
||||||
/// Հայերեն
|
|
||||||
Hy,
|
|
||||||
/// Bahasa Indonesia
|
|
||||||
Id,
|
|
||||||
/// Íslenska
|
|
||||||
Is,
|
|
||||||
/// Italiano
|
|
||||||
It,
|
|
||||||
/// עברית
|
|
||||||
Iw,
|
|
||||||
/// 日本語
|
|
||||||
Ja,
|
|
||||||
/// ქართული
|
|
||||||
Ka,
|
|
||||||
/// Қазақ Тілі
|
|
||||||
Kk,
|
|
||||||
/// ខ្មែរ
|
|
||||||
Km,
|
|
||||||
/// ಕನ್ನಡ
|
|
||||||
Kn,
|
|
||||||
/// 한국어
|
|
||||||
Ko,
|
|
||||||
/// Кыргызча
|
|
||||||
Ky,
|
|
||||||
/// ລາວ
|
|
||||||
Lo,
|
|
||||||
/// Lietuvių
|
|
||||||
Lt,
|
|
||||||
/// Latviešu valoda
|
|
||||||
Lv,
|
|
||||||
/// Македонски
|
|
||||||
Mk,
|
|
||||||
/// മലയാളം
|
|
||||||
Ml,
|
|
||||||
/// Монгол
|
|
||||||
Mn,
|
|
||||||
/// मराठी
|
|
||||||
Mr,
|
|
||||||
/// Bahasa Malaysia
|
|
||||||
Ms,
|
|
||||||
/// ဗမာ
|
|
||||||
My,
|
|
||||||
/// नेपाली
|
|
||||||
Ne,
|
|
||||||
/// Nederlands
|
|
||||||
Nl,
|
|
||||||
/// Norsk
|
|
||||||
No,
|
|
||||||
/// ଓଡ଼ିଆ
|
|
||||||
Or,
|
|
||||||
/// ਪੰਜਾਬੀ
|
|
||||||
Pa,
|
|
||||||
/// Polski
|
|
||||||
Pl,
|
|
||||||
/// Português (Brasil)
|
|
||||||
Pt,
|
|
||||||
/// Português
|
|
||||||
#[serde(rename = "pt-PT")]
|
|
||||||
PtPt,
|
|
||||||
/// Română
|
|
||||||
Ro,
|
|
||||||
/// Русский
|
|
||||||
Ru,
|
|
||||||
/// සිංහල
|
|
||||||
Si,
|
|
||||||
/// Slovenčina
|
|
||||||
Sk,
|
|
||||||
/// Slovenščina
|
|
||||||
Sl,
|
|
||||||
/// Shqip
|
|
||||||
Sq,
|
|
||||||
/// Српски
|
|
||||||
Sr,
|
|
||||||
/// Srpski
|
|
||||||
#[serde(rename = "sr-Latn")]
|
|
||||||
SrLatn,
|
|
||||||
/// Svenska
|
|
||||||
Sv,
|
|
||||||
/// Kiswahili
|
|
||||||
Sw,
|
|
||||||
/// தமிழ்
|
|
||||||
Ta,
|
|
||||||
/// తెలుగు
|
|
||||||
Te,
|
|
||||||
/// ภาษาไทย
|
|
||||||
Th,
|
|
||||||
/// Türkçe
|
|
||||||
Tr,
|
|
||||||
/// Українська
|
|
||||||
Uk,
|
|
||||||
/// اردو
|
|
||||||
Ur,
|
|
||||||
/// O‘zbek
|
|
||||||
Uz,
|
|
||||||
/// Tiếng Việt
|
|
||||||
Vi,
|
|
||||||
/// 中文 (简体)
|
|
||||||
#[serde(rename = "zh-CN")]
|
|
||||||
ZhCn,
|
|
||||||
/// 中文 (香港)
|
|
||||||
#[serde(rename = "zh-HK")]
|
|
||||||
ZhHk,
|
|
||||||
/// 中文 (繁體)
|
|
||||||
#[serde(rename = "zh-TW")]
|
|
||||||
ZhTw,
|
|
||||||
/// IsiZulu
|
|
||||||
Zu,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
|
||||||
pub enum Country {
|
|
||||||
/// United Arab Emirates
|
|
||||||
Ae,
|
|
||||||
/// Argentina
|
|
||||||
Ar,
|
|
||||||
/// Austria
|
|
||||||
At,
|
|
||||||
/// Australia
|
|
||||||
Au,
|
|
||||||
/// Azerbaijan
|
|
||||||
Az,
|
|
||||||
/// Bosnia and Herzegovina
|
|
||||||
Ba,
|
|
||||||
/// Bangladesh
|
|
||||||
Bd,
|
|
||||||
/// Belgium
|
|
||||||
Be,
|
|
||||||
/// Bulgaria
|
|
||||||
Bg,
|
|
||||||
/// Bahrain
|
|
||||||
Bh,
|
|
||||||
/// Bolivia
|
|
||||||
Bo,
|
|
||||||
/// Brazil
|
|
||||||
Br,
|
|
||||||
/// Belarus
|
|
||||||
By,
|
|
||||||
/// Canada
|
|
||||||
Ca,
|
|
||||||
/// Switzerland
|
|
||||||
Ch,
|
|
||||||
/// Chile
|
|
||||||
Cl,
|
|
||||||
/// Colombia
|
|
||||||
Co,
|
|
||||||
/// Costa Rica
|
|
||||||
Cr,
|
|
||||||
/// Cyprus
|
|
||||||
Cy,
|
|
||||||
/// Czechia
|
|
||||||
Cz,
|
|
||||||
/// Germany
|
|
||||||
De,
|
|
||||||
/// Denmark
|
|
||||||
Dk,
|
|
||||||
/// Dominican Republic
|
|
||||||
Do,
|
|
||||||
/// Algeria
|
|
||||||
Dz,
|
|
||||||
/// Ecuador
|
|
||||||
Ec,
|
|
||||||
/// Estonia
|
|
||||||
Ee,
|
|
||||||
/// Egypt
|
|
||||||
Eg,
|
|
||||||
/// Spain
|
|
||||||
Es,
|
|
||||||
/// Finland
|
|
||||||
Fi,
|
|
||||||
/// France
|
|
||||||
Fr,
|
|
||||||
/// United Kingdom
|
|
||||||
Gb,
|
|
||||||
/// Georgia
|
|
||||||
Ge,
|
|
||||||
/// Ghana
|
|
||||||
Gh,
|
|
||||||
/// Greece
|
|
||||||
Gr,
|
|
||||||
/// Guatemala
|
|
||||||
Gt,
|
|
||||||
/// Hong Kong
|
|
||||||
Hk,
|
|
||||||
/// Honduras
|
|
||||||
Hn,
|
|
||||||
/// Croatia
|
|
||||||
Hr,
|
|
||||||
/// Hungary
|
|
||||||
Hu,
|
|
||||||
/// Indonesia
|
|
||||||
Id,
|
|
||||||
/// Ireland
|
|
||||||
Ie,
|
|
||||||
/// Israel
|
|
||||||
Il,
|
|
||||||
/// India
|
|
||||||
In,
|
|
||||||
/// Iraq
|
|
||||||
Iq,
|
|
||||||
/// Iceland
|
|
||||||
Is,
|
|
||||||
/// Italy
|
|
||||||
It,
|
|
||||||
/// Jamaica
|
|
||||||
Jm,
|
|
||||||
/// Jordan
|
|
||||||
Jo,
|
|
||||||
/// Japan
|
|
||||||
Jp,
|
|
||||||
/// Kenya
|
|
||||||
Ke,
|
|
||||||
/// Cambodia
|
|
||||||
Kh,
|
|
||||||
/// South Korea
|
|
||||||
Kr,
|
|
||||||
/// Kuwait
|
|
||||||
Kw,
|
|
||||||
/// Kazakhstan
|
|
||||||
Kz,
|
|
||||||
/// Laos
|
|
||||||
La,
|
|
||||||
/// Lebanon
|
|
||||||
Lb,
|
|
||||||
/// Liechtenstein
|
|
||||||
Li,
|
|
||||||
/// Sri Lanka
|
|
||||||
Lk,
|
|
||||||
/// Lithuania
|
|
||||||
Lt,
|
|
||||||
/// Luxembourg
|
|
||||||
Lu,
|
|
||||||
/// Latvia
|
|
||||||
Lv,
|
|
||||||
/// Libya
|
|
||||||
Ly,
|
|
||||||
/// Morocco
|
|
||||||
Ma,
|
|
||||||
/// Montenegro
|
|
||||||
Me,
|
|
||||||
/// North Macedonia
|
|
||||||
Mk,
|
|
||||||
/// Malta
|
|
||||||
Mt,
|
|
||||||
/// Mexico
|
|
||||||
Mx,
|
|
||||||
/// Malaysia
|
|
||||||
My,
|
|
||||||
/// Nigeria
|
|
||||||
Ng,
|
|
||||||
/// Nicaragua
|
|
||||||
Ni,
|
|
||||||
/// Netherlands
|
|
||||||
Nl,
|
|
||||||
/// Norway
|
|
||||||
No,
|
|
||||||
/// Nepal
|
|
||||||
Np,
|
|
||||||
/// New Zealand
|
|
||||||
Nz,
|
|
||||||
/// Oman
|
|
||||||
Om,
|
|
||||||
/// Panama
|
|
||||||
Pa,
|
|
||||||
/// Peru
|
|
||||||
Pe,
|
|
||||||
/// Papua New Guinea
|
|
||||||
Pg,
|
|
||||||
/// Philippines
|
|
||||||
Ph,
|
|
||||||
/// Pakistan
|
|
||||||
Pk,
|
|
||||||
/// Poland
|
|
||||||
Pl,
|
|
||||||
/// Puerto Rico
|
|
||||||
Pr,
|
|
||||||
/// Portugal
|
|
||||||
Pt,
|
|
||||||
/// Paraguay
|
|
||||||
Py,
|
|
||||||
/// Qatar
|
|
||||||
Qa,
|
|
||||||
/// Romania
|
|
||||||
Ro,
|
|
||||||
/// Serbia
|
|
||||||
Rs,
|
|
||||||
/// Russia
|
|
||||||
Ru,
|
|
||||||
/// Saudi Arabia
|
|
||||||
Sa,
|
|
||||||
/// Sweden
|
|
||||||
Se,
|
|
||||||
/// Singapore
|
|
||||||
Sg,
|
|
||||||
/// Slovenia
|
|
||||||
Si,
|
|
||||||
/// Slovakia
|
|
||||||
Sk,
|
|
||||||
/// Senegal
|
|
||||||
Sn,
|
|
||||||
/// El Salvador
|
|
||||||
Sv,
|
|
||||||
/// Thailand
|
|
||||||
Th,
|
|
||||||
/// Tunisia
|
|
||||||
Tn,
|
|
||||||
/// Turkey
|
|
||||||
Tr,
|
|
||||||
/// Taiwan
|
|
||||||
Tw,
|
|
||||||
/// Tanzania
|
|
||||||
Tz,
|
|
||||||
/// Ukraine
|
|
||||||
Ua,
|
|
||||||
/// Uganda
|
|
||||||
Ug,
|
|
||||||
/// United States
|
|
||||||
Us,
|
|
||||||
/// Uruguay
|
|
||||||
Uy,
|
|
||||||
/// Venezuela
|
|
||||||
Ve,
|
|
||||||
/// Vietnam
|
|
||||||
Vn,
|
|
||||||
/// Yemen
|
|
||||||
Ye,
|
|
||||||
/// South Africa
|
|
||||||
Za,
|
|
||||||
/// Zimbabwe
|
|
||||||
Zw,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const LANGUAGES: [Language; 83] = [
|
|
||||||
Language::Af,
|
|
||||||
Language::Am,
|
|
||||||
Language::Ar,
|
|
||||||
Language::As,
|
|
||||||
Language::Az,
|
|
||||||
Language::Be,
|
|
||||||
Language::Bg,
|
|
||||||
Language::Bn,
|
|
||||||
Language::Bs,
|
|
||||||
Language::Ca,
|
|
||||||
Language::Cs,
|
|
||||||
Language::Da,
|
|
||||||
Language::De,
|
|
||||||
Language::El,
|
|
||||||
Language::En,
|
|
||||||
Language::EnGb,
|
|
||||||
Language::EnIn,
|
|
||||||
Language::Es,
|
|
||||||
Language::Es419,
|
|
||||||
Language::EsUs,
|
|
||||||
Language::Et,
|
|
||||||
Language::Eu,
|
|
||||||
Language::Fa,
|
|
||||||
Language::Fi,
|
|
||||||
Language::Fil,
|
|
||||||
Language::Fr,
|
|
||||||
Language::FrCa,
|
|
||||||
Language::Gl,
|
|
||||||
Language::Gu,
|
|
||||||
Language::Hi,
|
|
||||||
Language::Hr,
|
|
||||||
Language::Hu,
|
|
||||||
Language::Hy,
|
|
||||||
Language::Id,
|
|
||||||
Language::Is,
|
|
||||||
Language::It,
|
|
||||||
Language::Iw,
|
|
||||||
Language::Ja,
|
|
||||||
Language::Ka,
|
|
||||||
Language::Kk,
|
|
||||||
Language::Km,
|
|
||||||
Language::Kn,
|
|
||||||
Language::Ko,
|
|
||||||
Language::Ky,
|
|
||||||
Language::Lo,
|
|
||||||
Language::Lt,
|
|
||||||
Language::Lv,
|
|
||||||
Language::Mk,
|
|
||||||
Language::Ml,
|
|
||||||
Language::Mn,
|
|
||||||
Language::Mr,
|
|
||||||
Language::Ms,
|
|
||||||
Language::My,
|
|
||||||
Language::Ne,
|
|
||||||
Language::Nl,
|
|
||||||
Language::No,
|
|
||||||
Language::Or,
|
|
||||||
Language::Pa,
|
|
||||||
Language::Pl,
|
|
||||||
Language::Pt,
|
|
||||||
Language::PtPt,
|
|
||||||
Language::Ro,
|
|
||||||
Language::Ru,
|
|
||||||
Language::Si,
|
|
||||||
Language::Sk,
|
|
||||||
Language::Sl,
|
|
||||||
Language::Sq,
|
|
||||||
Language::Sr,
|
|
||||||
Language::SrLatn,
|
|
||||||
Language::Sv,
|
|
||||||
Language::Sw,
|
|
||||||
Language::Ta,
|
|
||||||
Language::Te,
|
|
||||||
Language::Th,
|
|
||||||
Language::Tr,
|
|
||||||
Language::Uk,
|
|
||||||
Language::Ur,
|
|
||||||
Language::Uz,
|
|
||||||
Language::Vi,
|
|
||||||
Language::ZhCn,
|
|
||||||
Language::ZhHk,
|
|
||||||
Language::ZhTw,
|
|
||||||
Language::Zu,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const COUNTRIES: [Country; 109] = [
|
|
||||||
Country::Ae,
|
|
||||||
Country::Ar,
|
|
||||||
Country::At,
|
|
||||||
Country::Au,
|
|
||||||
Country::Az,
|
|
||||||
Country::Ba,
|
|
||||||
Country::Bd,
|
|
||||||
Country::Be,
|
|
||||||
Country::Bg,
|
|
||||||
Country::Bh,
|
|
||||||
Country::Bo,
|
|
||||||
Country::Br,
|
|
||||||
Country::By,
|
|
||||||
Country::Ca,
|
|
||||||
Country::Ch,
|
|
||||||
Country::Cl,
|
|
||||||
Country::Co,
|
|
||||||
Country::Cr,
|
|
||||||
Country::Cy,
|
|
||||||
Country::Cz,
|
|
||||||
Country::De,
|
|
||||||
Country::Dk,
|
|
||||||
Country::Do,
|
|
||||||
Country::Dz,
|
|
||||||
Country::Ec,
|
|
||||||
Country::Ee,
|
|
||||||
Country::Eg,
|
|
||||||
Country::Es,
|
|
||||||
Country::Fi,
|
|
||||||
Country::Fr,
|
|
||||||
Country::Gb,
|
|
||||||
Country::Ge,
|
|
||||||
Country::Gh,
|
|
||||||
Country::Gr,
|
|
||||||
Country::Gt,
|
|
||||||
Country::Hk,
|
|
||||||
Country::Hn,
|
|
||||||
Country::Hr,
|
|
||||||
Country::Hu,
|
|
||||||
Country::Id,
|
|
||||||
Country::Ie,
|
|
||||||
Country::Il,
|
|
||||||
Country::In,
|
|
||||||
Country::Iq,
|
|
||||||
Country::Is,
|
|
||||||
Country::It,
|
|
||||||
Country::Jm,
|
|
||||||
Country::Jo,
|
|
||||||
Country::Jp,
|
|
||||||
Country::Ke,
|
|
||||||
Country::Kh,
|
|
||||||
Country::Kr,
|
|
||||||
Country::Kw,
|
|
||||||
Country::Kz,
|
|
||||||
Country::La,
|
|
||||||
Country::Lb,
|
|
||||||
Country::Li,
|
|
||||||
Country::Lk,
|
|
||||||
Country::Lt,
|
|
||||||
Country::Lu,
|
|
||||||
Country::Lv,
|
|
||||||
Country::Ly,
|
|
||||||
Country::Ma,
|
|
||||||
Country::Me,
|
|
||||||
Country::Mk,
|
|
||||||
Country::Mt,
|
|
||||||
Country::Mx,
|
|
||||||
Country::My,
|
|
||||||
Country::Ng,
|
|
||||||
Country::Ni,
|
|
||||||
Country::Nl,
|
|
||||||
Country::No,
|
|
||||||
Country::Np,
|
|
||||||
Country::Nz,
|
|
||||||
Country::Om,
|
|
||||||
Country::Pa,
|
|
||||||
Country::Pe,
|
|
||||||
Country::Pg,
|
|
||||||
Country::Ph,
|
|
||||||
Country::Pk,
|
|
||||||
Country::Pl,
|
|
||||||
Country::Pr,
|
|
||||||
Country::Pt,
|
|
||||||
Country::Py,
|
|
||||||
Country::Qa,
|
|
||||||
Country::Ro,
|
|
||||||
Country::Rs,
|
|
||||||
Country::Ru,
|
|
||||||
Country::Sa,
|
|
||||||
Country::Se,
|
|
||||||
Country::Sg,
|
|
||||||
Country::Si,
|
|
||||||
Country::Sk,
|
|
||||||
Country::Sn,
|
|
||||||
Country::Sv,
|
|
||||||
Country::Th,
|
|
||||||
Country::Tn,
|
|
||||||
Country::Tr,
|
|
||||||
Country::Tw,
|
|
||||||
Country::Tz,
|
|
||||||
Country::Ua,
|
|
||||||
Country::Ug,
|
|
||||||
Country::Us,
|
|
||||||
Country::Uy,
|
|
||||||
Country::Ve,
|
|
||||||
Country::Vn,
|
|
||||||
Country::Ye,
|
|
||||||
Country::Za,
|
|
||||||
Country::Zw,
|
|
||||||
];
|
|
||||||
|
|
||||||
impl Language {
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Language::Af => "Afrikaans",
|
|
||||||
Language::Am => "አማርኛ",
|
|
||||||
Language::Ar => "العربية",
|
|
||||||
Language::As => "অসমীয়া",
|
|
||||||
Language::Az => "Azərbaycan",
|
|
||||||
Language::Be => "Беларуская",
|
|
||||||
Language::Bg => "Български",
|
|
||||||
Language::Bn => "বাংলা",
|
|
||||||
Language::Bs => "Bosanski",
|
|
||||||
Language::Ca => "Català",
|
|
||||||
Language::Cs => "Čeština",
|
|
||||||
Language::Da => "Dansk",
|
|
||||||
Language::De => "Deutsch",
|
|
||||||
Language::El => "Ελληνικά",
|
|
||||||
Language::En => "English (US)",
|
|
||||||
Language::EnGb => "English (UK)",
|
|
||||||
Language::EnIn => "English (India)",
|
|
||||||
Language::Es => "Español (España)",
|
|
||||||
Language::Es419 => "Español (Latinoamérica)",
|
|
||||||
Language::EsUs => "Español (US)",
|
|
||||||
Language::Et => "Eesti",
|
|
||||||
Language::Eu => "Euskara",
|
|
||||||
Language::Fa => "فارسی",
|
|
||||||
Language::Fi => "Suomi",
|
|
||||||
Language::Fil => "Filipino",
|
|
||||||
Language::Fr => "Français",
|
|
||||||
Language::FrCa => "Français (Canada)",
|
|
||||||
Language::Gl => "Galego",
|
|
||||||
Language::Gu => "ગુજરાતી",
|
|
||||||
Language::Hi => "हिन्दी",
|
|
||||||
Language::Hr => "Hrvatski",
|
|
||||||
Language::Hu => "Magyar",
|
|
||||||
Language::Hy => "Հայերեն",
|
|
||||||
Language::Id => "Bahasa Indonesia",
|
|
||||||
Language::Is => "Íslenska",
|
|
||||||
Language::It => "Italiano",
|
|
||||||
Language::Iw => "עברית",
|
|
||||||
Language::Ja => "日本語",
|
|
||||||
Language::Ka => "ქართული",
|
|
||||||
Language::Kk => "Қазақ Тілі",
|
|
||||||
Language::Km => "ខ្មែរ",
|
|
||||||
Language::Kn => "ಕನ್ನಡ",
|
|
||||||
Language::Ko => "한국어",
|
|
||||||
Language::Ky => "Кыргызча",
|
|
||||||
Language::Lo => "ລາວ",
|
|
||||||
Language::Lt => "Lietuvių",
|
|
||||||
Language::Lv => "Latviešu valoda",
|
|
||||||
Language::Mk => "Македонски",
|
|
||||||
Language::Ml => "മലയാളം",
|
|
||||||
Language::Mn => "Монгол",
|
|
||||||
Language::Mr => "मराठी",
|
|
||||||
Language::Ms => "Bahasa Malaysia",
|
|
||||||
Language::My => "ဗမာ",
|
|
||||||
Language::Ne => "नेपाली",
|
|
||||||
Language::Nl => "Nederlands",
|
|
||||||
Language::No => "Norsk",
|
|
||||||
Language::Or => "ଓଡ଼ିଆ",
|
|
||||||
Language::Pa => "ਪੰਜਾਬੀ",
|
|
||||||
Language::Pl => "Polski",
|
|
||||||
Language::Pt => "Português (Brasil)",
|
|
||||||
Language::PtPt => "Português",
|
|
||||||
Language::Ro => "Română",
|
|
||||||
Language::Ru => "Русский",
|
|
||||||
Language::Si => "සිංහල",
|
|
||||||
Language::Sk => "Slovenčina",
|
|
||||||
Language::Sl => "Slovenščina",
|
|
||||||
Language::Sq => "Shqip",
|
|
||||||
Language::Sr => "Српски",
|
|
||||||
Language::SrLatn => "Srpski",
|
|
||||||
Language::Sv => "Svenska",
|
|
||||||
Language::Sw => "Kiswahili",
|
|
||||||
Language::Ta => "தமிழ்",
|
|
||||||
Language::Te => "తెలుగు",
|
|
||||||
Language::Th => "ภาษาไทย",
|
|
||||||
Language::Tr => "Türkçe",
|
|
||||||
Language::Uk => "Українська",
|
|
||||||
Language::Ur => "اردو",
|
|
||||||
Language::Uz => "O‘zbek",
|
|
||||||
Language::Vi => "Tiếng Việt",
|
|
||||||
Language::ZhCn => "中文 (简体)",
|
|
||||||
Language::ZhHk => "中文 (香港)",
|
|
||||||
Language::ZhTw => "中文 (繁體)",
|
|
||||||
Language::Zu => "IsiZulu",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Country {
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Country::Ae => "United Arab Emirates",
|
|
||||||
Country::Ar => "Argentina",
|
|
||||||
Country::At => "Austria",
|
|
||||||
Country::Au => "Australia",
|
|
||||||
Country::Az => "Azerbaijan",
|
|
||||||
Country::Ba => "Bosnia and Herzegovina",
|
|
||||||
Country::Bd => "Bangladesh",
|
|
||||||
Country::Be => "Belgium",
|
|
||||||
Country::Bg => "Bulgaria",
|
|
||||||
Country::Bh => "Bahrain",
|
|
||||||
Country::Bo => "Bolivia",
|
|
||||||
Country::Br => "Brazil",
|
|
||||||
Country::By => "Belarus",
|
|
||||||
Country::Ca => "Canada",
|
|
||||||
Country::Ch => "Switzerland",
|
|
||||||
Country::Cl => "Chile",
|
|
||||||
Country::Co => "Colombia",
|
|
||||||
Country::Cr => "Costa Rica",
|
|
||||||
Country::Cy => "Cyprus",
|
|
||||||
Country::Cz => "Czechia",
|
|
||||||
Country::De => "Germany",
|
|
||||||
Country::Dk => "Denmark",
|
|
||||||
Country::Do => "Dominican Republic",
|
|
||||||
Country::Dz => "Algeria",
|
|
||||||
Country::Ec => "Ecuador",
|
|
||||||
Country::Ee => "Estonia",
|
|
||||||
Country::Eg => "Egypt",
|
|
||||||
Country::Es => "Spain",
|
|
||||||
Country::Fi => "Finland",
|
|
||||||
Country::Fr => "France",
|
|
||||||
Country::Gb => "United Kingdom",
|
|
||||||
Country::Ge => "Georgia",
|
|
||||||
Country::Gh => "Ghana",
|
|
||||||
Country::Gr => "Greece",
|
|
||||||
Country::Gt => "Guatemala",
|
|
||||||
Country::Hk => "Hong Kong",
|
|
||||||
Country::Hn => "Honduras",
|
|
||||||
Country::Hr => "Croatia",
|
|
||||||
Country::Hu => "Hungary",
|
|
||||||
Country::Id => "Indonesia",
|
|
||||||
Country::Ie => "Ireland",
|
|
||||||
Country::Il => "Israel",
|
|
||||||
Country::In => "India",
|
|
||||||
Country::Iq => "Iraq",
|
|
||||||
Country::Is => "Iceland",
|
|
||||||
Country::It => "Italy",
|
|
||||||
Country::Jm => "Jamaica",
|
|
||||||
Country::Jo => "Jordan",
|
|
||||||
Country::Jp => "Japan",
|
|
||||||
Country::Ke => "Kenya",
|
|
||||||
Country::Kh => "Cambodia",
|
|
||||||
Country::Kr => "South Korea",
|
|
||||||
Country::Kw => "Kuwait",
|
|
||||||
Country::Kz => "Kazakhstan",
|
|
||||||
Country::La => "Laos",
|
|
||||||
Country::Lb => "Lebanon",
|
|
||||||
Country::Li => "Liechtenstein",
|
|
||||||
Country::Lk => "Sri Lanka",
|
|
||||||
Country::Lt => "Lithuania",
|
|
||||||
Country::Lu => "Luxembourg",
|
|
||||||
Country::Lv => "Latvia",
|
|
||||||
Country::Ly => "Libya",
|
|
||||||
Country::Ma => "Morocco",
|
|
||||||
Country::Me => "Montenegro",
|
|
||||||
Country::Mk => "North Macedonia",
|
|
||||||
Country::Mt => "Malta",
|
|
||||||
Country::Mx => "Mexico",
|
|
||||||
Country::My => "Malaysia",
|
|
||||||
Country::Ng => "Nigeria",
|
|
||||||
Country::Ni => "Nicaragua",
|
|
||||||
Country::Nl => "Netherlands",
|
|
||||||
Country::No => "Norway",
|
|
||||||
Country::Np => "Nepal",
|
|
||||||
Country::Nz => "New Zealand",
|
|
||||||
Country::Om => "Oman",
|
|
||||||
Country::Pa => "Panama",
|
|
||||||
Country::Pe => "Peru",
|
|
||||||
Country::Pg => "Papua New Guinea",
|
|
||||||
Country::Ph => "Philippines",
|
|
||||||
Country::Pk => "Pakistan",
|
|
||||||
Country::Pl => "Poland",
|
|
||||||
Country::Pr => "Puerto Rico",
|
|
||||||
Country::Pt => "Portugal",
|
|
||||||
Country::Py => "Paraguay",
|
|
||||||
Country::Qa => "Qatar",
|
|
||||||
Country::Ro => "Romania",
|
|
||||||
Country::Rs => "Serbia",
|
|
||||||
Country::Ru => "Russia",
|
|
||||||
Country::Sa => "Saudi Arabia",
|
|
||||||
Country::Se => "Sweden",
|
|
||||||
Country::Sg => "Singapore",
|
|
||||||
Country::Si => "Slovenia",
|
|
||||||
Country::Sk => "Slovakia",
|
|
||||||
Country::Sn => "Senegal",
|
|
||||||
Country::Sv => "El Salvador",
|
|
||||||
Country::Th => "Thailand",
|
|
||||||
Country::Tn => "Tunisia",
|
|
||||||
Country::Tr => "Turkey",
|
|
||||||
Country::Tw => "Taiwan",
|
|
||||||
Country::Tz => "Tanzania",
|
|
||||||
Country::Ua => "Ukraine",
|
|
||||||
Country::Ug => "Uganda",
|
|
||||||
Country::Us => "United States",
|
|
||||||
Country::Uy => "Uruguay",
|
|
||||||
Country::Ve => "Venezuela",
|
|
||||||
Country::Vn => "Vietnam",
|
|
||||||
Country::Ye => "Yemen",
|
|
||||||
Country::Za => "South Africa",
|
|
||||||
Country::Zw => "Zimbabwe",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Language {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(
|
|
||||||
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Country {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(
|
|
||||||
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Language {
|
|
||||||
type Err = serde_json::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
serde_json::from_str(&format!("\"{}\"", s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Country {
|
|
||||||
type Err = serde_json::Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
serde_json::from_str(&format!("\"{}\"", s))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,6 @@
|
||||||
mod locale;
|
|
||||||
mod ordering;
|
mod ordering;
|
||||||
pub mod stream_filter;
|
pub mod stream_filter;
|
||||||
|
|
||||||
pub use locale::{Country, Language};
|
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
@ -25,15 +22,13 @@ pub struct VideoPlayer {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Playlist {
|
pub struct Playlist {
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub videos: Vec<Video>,
|
pub videos: Vec<Video>,
|
||||||
pub n_videos: u32,
|
pub n_videos: u32,
|
||||||
pub ctoken: Option<String>,
|
pub ctoken: Option<String>,
|
||||||
|
pub name: String,
|
||||||
pub thumbnails: Vec<Thumbnail>,
|
pub thumbnails: Vec<Thumbnail>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub channel: Option<Channel>,
|
pub channel: Option<Channel>,
|
||||||
pub last_update: Option<DateTime<Utc>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
@ -181,6 +176,12 @@ pub struct Subtitle {
|
||||||
pub auto_generated: bool,
|
pub auto_generated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Locale {
|
||||||
|
pub lang: String,
|
||||||
|
pub country: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct Video {
|
pub struct Video {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|
|
@ -331,14 +331,14 @@ mod tests {
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
use velcro::hash_set;
|
use velcro::hash_set;
|
||||||
|
|
||||||
static PLAYER_ML: Lazy<VideoPlayer> = Lazy::new(|| {
|
const PLAYER_ML: Lazy<VideoPlayer> = Lazy::new(|| {
|
||||||
let json_path = Path::new("testfiles/player_model/multilanguage.json");
|
let json_path = Path::new("testfiles/player_model/multilanguage.json");
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
serde_json::from_reader(BufReader::new(json_file)).unwrap()
|
serde_json::from_reader(BufReader::new(json_file)).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
static PLAYER_HDR: Lazy<VideoPlayer> = Lazy::new(|| {
|
const PLAYER_HDR: Lazy<VideoPlayer> = Lazy::new(|| {
|
||||||
let json_path = Path::new("testfiles/player_model/hdr.json");
|
let json_path = Path::new("testfiles/player_model/hdr.json");
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
|
@ -356,7 +356,8 @@ mod tests {
|
||||||
#[case::noformat(Filter::default().audio_formats(hash_set!()).to_owned(), None)]
|
#[case::noformat(Filter::default().audio_formats(hash_set!()).to_owned(), None)]
|
||||||
#[case::nocodec(Filter::default().audio_codecs(hash_set!()).to_owned(), None)]
|
#[case::nocodec(Filter::default().audio_codecs(hash_set!()).to_owned(), None)]
|
||||||
fn t_select_audio_stream(#[case] filter: Filter, #[case] expect_url: Option<&str>) {
|
fn t_select_audio_stream(#[case] filter: Filter, #[case] expect_url: Option<&str>) {
|
||||||
let selection = PLAYER_ML.select_audio_stream(&filter);
|
let player_data = PLAYER_ML;
|
||||||
|
let selection = player_data.select_audio_stream(&filter);
|
||||||
|
|
||||||
match expect_url {
|
match expect_url {
|
||||||
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
|
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
|
||||||
|
@ -375,7 +376,8 @@ mod tests {
|
||||||
#[case::noformat(Filter::default().video_formats(hash_set!()).to_owned(), None)]
|
#[case::noformat(Filter::default().video_formats(hash_set!()).to_owned(), None)]
|
||||||
#[case::nocodec(Filter::default().video_codecs(hash_set!()).to_owned(), None)]
|
#[case::nocodec(Filter::default().video_codecs(hash_set!()).to_owned(), None)]
|
||||||
fn t_select_video_only_stream(#[case] filter: Filter, #[case] expect_url: Option<&str>) {
|
fn t_select_video_only_stream(#[case] filter: Filter, #[case] expect_url: Option<&str>) {
|
||||||
let selection = PLAYER_HDR.select_video_only_stream(&filter);
|
let player_data = PLAYER_HDR;
|
||||||
|
let selection = player_data.select_video_only_stream(&filter);
|
||||||
|
|
||||||
match expect_url {
|
match expect_url {
|
||||||
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
|
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
|
||||||
|
@ -410,7 +412,8 @@ mod tests {
|
||||||
#[case] expect_video_url: Option<&str>,
|
#[case] expect_video_url: Option<&str>,
|
||||||
#[case] expect_audio_url: Option<&str>,
|
#[case] expect_audio_url: Option<&str>,
|
||||||
) {
|
) {
|
||||||
let (video, audio) = PLAYER_HDR.select_video_audio_stream(&filter);
|
let player_data = PLAYER_HDR;
|
||||||
|
let (video, audio) = player_data.select_video_audio_stream(&filter);
|
||||||
|
|
||||||
match expect_video_url {
|
match expect_video_url {
|
||||||
Some(expect_url) => assert_eq!(video.unwrap().url, expect_url),
|
Some(expect_url) => assert_eq!(video.unwrap().url, expect_url),
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod range;
|
pub mod range;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
// pub mod renderer;
|
||||||
|
|
98
src/serializer/renderer.rs
Normal file
98
src/serializer/renderer.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||||
|
use serde_with::{serde_as, DeserializeAs, rust::maps_duplicate_key_is_error::deserialize};
|
||||||
|
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// itemSectionRenderer": {
|
||||||
|
/// "contents": [
|
||||||
|
/// {
|
||||||
|
/// "playlistVideoListRenderer": {
|
||||||
|
/// "contents": [
|
||||||
|
/// {
|
||||||
|
/// "playlistVideoRenderer": { ... }
|
||||||
|
/// },
|
||||||
|
/// {
|
||||||
|
/// "playlistVideoRenderer": { ... }
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Renderer names:
|
||||||
|
///
|
||||||
|
/// 1 content element:
|
||||||
|
/// - tabRenderer > content
|
||||||
|
///
|
||||||
|
/// 1 content element (array):
|
||||||
|
/// - twoColumnBrowseResultsRenderer > tabs
|
||||||
|
/// - sectionListRenderer > contents
|
||||||
|
/// - itemSectionRenderer > contents
|
||||||
|
///
|
||||||
|
/// n content elements:
|
||||||
|
/// - playlistVideoListRenderer > contents
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged, bound = "for<'de2> T: Deserialize<'de2>")]
|
||||||
|
pub enum Renderer<T> where for<'de2> T: Deserialize<'de2> {
|
||||||
|
Single {
|
||||||
|
#[serde_as(as = "crate::serializer::renderer::Renderer<T>")]
|
||||||
|
content: T,
|
||||||
|
},
|
||||||
|
Multiple {
|
||||||
|
#[serde(alias = "tabs")]
|
||||||
|
#[serde_as(as = "crate::serializer::renderer::Renderer<T>")]
|
||||||
|
contents: Vec<T>,
|
||||||
|
},
|
||||||
|
Content {
|
||||||
|
#[serde(flatten)]
|
||||||
|
inner: T,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub struct Renderer<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<'de, T> DeserializeAs<'de, T> for Renderer<T> {
|
||||||
|
fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T> DeserializeAs<'de, Vec<T>> for Renderer<T> {
|
||||||
|
fn deserialize_as<D>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
struct RendererVisitor<T, U>(PhantomData<T>, PhantomData<U>);
|
||||||
|
|
||||||
|
impl<'de, T, U> Visitor<'de> for RendererVisitor<T, U>
|
||||||
|
where
|
||||||
|
U: DeserializeAs<'de, T>,
|
||||||
|
{
|
||||||
|
type Value = T;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a yt renderer")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>, {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -65,16 +65,12 @@ pub enum TextLink {
|
||||||
page_type: PageType,
|
page_type: PageType,
|
||||||
browse_id: String,
|
browse_id: String,
|
||||||
},
|
},
|
||||||
Web {
|
|
||||||
text: String,
|
|
||||||
url: String,
|
|
||||||
},
|
|
||||||
None {
|
None {
|
||||||
text: String,
|
text: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextLinks;
|
pub struct TextLinks {}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct TextLinkInternal {
|
struct TextLinkInternal {
|
||||||
|
@ -101,9 +97,6 @@ struct NavigationEndpoint {
|
||||||
browse_endpoint: Option<BrowseEndpoint>,
|
browse_endpoint: Option<BrowseEndpoint>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||||
url_endpoint: Option<UrlEndpoint>,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
|
||||||
command_metadata: Option<CommandMetadata>,
|
command_metadata: Option<CommandMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,12 +113,6 @@ struct BrowseEndpoint {
|
||||||
browse_endpoint_context_supported_configs: Option<BrowseEndpointConfig>,
|
browse_endpoint_context_supported_configs: Option<BrowseEndpointConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct UrlEndpoint {
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct BrowseEndpointConfig {
|
struct BrowseEndpointConfig {
|
||||||
|
@ -186,13 +173,7 @@ fn map_text_linkrun(lr: &TextLinkRun) -> Option<TextLink> {
|
||||||
},
|
},
|
||||||
browse_id: b.browse_id.to_owned(),
|
browse_id: b.browse_id.to_owned(),
|
||||||
},
|
},
|
||||||
None => match &nav.url_endpoint {
|
None => TextLink::None { text },
|
||||||
Some(u) => TextLink::Web {
|
|
||||||
text,
|
|
||||||
url: u.url.to_owned(),
|
|
||||||
},
|
|
||||||
None => TextLink::None { text },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -417,42 +398,6 @@ mod tests {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn t_link_web() {
|
|
||||||
let test_json = r#"{
|
|
||||||
"ln": {
|
|
||||||
"runs": [
|
|
||||||
{
|
|
||||||
"text": "Creative Commons",
|
|
||||||
"navigationEndpoint": {
|
|
||||||
"clickTrackingParams": "CJsBEM2rARgBIhMImKz9y6Oc-QIVTJpVCh3VrAYM",
|
|
||||||
"commandMetadata": {
|
|
||||||
"webCommandMetadata": {
|
|
||||||
"url": "https://www.youtube.com/t/creative_commons",
|
|
||||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
|
||||||
"rootVe": 83769
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"urlEndpoint": {
|
|
||||||
"url": "https://www.youtube.com/t/creative_commons"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let res = serde_json::from_str::<SLink>(&test_json).unwrap();
|
|
||||||
insta::assert_debug_snapshot!(res, @r###"
|
|
||||||
SLink {
|
|
||||||
ln: Web {
|
|
||||||
text: "Creative Commons",
|
|
||||||
url: "https://www.youtube.com/t/creative_commons",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
"###);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn t_links_artists() {
|
fn t_links_artists() {
|
||||||
let test_json = r#"{
|
let test_json = r#"{
|
||||||
|
|
326
src/timeago.rs
326
src/timeago.rs
|
@ -1,326 +0,0 @@
|
||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{dictionary, model::Language, util};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq)]
|
|
||||||
pub struct TimeAgo {
|
|
||||||
pub n: u8,
|
|
||||||
pub unit: TimeUnit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub(crate) struct TaToken {
|
|
||||||
pub n: u8,
|
|
||||||
pub unit: Option<TimeUnit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum TimeUnit {
|
|
||||||
Second,
|
|
||||||
Minute,
|
|
||||||
Hour,
|
|
||||||
Day,
|
|
||||||
Week,
|
|
||||||
Month,
|
|
||||||
Year,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeUnit {
|
|
||||||
fn seconds(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
TimeUnit::Second => 1,
|
|
||||||
TimeUnit::Minute => 60,
|
|
||||||
TimeUnit::Hour => 3600,
|
|
||||||
TimeUnit::Day => 24 * 3600,
|
|
||||||
TimeUnit::Week => 7 * 24 * 3600,
|
|
||||||
TimeUnit::Month => 30 * 24 * 3600,
|
|
||||||
TimeUnit::Year => 365 * 24 * 3600,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeAgo {
|
|
||||||
fn seconds(&self) -> u64 {
|
|
||||||
self.n as u64 * self.unit.seconds()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for TimeAgo {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.seconds() == other.seconds()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for TimeAgo {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
self.seconds().cmp(&other.seconds())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for TimeAgo {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(lang: Language, textual_date: &str) -> Option<TimeAgo> {
|
|
||||||
let mappings = dictionary::get_timeago_tokens(lang);
|
|
||||||
|
|
||||||
let filtered_str = textual_date
|
|
||||||
.to_lowercase()
|
|
||||||
.chars()
|
|
||||||
.filter(|c| c != &'\u{200b}' && !c.is_ascii_digit())
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
let mut qu: u8 = util::parse_numeric(&textual_date).unwrap_or(1);
|
|
||||||
|
|
||||||
match lang {
|
|
||||||
Language::Ja | Language::ZhCn | Language::ZhHk | Language::ZhTw => {
|
|
||||||
filtered_str.chars().find_map(|word| {
|
|
||||||
mappings
|
|
||||||
.get(&word.to_string())
|
|
||||||
.map(|t| match t.unit {
|
|
||||||
Some(unit) => Some(TimeAgo { n: t.n * qu, unit }),
|
|
||||||
None => {
|
|
||||||
qu = t.n;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => filtered_str.split(' ').find_map(|word| {
|
|
||||||
mappings
|
|
||||||
.get(word)
|
|
||||||
.map(|t| match t.unit {
|
|
||||||
Some(unit) => Some(TimeAgo { n: t.n * qu, unit }),
|
|
||||||
None => {
|
|
||||||
qu = t.n;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::{collections::BTreeMap, fs::File, io::BufReader, path::Path};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn t_testfile() {
|
|
||||||
let json_path = Path::new("testfiles/date/timeago_samples.json");
|
|
||||||
|
|
||||||
let expect = [
|
|
||||||
TimeAgo {
|
|
||||||
n: 10,
|
|
||||||
unit: TimeUnit::Minute,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 20,
|
|
||||||
unit: TimeUnit::Minute,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 1,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 2,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 7,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 8,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 9,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 10,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 11,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 12,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 13,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 14,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 15,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 3,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 4,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 4,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 5,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 6,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 6,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 20,
|
|
||||||
unit: TimeUnit::Hour,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 2,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 3,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 5,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 6,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 8,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 10,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 12,
|
|
||||||
unit: TimeUnit::Day,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 2,
|
|
||||||
unit: TimeUnit::Week,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 3,
|
|
||||||
unit: TimeUnit::Week,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 4,
|
|
||||||
unit: TimeUnit::Week,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 1,
|
|
||||||
unit: TimeUnit::Month,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 8,
|
|
||||||
unit: TimeUnit::Month,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 11,
|
|
||||||
unit: TimeUnit::Month,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 1,
|
|
||||||
unit: TimeUnit::Year,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 2,
|
|
||||||
unit: TimeUnit::Year,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 3,
|
|
||||||
unit: TimeUnit::Year,
|
|
||||||
},
|
|
||||||
TimeAgo {
|
|
||||||
n: 4,
|
|
||||||
unit: TimeUnit::Year,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let json_file = File::open(json_path).unwrap();
|
|
||||||
let strings_map: BTreeMap<Language, Vec<String>> =
|
|
||||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
|
||||||
|
|
||||||
strings_map.iter().for_each(|(lang, strings)| {
|
|
||||||
assert_eq!(strings.len(), expect.len());
|
|
||||||
strings.iter().enumerate().for_each(|(n, s)| {
|
|
||||||
assert_eq!(
|
|
||||||
parse(*lang, s),
|
|
||||||
Some(expect[n]),
|
|
||||||
"Language: {}, n: {}",
|
|
||||||
lang,
|
|
||||||
n
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn t_timeago_table() {
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
struct TimeagoTable {
|
|
||||||
entries: BTreeMap<Language, BTreeMap<TimeUnit, TimeagoTableEntry>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
struct TimeagoTableEntry {
|
|
||||||
cases: BTreeMap<String, u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let json_path = Path::new("testfiles/date/timeago_table.json");
|
|
||||||
let json_file = File::open(json_path).unwrap();
|
|
||||||
let timeago_table: TimeagoTable =
|
|
||||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
|
||||||
let mut n_cases = 0;
|
|
||||||
|
|
||||||
timeago_table.entries.iter().for_each(|(lang, entries)| {
|
|
||||||
entries.iter().for_each(|(t, entry)| {
|
|
||||||
entry.cases.iter().for_each(|(txt, n)| {
|
|
||||||
let timeago = parse(*lang, txt);
|
|
||||||
assert_eq!(
|
|
||||||
timeago,
|
|
||||||
Some(TimeAgo { n: *n, unit: *t }),
|
|
||||||
"lang: {}, txt: {}",
|
|
||||||
lang,
|
|
||||||
txt
|
|
||||||
);
|
|
||||||
|
|
||||||
n_cases += 1;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(n_cases, 1065)
|
|
||||||
}
|
|
||||||
}
|
|
43
src/util.rs
43
src/util.rs
|
@ -1,8 +1,7 @@
|
||||||
use std::{collections::BTreeMap, str::FromStr};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -19,28 +18,26 @@ where
|
||||||
.map(|c| c.get(cg).unwrap().as_str().to_owned())
|
.map(|c| c.get(cg).unwrap().as_str().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a random string with given length and byte charset.
|
/// Generates a random string with given length and byte charset.
|
||||||
fn random_string(charset: &[u8], length: usize) -> String {
|
fn random_string(charset: &[u8], length: usize) -> String {
|
||||||
let mut result = String::with_capacity(length);
|
let mut result = String::with_capacity(length);
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
for _ in 0..length {
|
unsafe {
|
||||||
result.push(char::from(charset[rng.gen_range(0..charset.len())]));
|
for _ in 0..length {
|
||||||
|
result.push(char::from(
|
||||||
|
*charset.get_unchecked(rng.gen_range(0..charset.len())),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a 16 characters long random string used as a CPN (Content Playback Nonce)
|
|
||||||
pub fn generate_content_playback_nonce() -> String {
|
pub fn generate_content_playback_nonce() -> String {
|
||||||
random_string(CONTENT_PLAYBACK_NONCE_ALPHABET, 16)
|
random_string(CONTENT_PLAYBACK_NONCE_ALPHABET, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split an URL into its base string and parameter map
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// `example.com/api?k1=v1&k2=v2 => example.com/api; {k1: v1, k2: v2}`
|
|
||||||
pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>)> {
|
pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>)> {
|
||||||
let parsed_url = Url::parse(url)?;
|
let parsed_url = Url::parse(url)?;
|
||||||
let url_params: BTreeMap<String, String> = parsed_url
|
let url_params: BTreeMap<String, String> = parsed_url
|
||||||
|
@ -53,27 +50,3 @@ pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>)> {
|
||||||
|
|
||||||
Ok((url_base.to_string(), url_params))
|
Ok((url_base.to_string(), url_params))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a string after removing all non-numeric characters
|
|
||||||
pub fn parse_numeric<F>(string: &str) -> Result<F, F::Err>
|
|
||||||
where
|
|
||||||
F: FromStr,
|
|
||||||
{
|
|
||||||
static NUM_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new("\\D+").unwrap());
|
|
||||||
NUM_PATTERN.replace_all(string, "").parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("1.000", 1000)]
|
|
||||||
#[case("4 Hello World 2", 42)]
|
|
||||||
fn t_parse_num(#[case] string: &str, #[case] expect: u32) {
|
|
||||||
let n = parse_numeric::<u32>(string).unwrap();
|
|
||||||
assert_eq!(n, expect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,902 +0,0 @@
|
||||||
{
|
|
||||||
"supplemental": {
|
|
||||||
"version": {
|
|
||||||
"_unicodeVersion": "13.0.0",
|
|
||||||
"_cldrVersion": "37"
|
|
||||||
},
|
|
||||||
"plurals-type-cardinal": {
|
|
||||||
"af": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ak": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"am": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"an": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ar": {
|
|
||||||
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …",
|
|
||||||
"pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ars": {
|
|
||||||
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …",
|
|
||||||
"pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"as": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"asa": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ast": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"az": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"be": {
|
|
||||||
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …",
|
|
||||||
"pluralRule-count-few": "n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …",
|
|
||||||
"pluralRule-count-many": "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …"
|
|
||||||
},
|
|
||||||
"bem": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bez": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bg": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bho": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bm": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bn": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bo": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"br": {
|
|
||||||
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …",
|
|
||||||
"pluralRule-count-two": "n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …",
|
|
||||||
"pluralRule-count-few": "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …",
|
|
||||||
"pluralRule-count-many": "n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …"
|
|
||||||
},
|
|
||||||
"brx": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"bs": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ca": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ce": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ceb": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …"
|
|
||||||
},
|
|
||||||
"cgg": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"chr": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ckb": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"cs": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4",
|
|
||||||
"pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
|
|
||||||
},
|
|
||||||
"cy": {
|
|
||||||
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-few": "n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000",
|
|
||||||
"pluralRule-count-many": "n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000",
|
|
||||||
"pluralRule-count-other": " @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"da": {
|
|
||||||
"pluralRule-count-one": "n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"de": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"dsb": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"dv": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"dz": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ee": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"el": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"eo": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"et": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"eu": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"fa": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ff": {
|
|
||||||
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"fi": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"fil": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …"
|
|
||||||
},
|
|
||||||
"fo": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"fur": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"fy": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ga": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-few": "n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000",
|
|
||||||
"pluralRule-count-many": "n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"gd": {
|
|
||||||
"pluralRule-count-one": "n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000",
|
|
||||||
"pluralRule-count-few": "n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00",
|
|
||||||
"pluralRule-count-other": " @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"gl": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"gsw": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"gu": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"guw": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"gv": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …",
|
|
||||||
"pluralRule-count-two": "v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …",
|
|
||||||
"pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 3~10, 13~19, 23, 103, 1003, …"
|
|
||||||
},
|
|
||||||
"ha": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"haw": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"he": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-two": "i = 2 and v = 0 @integer 2",
|
|
||||||
"pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"hi": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"hr": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"hsb": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"hu": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"hy": {
|
|
||||||
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ia": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ig": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ii": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"in": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"io": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"is": {
|
|
||||||
"pluralRule-count-one": "t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"it": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"iu": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"iw": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-two": "i = 2 and v = 0 @integer 2",
|
|
||||||
"pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"jbo": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"jgo": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ji": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"jmc": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"jv": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"jw": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ka": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kab": {
|
|
||||||
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kaj": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kcg": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kde": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kea": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kk": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kkj": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kl": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"km": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kn": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ko": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ks": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ksb": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ksh": {
|
|
||||||
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ku": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"kw": {
|
|
||||||
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n % 100 = 2,22,42,62,82 or n % 1000 = 0 and n % 100000 = 1000..20000,40000,60000,80000 or n != 0 and n % 1000000 = 100000 @integer 2, 22, 42, 62, 82, 102, 122, 142, 1000, 10000, 100000, … @decimal 2.0, 22.0, 42.0, 62.0, 82.0, 102.0, 122.0, 142.0, 1000.0, 10000.0, 100000.0, …",
|
|
||||||
"pluralRule-count-few": "n % 100 = 3,23,43,63,83 @integer 3, 23, 43, 63, 83, 103, 123, 143, 1003, … @decimal 3.0, 23.0, 43.0, 63.0, 83.0, 103.0, 123.0, 143.0, 1003.0, …",
|
|
||||||
"pluralRule-count-many": "n != 1 and n % 100 = 1,21,41,61,81 @integer 21, 41, 61, 81, 101, 121, 141, 161, 1001, … @decimal 21.0, 41.0, 61.0, 81.0, 101.0, 121.0, 141.0, 161.0, 1001.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 4~19, 100, 1004, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.1, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ky": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lag": {
|
|
||||||
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
|
|
||||||
"pluralRule-count-one": "i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lb": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lkt": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ln": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lo": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lt": {
|
|
||||||
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …",
|
|
||||||
"pluralRule-count-few": "n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, …",
|
|
||||||
"pluralRule-count-many": "f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"lv": {
|
|
||||||
"pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …"
|
|
||||||
},
|
|
||||||
"mas": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"mg": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"mgo": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"mk": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ml": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"mn": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"mo": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-few": "v != 0 or n = 0 or n % 100 = 2..19 @integer 0, 2~16, 102, 1002, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …"
|
|
||||||
},
|
|
||||||
"mr": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"mt": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-few": "n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …",
|
|
||||||
"pluralRule-count-many": "n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"my": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nah": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"naq": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nb": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nd": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ne": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nn": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nnh": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"no": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nqo": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nr": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nso": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ny": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"nyn": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"om": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"or": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"os": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"osa": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"pa": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"pap": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"pcm": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"pl": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …",
|
|
||||||
"pluralRule-count-many": "v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
|
|
||||||
"pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"prg": {
|
|
||||||
"pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …"
|
|
||||||
},
|
|
||||||
"ps": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"pt": {
|
|
||||||
"pluralRule-count-one": "i = 0..1 @integer 0, 1 @decimal 0.0~1.5",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"pt-PT": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"rm": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ro": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-few": "v != 0 or n = 0 or n % 100 = 2..19 @integer 0, 2~16, 102, 1002, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …"
|
|
||||||
},
|
|
||||||
"rof": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …",
|
|
||||||
"pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
|
|
||||||
"pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"rwk": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sah": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"saq": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sat": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sc": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"scn": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sd": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sdh": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"se": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"seh": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ses": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sg": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sh": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"shi": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-few": "n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00",
|
|
||||||
"pluralRule-count-other": " @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"si": {
|
|
||||||
"pluralRule-count-one": "n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sk": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4",
|
|
||||||
"pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
|
|
||||||
},
|
|
||||||
"sl": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …",
|
|
||||||
"pluralRule-count-two": "v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
|
|
||||||
},
|
|
||||||
"sma": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"smi": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"smj": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"smn": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sms": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sn": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"so": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sq": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sr": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
|
|
||||||
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ss": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ssy": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"st": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"su": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sv": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"sw": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"syr": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ta": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"te": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"teo": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"th": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ti": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"tig": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"tk": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"tl": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
|
|
||||||
"pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …"
|
|
||||||
},
|
|
||||||
"tn": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"tr": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ts": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"tzm": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0",
|
|
||||||
"pluralRule-count-other": " @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ug": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"uk": {
|
|
||||||
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …",
|
|
||||||
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …",
|
|
||||||
"pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
|
|
||||||
"pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ur": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"uz": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"ve": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"vi": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"vo": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"vun": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"wa": {
|
|
||||||
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"wae": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"wo": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"xh": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"xog": {
|
|
||||||
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"yi": {
|
|
||||||
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
|
|
||||||
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"yo": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"yue": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"zh": {
|
|
||||||
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
},
|
|
||||||
"zu": {
|
|
||||||
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
|
|
||||||
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue