fix: fix testsuite
This commit is contained in:
parent
313b1bf1ba
commit
22a2ce0559
159
Cargo.lock
generated
159
Cargo.lock
generated
@ -1093,6 +1093,33 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e1761c0e16f8883bbbb8ce5990867f4f06bf11a0253da6495a04ce4b6ef0ec"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"color-spantrace",
|
||||
"eyre",
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-spantrace"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ddd8d5bfda1e11a501d0a7303f3bfed9aa632ebdb859be40d0fd70478ed70d5"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-core",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
@ -1328,6 +1355,31 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"parking_lot 0.12.3",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
@ -1919,6 +1971,16 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
@ -2218,6 +2280,15 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuzzy-matcher"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
|
||||
dependencies = [
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
@ -2932,6 +3003,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@ -3000,6 +3077,23 @@ dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inquire"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"crossterm",
|
||||
"dyn-clone",
|
||||
"fuzzy-matcher",
|
||||
"fxhash",
|
||||
"newline-converter",
|
||||
"once_cell",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.43.1"
|
||||
@ -3816,6 +3910,18 @@ dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
@ -3953,6 +4059,15 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "newline-converter"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
@ -3987,7 +4102,7 @@ dependencies = [
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"mio 1.0.4",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.52.0",
|
||||
@ -4318,6 +4433,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
@ -5083,6 +5204,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"cocoon",
|
||||
"color-eyre",
|
||||
"ctor",
|
||||
"dotenvy",
|
||||
"downloader",
|
||||
@ -5092,6 +5214,7 @@ dependencies = [
|
||||
"futures",
|
||||
"html-escape",
|
||||
"http",
|
||||
"inquire",
|
||||
"insta",
|
||||
"ipnetwork",
|
||||
"itertools 0.14.0",
|
||||
@ -5106,6 +5229,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"opendal",
|
||||
"openidconnect",
|
||||
"percent-encoding",
|
||||
"quirks_path",
|
||||
"rand 0.9.1",
|
||||
"regex",
|
||||
@ -6186,6 +6310,27 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
@ -6970,7 +7115,7 @@ dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.4",
|
||||
"parking_lot 0.12.3",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
@ -7228,6 +7373,16 @@ dependencies = [
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-futures"
|
||||
version = "0.2.5"
|
||||
|
@ -53,6 +53,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||
axum-extra = "0.10"
|
||||
mockito = { version = "1.6.1" }
|
||||
convert_case = "0.8"
|
||||
color-eyre = "0.6.4"
|
||||
inquire = "0.7.5"
|
||||
|
||||
[patch.crates-io]
|
||||
seaography = { git = "https://github.com/dumtruck/seaography.git", rev = "10ba248" }
|
||||
|
@ -15,7 +15,7 @@ required-features = []
|
||||
|
||||
[features]
|
||||
default = []
|
||||
playground = ["dep:mockito"]
|
||||
playground = ["dep:mockito", "dep:inquire", "dep:color-eyre"]
|
||||
testcontainers = [
|
||||
"dep:testcontainers",
|
||||
"dep:testcontainers-modules",
|
||||
@ -118,7 +118,9 @@ rust_decimal = "1.37.1"
|
||||
reqwest_cookie_store = "0.8.0"
|
||||
nanoid = "0.4.0"
|
||||
jwtk = "0.4.0"
|
||||
|
||||
color-eyre = { workspace = true, optional = true }
|
||||
inquire = { workspace = true, optional = true }
|
||||
percent-encoding = "2.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "3"
|
||||
@ -126,3 +128,5 @@ insta = { version = "1", features = ["redactions", "toml", "filters"] }
|
||||
rstest = "0.25"
|
||||
ctor = "0.4.0"
|
||||
mockito = { workspace = true }
|
||||
inquire = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
|
249
apps/recorder/examples/mikan_doppel_season_subscription.rs
Normal file
249
apps/recorder/examples/mikan_doppel_season_subscription.rs
Normal file
@ -0,0 +1,249 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::{Result, eyre::OptionExt};
|
||||
use fetch::{FetchError, HttpClientConfig, fetch_bytes, fetch_html, fetch_image, reqwest};
|
||||
use inquire::{Password, Text, validator::Validation};
|
||||
use recorder::{
|
||||
crypto::UserPassCredential,
|
||||
extract::mikan::{
|
||||
MikanClient, MikanConfig, MikanRssItem, build_mikan_bangumi_expand_subscribed_url,
|
||||
extract_mikan_bangumi_index_meta_list_from_season_flow_fragment,
|
||||
extract_mikan_bangumi_meta_from_expand_subscribed_fragment,
|
||||
},
|
||||
test_utils::mikan::{MikanDoppelMeta, MikanDoppelPath},
|
||||
};
|
||||
use scraper::Html;
|
||||
use tokio::fs;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::INFO)
|
||||
.init();
|
||||
std::env::set_current_dir(std::path::Path::new("apps/recorder"))?;
|
||||
|
||||
let mikan_scrape_client = MikanClient::from_config(MikanConfig {
|
||||
http_client: HttpClientConfig {
|
||||
exponential_backoff_max_retries: Some(3),
|
||||
leaky_bucket_max_tokens: Some(2),
|
||||
leaky_bucket_initial_tokens: Some(0),
|
||||
leaky_bucket_refill_tokens: Some(1),
|
||||
leaky_bucket_refill_interval: Some(Duration::from_millis(1000)),
|
||||
user_agent: Some(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
|
||||
Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
|
||||
.to_string(),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
base_url: Url::parse("https://mikanani.me")?,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let username_validator = |input: &str| {
|
||||
if input.trim().is_empty() {
|
||||
Ok(Validation::Invalid("Username cannot be empty".into()))
|
||||
} else {
|
||||
Ok(Validation::Valid)
|
||||
}
|
||||
};
|
||||
let password_validator = |input: &str| {
|
||||
if input.trim().is_empty() {
|
||||
Ok(Validation::Invalid("Password cannot be empty".into()))
|
||||
} else {
|
||||
Ok(Validation::Valid)
|
||||
}
|
||||
};
|
||||
let username = Text::new("Please enter your mikan username:")
|
||||
.with_validator(username_validator)
|
||||
.prompt()?;
|
||||
let password = Password::new("Please enter your mikan password:")
|
||||
.without_confirmation()
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.with_validator(password_validator)
|
||||
.prompt()?;
|
||||
|
||||
let mikan_scrape_client = mikan_scrape_client
|
||||
.fork_with_credential(UserPassCredential {
|
||||
username,
|
||||
password,
|
||||
user_agent: None,
|
||||
cookies: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
tracing::info!("Checking if logged in...");
|
||||
if !mikan_scrape_client.has_login().await? {
|
||||
tracing::info!("Logging in to mikan...");
|
||||
mikan_scrape_client.login().await?;
|
||||
tracing::info!("Logged in to mikan");
|
||||
}
|
||||
|
||||
let mikan_base_url = mikan_scrape_client.base_url().clone();
|
||||
tracing::info!("Scraping season subscription...");
|
||||
let season_subscription =
|
||||
fs::read("tests/resources/mikan/BangumiCoverFlow-2025-spring.html").await?;
|
||||
let html = Html::parse_fragment(String::from_utf8(season_subscription)?.as_str());
|
||||
let bangumi_index_list =
|
||||
extract_mikan_bangumi_index_meta_list_from_season_flow_fragment(&html, &mikan_base_url);
|
||||
|
||||
for bangumi_index in bangumi_index_list {
|
||||
let bangumi_meta = {
|
||||
let bangumi_expand_subscribed_url = build_mikan_bangumi_expand_subscribed_url(
|
||||
mikan_base_url.clone(),
|
||||
bangumi_index.mikan_bangumi_id.as_ref(),
|
||||
);
|
||||
let bangumi_expand_subscribed_doppel_path =
|
||||
MikanDoppelPath::new(bangumi_expand_subscribed_url.clone());
|
||||
tracing::info!(
|
||||
bangumi_title = bangumi_index.bangumi_title,
|
||||
"Scraping bangumi expand subscribed..."
|
||||
);
|
||||
let bangumi_expand_subscribed_data =
|
||||
if !bangumi_expand_subscribed_doppel_path.exists_any() {
|
||||
let bangumi_expand_subscribed_data =
|
||||
fetch_html(&mikan_scrape_client, bangumi_expand_subscribed_url).await?;
|
||||
bangumi_expand_subscribed_doppel_path.write(&bangumi_expand_subscribed_data)?;
|
||||
tracing::info!(
|
||||
bangumi_title = bangumi_index.bangumi_title,
|
||||
"Bangumi expand subscribed saved"
|
||||
);
|
||||
bangumi_expand_subscribed_data
|
||||
} else {
|
||||
tracing::info!(
|
||||
bangumi_title = bangumi_index.bangumi_title,
|
||||
"Bangumi expand subscribed already exists"
|
||||
);
|
||||
String::from_utf8(bangumi_expand_subscribed_doppel_path.read()?)?
|
||||
};
|
||||
|
||||
let html = Html::parse_fragment(&bangumi_expand_subscribed_data);
|
||||
extract_mikan_bangumi_meta_from_expand_subscribed_fragment(
|
||||
&html,
|
||||
bangumi_index.clone(),
|
||||
mikan_base_url.clone(),
|
||||
)
|
||||
.ok_or_eyre(format!(
|
||||
"Failed to extract bangumi meta from expand subscribed fragment: {:?}",
|
||||
bangumi_index.bangumi_title
|
||||
))
|
||||
}?;
|
||||
{
|
||||
if let Some(poster_url) = bangumi_meta.origin_poster_src.as_ref() {
|
||||
let poster_doppel_path = MikanDoppelPath::new(poster_url.clone());
|
||||
tracing::info!(
|
||||
title = bangumi_meta.bangumi_title,
|
||||
"Scraping bangumi poster..."
|
||||
);
|
||||
if !poster_doppel_path.exists_any() {
|
||||
let poster_data = fetch_image(&mikan_scrape_client, poster_url.clone()).await?;
|
||||
poster_doppel_path.write(&poster_data)?;
|
||||
tracing::info!(title = bangumi_meta.bangumi_title, "Bangumi poster saved");
|
||||
} else {
|
||||
tracing::info!(
|
||||
title = bangumi_meta.bangumi_title,
|
||||
"Bangumi poster already exists"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
let bangumi_homepage_url = bangumi_meta
|
||||
.bangumi_hash()
|
||||
.build_homepage_url(mikan_base_url.clone());
|
||||
let bangumi_homepage_doppel_path = MikanDoppelPath::new(bangumi_homepage_url.clone());
|
||||
tracing::info!(
|
||||
title = bangumi_meta.bangumi_title,
|
||||
"Scraping bangumi homepage..."
|
||||
);
|
||||
if !bangumi_homepage_doppel_path.exists_any() {
|
||||
let bangumi_homepage_data =
|
||||
fetch_html(&mikan_scrape_client, bangumi_homepage_url).await?;
|
||||
bangumi_homepage_doppel_path.write(&bangumi_homepage_data)?;
|
||||
tracing::info!(title = bangumi_meta.bangumi_title, "Bangumi homepage saved");
|
||||
} else {
|
||||
tracing::info!(
|
||||
title = bangumi_meta.bangumi_title,
|
||||
"Bangumi homepage already exists"
|
||||
);
|
||||
}
|
||||
}
|
||||
let rss_items = {
|
||||
let bangumi_rss_url = bangumi_meta
|
||||
.bangumi_hash()
|
||||
.build_rss_url(mikan_base_url.clone());
|
||||
let bangumi_rss_doppel_path = MikanDoppelPath::new(bangumi_rss_url.clone());
|
||||
tracing::info!(
|
||||
title = bangumi_meta.bangumi_title,
|
||||
"Scraping bangumi rss..."
|
||||
);
|
||||
let bangumi_rss_data = if !bangumi_rss_doppel_path.exists_any() {
|
||||
let bangumi_rss_data = fetch_html(&mikan_scrape_client, bangumi_rss_url).await?;
|
||||
bangumi_rss_doppel_path.write(&bangumi_rss_data)?;
|
||||
tracing::info!(title = bangumi_meta.bangumi_title, "Bangumi rss saved");
|
||||
bangumi_rss_data
|
||||
} else {
|
||||
tracing::info!(
|
||||
title = bangumi_meta.bangumi_title,
|
||||
"Bangumi rss already exists"
|
||||
);
|
||||
String::from_utf8(bangumi_rss_doppel_path.read()?)?
|
||||
};
|
||||
let rss_items = rss::Channel::read_from(bangumi_rss_data.as_bytes())?.items;
|
||||
rss_items
|
||||
.into_iter()
|
||||
.map(MikanRssItem::try_from)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}?;
|
||||
for rss_item in rss_items {
|
||||
{
|
||||
let episode_homepage_url = rss_item.homepage;
|
||||
let episode_homepage_doppel_path =
|
||||
MikanDoppelPath::new(episode_homepage_url.clone());
|
||||
tracing::info!(title = rss_item.title, "Scraping episode...");
|
||||
if !episode_homepage_doppel_path.exists_any() {
|
||||
let episode_homepage_data =
|
||||
fetch_html(&mikan_scrape_client, episode_homepage_url).await?;
|
||||
episode_homepage_doppel_path.write(&episode_homepage_data)?;
|
||||
tracing::info!(title = rss_item.title, "Episode saved");
|
||||
} else {
|
||||
tracing::info!(title = rss_item.title, "Episode already exists");
|
||||
};
|
||||
}
|
||||
{
|
||||
let episode_torrent_url = rss_item.url;
|
||||
let episode_torrent_doppel_path = MikanDoppelPath::new(episode_torrent_url.clone());
|
||||
tracing::info!(title = rss_item.title, "Scraping episode torrent...");
|
||||
if !episode_torrent_doppel_path.exists_any() {
|
||||
match fetch_bytes(&mikan_scrape_client, episode_torrent_url).await {
|
||||
Ok(episode_torrent_data) => {
|
||||
episode_torrent_doppel_path.write(&episode_torrent_data)?;
|
||||
tracing::info!(title = rss_item.title, "Episode torrent saved");
|
||||
}
|
||||
Err(e) => {
|
||||
if let FetchError::ReqwestError { source } = &e
|
||||
&& source
|
||||
.status()
|
||||
.is_some_and(|status| status == reqwest::StatusCode::NOT_FOUND)
|
||||
{
|
||||
tracing::warn!(
|
||||
title = rss_item.title,
|
||||
"Episode torrent not found, maybe deleted since new version"
|
||||
);
|
||||
episode_torrent_doppel_path
|
||||
.write_meta(MikanDoppelMeta { status: 404 })?;
|
||||
} else {
|
||||
Err(e)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::info!(title = rss_item.title, "Episode torrent already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tracing::info!("Scraping season subscription done");
|
||||
Ok(())
|
||||
}
|
140
apps/recorder/examples/mikan_doppel_subscriber_subscription.rs
Normal file
140
apps/recorder/examples/mikan_doppel_subscriber_subscription.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use fetch::{FetchError, HttpClientConfig, fetch_bytes, fetch_html, fetch_image, reqwest};
|
||||
use recorder::{
|
||||
errors::RecorderResult,
|
||||
extract::mikan::{
|
||||
MikanClient, MikanConfig, MikanRssItem,
|
||||
extract_mikan_episode_meta_from_episode_homepage_html,
|
||||
},
|
||||
test_utils::mikan::{MikanDoppelMeta, MikanDoppelPath},
|
||||
};
|
||||
use scraper::Html;
|
||||
use tokio::fs;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> RecorderResult<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::INFO)
|
||||
.init();
|
||||
std::env::set_current_dir(std::path::Path::new("apps/recorder"))?;
|
||||
|
||||
let mikan_scrape_client = MikanClient::from_config(MikanConfig {
|
||||
http_client: HttpClientConfig {
|
||||
exponential_backoff_max_retries: Some(3),
|
||||
leaky_bucket_max_tokens: Some(2),
|
||||
leaky_bucket_initial_tokens: Some(0),
|
||||
leaky_bucket_refill_tokens: Some(1),
|
||||
leaky_bucket_refill_interval: Some(Duration::from_millis(500)),
|
||||
user_agent: Some(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
|
||||
Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
|
||||
.to_string(),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
base_url: Url::parse("https://mikanani.me")?,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mikan_base_url = mikan_scrape_client.base_url().clone();
|
||||
tracing::info!("Scraping subscriber subscription...");
|
||||
let subscriber_subscription =
|
||||
fs::read("tests/resources/mikan/MyBangumi-2025-spring.rss").await?;
|
||||
let channel = rss::Channel::read_from(&subscriber_subscription[..])?;
|
||||
let rss_items: Vec<MikanRssItem> = channel
|
||||
.items
|
||||
.into_iter()
|
||||
.map(MikanRssItem::try_from)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
for rss_item in rss_items {
|
||||
let episode_homepage_meta = {
|
||||
tracing::info!(title = rss_item.title, "Scraping episode homepage...");
|
||||
let episode_homepage_url = rss_item.homepage;
|
||||
let episode_homepage_doppel_path = MikanDoppelPath::new(episode_homepage_url.clone());
|
||||
let episode_homepage_data = if !episode_homepage_doppel_path.exists_any() {
|
||||
let episode_homepage_data =
|
||||
fetch_html(&mikan_scrape_client, episode_homepage_url.clone()).await?;
|
||||
episode_homepage_doppel_path.write(&episode_homepage_data)?;
|
||||
tracing::info!(title = rss_item.title, "Episode homepage saved");
|
||||
episode_homepage_data
|
||||
} else {
|
||||
tracing::info!(title = rss_item.title, "Episode homepage already exists");
|
||||
String::from_utf8(episode_homepage_doppel_path.read()?)?
|
||||
};
|
||||
let html = Html::parse_document(&episode_homepage_data);
|
||||
extract_mikan_episode_meta_from_episode_homepage_html(
|
||||
&html,
|
||||
mikan_base_url.clone(),
|
||||
episode_homepage_url,
|
||||
)
|
||||
}?;
|
||||
|
||||
{
|
||||
let episode_torrent_url = rss_item.url;
|
||||
let episode_torrent_doppel_path = MikanDoppelPath::new(episode_torrent_url.clone());
|
||||
tracing::info!(title = rss_item.title, "Scraping episode torrent...");
|
||||
if !episode_torrent_doppel_path.exists_any() {
|
||||
match fetch_bytes(&mikan_scrape_client, episode_torrent_url).await {
|
||||
Ok(episode_torrent_data) => {
|
||||
episode_torrent_doppel_path.write(&episode_torrent_data)?;
|
||||
tracing::info!(title = rss_item.title, "Episode torrent saved");
|
||||
}
|
||||
Err(e) => {
|
||||
if let FetchError::ReqwestError { source } = &e
|
||||
&& source
|
||||
.status()
|
||||
.is_some_and(|status| status == reqwest::StatusCode::NOT_FOUND)
|
||||
{
|
||||
tracing::warn!(
|
||||
title = rss_item.title,
|
||||
"Episode torrent not found, maybe deleted since new version"
|
||||
);
|
||||
episode_torrent_doppel_path
|
||||
.write_meta(MikanDoppelMeta { status: 404 })?;
|
||||
} else {
|
||||
Err(e)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(title = rss_item.title, "Episode torrent saved");
|
||||
} else {
|
||||
tracing::info!(title = rss_item.title, "Episode torrent already exists");
|
||||
}
|
||||
}
|
||||
{
|
||||
if let Some(episode_poster_url) = episode_homepage_meta.origin_poster_src.as_ref() {
|
||||
let episode_poster_doppel_path = MikanDoppelPath::new(episode_poster_url.clone());
|
||||
tracing::info!(title = rss_item.title, "Scraping episode poster...");
|
||||
if !episode_poster_doppel_path.exists_any() {
|
||||
let episode_poster_data =
|
||||
fetch_image(&mikan_scrape_client, episode_poster_url.clone()).await?;
|
||||
episode_poster_doppel_path.write(&episode_poster_data)?;
|
||||
tracing::info!(title = rss_item.title, "Episode poster saved");
|
||||
} else {
|
||||
tracing::info!(title = rss_item.title, "Episode poster already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let bangumi_homepage_url = episode_homepage_meta
|
||||
.bangumi_hash()
|
||||
.build_homepage_url(mikan_base_url.clone());
|
||||
let bangumi_homepage_doppel_path = MikanDoppelPath::new(bangumi_homepage_url.clone());
|
||||
tracing::info!(title = rss_item.title, "Scraping bangumi homepage...");
|
||||
if !bangumi_homepage_doppel_path.exists_any() {
|
||||
let bangumi_homepage_data =
|
||||
fetch_html(&mikan_scrape_client, bangumi_homepage_url).await?;
|
||||
bangumi_homepage_doppel_path.write(&bangumi_homepage_data)?;
|
||||
tracing::info!(title = rss_item.title, "Bangumi homepage saved");
|
||||
} else {
|
||||
tracing::info!(title = rss_item.title, "Bangumi homepage already exists");
|
||||
};
|
||||
}
|
||||
}
|
||||
tracing::info!("Scraping subscriber subscription done");
|
||||
Ok(())
|
||||
}
|
@ -105,7 +105,7 @@ pub enum RecorderError {
|
||||
ModelEntityNotFound { entity: Cow<'static, str> },
|
||||
#[snafu(transparent)]
|
||||
FetchError { source: FetchError },
|
||||
#[snafu(display("Credential3rdError: {source}"))]
|
||||
#[snafu(display("Credential3rdError: {message}, source = {source}"))]
|
||||
Credential3rdError {
|
||||
message: String,
|
||||
#[snafu(source(from(Box<dyn std::error::Error + Send + Sync>, OptDynErr::some)))]
|
||||
@ -113,6 +113,8 @@ pub enum RecorderError {
|
||||
},
|
||||
#[snafu(transparent)]
|
||||
CryptoError { source: CryptoError },
|
||||
#[snafu(transparent)]
|
||||
StringFromUtf8Error { source: std::string::FromUtf8Error },
|
||||
#[snafu(display("{message}"))]
|
||||
Whatever {
|
||||
message: String,
|
||||
|
@ -2,6 +2,7 @@ use std::{fmt::Debug, ops::Deref, sync::Arc};
|
||||
|
||||
use fetch::{HttpClient, HttpClientTrait};
|
||||
use maplit::hashmap;
|
||||
use scraper::{Html, Selector};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue::Set, ColumnTrait, DbErr, EntityTrait, QueryFilter, TryIntoModel,
|
||||
};
|
||||
@ -68,50 +69,44 @@ impl MikanClient {
|
||||
message: "mikan login failed, credential required".to_string(),
|
||||
source: None.into(),
|
||||
})?;
|
||||
|
||||
let login_page_url = {
|
||||
let mut u = self.base_url.join(MIKAN_LOGIN_PAGE_PATH)?;
|
||||
u.set_query(Some(MIKAN_LOGIN_PAGE_SEARCH));
|
||||
u
|
||||
};
|
||||
|
||||
// access login page to get antiforgery cookie
|
||||
self.http_client
|
||||
.get(login_page_url.clone())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| RecorderError::Credential3rdError {
|
||||
message: "failed to get mikan login page".to_string(),
|
||||
source: OptDynErr::some_boxed(error),
|
||||
})?;
|
||||
let antiforgery_token = {
|
||||
// access login page to get antiforgery cookie
|
||||
let login_page_html = self
|
||||
.http_client
|
||||
.get(login_page_url.clone())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| RecorderError::Credential3rdError {
|
||||
message: "failed to get mikan login page".to_string(),
|
||||
source: OptDynErr::some_boxed(error),
|
||||
})?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
let antiforgery_cookie = {
|
||||
let cookie_store_lock = self.http_client.cookie_store.clone().ok_or_else(|| {
|
||||
RecorderError::Credential3rdError {
|
||||
message: "failed to get cookie store".to_string(),
|
||||
let login_page_html = Html::parse_document(&login_page_html);
|
||||
|
||||
let antiforgery_selector =
|
||||
Selector::parse("input[name='__RequestVerificationToken']").unwrap();
|
||||
|
||||
login_page_html
|
||||
.select(&antiforgery_selector)
|
||||
.next()
|
||||
.and_then(|element| element.value().attr("value").map(|value| value.to_string()))
|
||||
.ok_or_else(|| RecorderError::Credential3rdError {
|
||||
message: "mikan login failed, failed to get antiforgery token".to_string(),
|
||||
source: None.into(),
|
||||
}
|
||||
})?;
|
||||
let cookie_store =
|
||||
cookie_store_lock
|
||||
.read()
|
||||
.map_err(|_| RecorderError::Credential3rdError {
|
||||
message: "failed to read cookie store".to_string(),
|
||||
source: None.into(),
|
||||
})?;
|
||||
|
||||
cookie_store
|
||||
.matches(&login_page_url)
|
||||
.iter()
|
||||
.find(|cookie| cookie.name().starts_with(".AspNetCore.Antiforgery."))
|
||||
.map(|cookie| cookie.value().to_string())
|
||||
}
|
||||
.ok_or_else(|| RecorderError::Credential3rdError {
|
||||
message: "mikan login failed, failed to get antiforgery cookie".to_string(),
|
||||
source: None.into(),
|
||||
})?;
|
||||
})
|
||||
}?;
|
||||
|
||||
let login_post_form = hashmap! {
|
||||
"__RequestVerificationToken".to_string() => antiforgery_cookie,
|
||||
"__RequestVerificationToken".to_string() => antiforgery_token,
|
||||
"UserName".to_string() => userpass_credential.username.clone(),
|
||||
"Password".to_string() => userpass_credential.password.clone(),
|
||||
"RememberMe".to_string() => "true".to_string(),
|
||||
@ -185,12 +180,33 @@ impl MikanClient {
|
||||
}
|
||||
|
||||
pub async fn fork_with_credential(
|
||||
&self,
|
||||
userpass_credential: UserPassCredential,
|
||||
) -> RecorderResult<Self> {
|
||||
let mut fork = self
|
||||
.http_client
|
||||
.fork()
|
||||
.attach_cookies(userpass_credential.cookies.as_deref())?;
|
||||
|
||||
if let Some(user_agent) = userpass_credential.user_agent.as_ref() {
|
||||
fork = fork.attach_user_agent(user_agent);
|
||||
}
|
||||
|
||||
let userpass_credential_opt = Some(userpass_credential);
|
||||
|
||||
Ok(Self {
|
||||
http_client: HttpClient::from_fork(fork)?,
|
||||
base_url: self.base_url.clone(),
|
||||
origin_url: self.origin_url.clone(),
|
||||
userpass_credential: userpass_credential_opt,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn fork_with_credential_id(
|
||||
&self,
|
||||
ctx: Arc<dyn AppContextTrait>,
|
||||
credential_id: i32,
|
||||
) -> RecorderResult<Self> {
|
||||
let mut fork = self.http_client.fork();
|
||||
|
||||
let credential = credential_3rd::Model::find_by_id(ctx.clone(), credential_id).await?;
|
||||
if let Some(credential) = credential {
|
||||
if credential.credential_type != Credential3rdType::Mikan {
|
||||
@ -203,20 +219,7 @@ impl MikanClient {
|
||||
let userpass_credential: UserPassCredential =
|
||||
credential.try_into_userpass_credential(ctx)?;
|
||||
|
||||
fork = fork.attach_cookies(userpass_credential.cookies.as_deref())?;
|
||||
|
||||
if let Some(user_agent) = userpass_credential.user_agent.as_ref() {
|
||||
fork = fork.attach_user_agent(user_agent);
|
||||
}
|
||||
|
||||
let userpass_credential_opt = Some(userpass_credential);
|
||||
|
||||
Ok(Self {
|
||||
http_client: HttpClient::from_fork(fork)?,
|
||||
base_url: self.base_url.clone(),
|
||||
origin_url: self.origin_url.clone(),
|
||||
userpass_credential: userpass_credential_opt,
|
||||
})
|
||||
self.fork_with_credential(userpass_credential).await
|
||||
} else {
|
||||
Err(RecorderError::from_db_record_not_found(
|
||||
DbErr::RecordNotFound(format!("credential={credential_id} not found")),
|
||||
@ -319,7 +322,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mikan_client = mikan_client
|
||||
.fork_with_credential(app_ctx.clone(), credential_model.id)
|
||||
.fork_with_credential_id(app_ctx.clone(), credential_model.id)
|
||||
.await?;
|
||||
|
||||
mikan_client.login().await?;
|
||||
|
@ -5,4 +5,13 @@ pub const MIKAN_LOGIN_PAGE_PATH: &str = "/Account/Login";
|
||||
pub const MIKAN_LOGIN_PAGE_SEARCH: &str = "ReturnUrl=%2F";
|
||||
pub const MIKAN_ACCOUNT_MANAGE_PAGE_PATH: &str = "/Account/Manage";
|
||||
pub const MIKAN_SEASON_FLOW_PAGE_PATH: &str = "/Home/BangumiCoverFlow";
|
||||
pub const MIKAN_BANGUMI_HOMEPAGE_PATH: &str = "/Home/Bangumi";
|
||||
pub const MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH: &str = "/Home/ExpandBangumi";
|
||||
pub const MIKAN_EPISODE_HOMEPAGE_PATH: &str = "/Home/Episode";
|
||||
pub const MIKAN_BANGUMI_POSTER_PATH: &str = "/images/Bangumi";
|
||||
pub const MIKAN_EPISODE_TORRENT_PATH: &str = "/Download";
|
||||
pub const MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH: &str = "/RSS/MyBangumi";
|
||||
pub const MIKAN_BANGUMI_RSS_PATH: &str = "/RSS/Bangumi";
|
||||
pub const MIKAN_BANGUMI_ID_QUERY_KEY: &str = "bangumiId";
|
||||
pub const MIKAN_FANSUB_ID_QUERY_KEY: &str = "subgroupid";
|
||||
pub const MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY: &str = "token";
|
||||
|
@ -9,8 +9,12 @@ pub use client::MikanClient;
|
||||
pub use config::MikanConfig;
|
||||
pub use constants::{
|
||||
MIKAN_ACCOUNT_MANAGE_PAGE_PATH, MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH,
|
||||
MIKAN_LOGIN_PAGE_PATH, MIKAN_LOGIN_PAGE_SEARCH, MIKAN_POSTER_BUCKET_KEY,
|
||||
MIKAN_SEASON_FLOW_PAGE_PATH, MIKAN_UNKNOWN_FANSUB_ID, MIKAN_UNKNOWN_FANSUB_NAME,
|
||||
MIKAN_BANGUMI_HOMEPAGE_PATH, MIKAN_BANGUMI_ID_QUERY_KEY, MIKAN_BANGUMI_POSTER_PATH,
|
||||
MIKAN_BANGUMI_RSS_PATH, MIKAN_EPISODE_HOMEPAGE_PATH, MIKAN_EPISODE_TORRENT_PATH,
|
||||
MIKAN_FANSUB_ID_QUERY_KEY, MIKAN_LOGIN_PAGE_PATH, MIKAN_LOGIN_PAGE_SEARCH,
|
||||
MIKAN_POSTER_BUCKET_KEY, MIKAN_SEASON_FLOW_PAGE_PATH, MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH,
|
||||
MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY, MIKAN_UNKNOWN_FANSUB_ID,
|
||||
MIKAN_UNKNOWN_FANSUB_NAME,
|
||||
};
|
||||
pub use credential::MikanCredentialForm;
|
||||
pub use subscription::{
|
||||
|
@ -20,8 +20,11 @@ use crate::{
|
||||
html::{extract_background_image_src_from_style_attr, extract_inner_text_from_element_ref},
|
||||
media::extract_image_src_from_str,
|
||||
mikan::{
|
||||
MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH, MIKAN_POSTER_BUCKET_KEY,
|
||||
MIKAN_SEASON_FLOW_PAGE_PATH, MikanClient,
|
||||
MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH, MIKAN_BANGUMI_HOMEPAGE_PATH,
|
||||
MIKAN_BANGUMI_ID_QUERY_KEY, MIKAN_BANGUMI_POSTER_PATH, MIKAN_BANGUMI_RSS_PATH,
|
||||
MIKAN_EPISODE_HOMEPAGE_PATH, MIKAN_FANSUB_ID_QUERY_KEY, MIKAN_POSTER_BUCKET_KEY,
|
||||
MIKAN_SEASON_FLOW_PAGE_PATH, MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH,
|
||||
MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY, MikanClient,
|
||||
},
|
||||
},
|
||||
storage::{StorageContentCategory, StorageServiceTrait},
|
||||
@ -101,12 +104,12 @@ pub struct MikanSubscriberSubscriptionRssUrlMeta {
|
||||
|
||||
impl MikanSubscriberSubscriptionRssUrlMeta {
|
||||
pub fn from_rss_url(url: &Url) -> Option<Self> {
|
||||
if url.path() == "/RSS/MyBangumi" {
|
||||
url.query_pairs().find(|(k, _)| k == "token").map(|(_, v)| {
|
||||
MikanSubscriberSubscriptionRssUrlMeta {
|
||||
if url.path() == MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH {
|
||||
url.query_pairs()
|
||||
.find(|(k, _)| k == MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY)
|
||||
.map(|(_, v)| MikanSubscriberSubscriptionRssUrlMeta {
|
||||
mikan_subscription_token: v.to_string(),
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -122,9 +125,11 @@ pub fn build_mikan_subscriber_subscription_rss_url(
|
||||
mikan_subscription_token: &str,
|
||||
) -> Url {
|
||||
let mut url = mikan_base_url;
|
||||
url.set_path("/RSS/MyBangumi");
|
||||
url.query_pairs_mut()
|
||||
.append_pair("token", mikan_subscription_token);
|
||||
url.set_path(MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH);
|
||||
url.query_pairs_mut().append_pair(
|
||||
MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY,
|
||||
mikan_subscription_token,
|
||||
);
|
||||
url
|
||||
}
|
||||
|
||||
@ -224,8 +229,10 @@ pub struct MikanBangumiIndexHash {
|
||||
|
||||
impl MikanBangumiIndexHash {
|
||||
pub fn from_homepage_url(url: &Url) -> Option<Self> {
|
||||
if url.path().starts_with("/Home/Bangumi/") {
|
||||
let mikan_bangumi_id = url.path().replace("/Home/Bangumi/", "");
|
||||
if url.path().starts_with(MIKAN_BANGUMI_HOMEPAGE_PATH) {
|
||||
let mikan_bangumi_id = url
|
||||
.path()
|
||||
.replace(&format!("{MIKAN_BANGUMI_HOMEPAGE_PATH}/"), "");
|
||||
|
||||
Some(Self { mikan_bangumi_id })
|
||||
} else {
|
||||
@ -244,12 +251,12 @@ pub fn build_mikan_bangumi_subscription_rss_url(
|
||||
mikan_fansub_id: Option<&str>,
|
||||
) -> Url {
|
||||
let mut url = mikan_base_url;
|
||||
url.set_path("/RSS/Bangumi");
|
||||
url.set_path(MIKAN_BANGUMI_RSS_PATH);
|
||||
url.query_pairs_mut()
|
||||
.append_pair("bangumiId", mikan_bangumi_id);
|
||||
.append_pair(MIKAN_BANGUMI_ID_QUERY_KEY, mikan_bangumi_id);
|
||||
if let Some(mikan_fansub_id) = mikan_fansub_id {
|
||||
url.query_pairs_mut()
|
||||
.append_pair("subgroupid", mikan_fansub_id);
|
||||
.append_pair(MIKAN_FANSUB_ID_QUERY_KEY, mikan_fansub_id);
|
||||
};
|
||||
url
|
||||
}
|
||||
@ -262,8 +269,10 @@ pub struct MikanBangumiHash {
|
||||
|
||||
impl MikanBangumiHash {
|
||||
pub fn from_homepage_url(url: &Url) -> Option<Self> {
|
||||
if url.path().starts_with("/Home/Bangumi/") {
|
||||
let mikan_bangumi_id = url.path().replace("/Home/Bangumi/", "");
|
||||
if url.path().starts_with(MIKAN_BANGUMI_HOMEPAGE_PATH) {
|
||||
let mikan_bangumi_id = url
|
||||
.path()
|
||||
.replace(&format!("{MIKAN_BANGUMI_HOMEPAGE_PATH}/"), "");
|
||||
|
||||
let url_fragment = url.fragment()?;
|
||||
|
||||
@ -277,13 +286,13 @@ impl MikanBangumiHash {
|
||||
}
|
||||
|
||||
pub fn from_rss_url(url: &Url) -> Option<Self> {
|
||||
if url.path() == "/RSS/Bangumi" {
|
||||
if url.path() == MIKAN_BANGUMI_RSS_PATH {
|
||||
if let (Some(mikan_fansub_id), Some(mikan_bangumi_id)) = (
|
||||
url.query_pairs()
|
||||
.find(|(k, _)| k == "subgroupid")
|
||||
.find(|(k, _)| k == MIKAN_FANSUB_ID_QUERY_KEY)
|
||||
.map(|(_, v)| v.to_string()),
|
||||
url.query_pairs()
|
||||
.find(|(k, _)| k == "bangumiId")
|
||||
.find(|(k, _)| k == MIKAN_BANGUMI_ID_QUERY_KEY)
|
||||
.map(|(_, v)| v.to_string()),
|
||||
) {
|
||||
Some(Self {
|
||||
@ -317,7 +326,7 @@ impl MikanBangumiHash {
|
||||
|
||||
pub fn build_mikan_episode_homepage_url(mikan_base_url: Url, mikan_episode_id: &str) -> Url {
|
||||
let mut url = mikan_base_url;
|
||||
url.set_path(&format!("/Home/Episode/{mikan_episode_id}"));
|
||||
url.set_path(&format!("{MIKAN_EPISODE_HOMEPAGE_PATH}/{mikan_episode_id}"));
|
||||
url
|
||||
}
|
||||
|
||||
@ -328,8 +337,10 @@ pub struct MikanEpisodeHash {
|
||||
|
||||
impl MikanEpisodeHash {
|
||||
pub fn from_homepage_url(url: &Url) -> Option<Self> {
|
||||
if url.path().starts_with("/Home/Episode/") {
|
||||
let mikan_episode_id = url.path().replace("/Home/Episode/", "");
|
||||
if url.path().starts_with(MIKAN_EPISODE_HOMEPAGE_PATH) {
|
||||
let mikan_episode_id = url
|
||||
.path()
|
||||
.replace(&format!("{MIKAN_EPISODE_HOMEPAGE_PATH}/"), "");
|
||||
Some(Self { mikan_episode_id })
|
||||
} else {
|
||||
None
|
||||
@ -416,7 +427,7 @@ pub fn build_mikan_bangumi_homepage_url(
|
||||
mikan_fansub_id: Option<&str>,
|
||||
) -> Url {
|
||||
let mut url = mikan_base_url;
|
||||
url.set_path(&format!("/Home/Bangumi/{mikan_bangumi_id}"));
|
||||
url.set_path(&format!("{MIKAN_BANGUMI_HOMEPAGE_PATH}/{mikan_bangumi_id}"));
|
||||
url.set_fragment(mikan_fansub_id);
|
||||
url
|
||||
}
|
||||
@ -715,7 +726,9 @@ pub async fn scrape_mikan_poster_meta_from_image_url(
|
||||
StorageContentCategory::Image,
|
||||
subscriber_id,
|
||||
Some(MIKAN_POSTER_BUCKET_KEY),
|
||||
&origin_poster_src_url.path().replace("/images/Bangumi/", ""),
|
||||
&origin_poster_src_url
|
||||
.path()
|
||||
.replace(&format!("{MIKAN_BANGUMI_POSTER_PATH}/"), ""),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@ -734,7 +747,9 @@ pub async fn scrape_mikan_poster_meta_from_image_url(
|
||||
StorageContentCategory::Image,
|
||||
subscriber_id,
|
||||
Some(MIKAN_POSTER_BUCKET_KEY),
|
||||
&origin_poster_src_url.path().replace("/images/Bangumi/", ""),
|
||||
&origin_poster_src_url
|
||||
.path()
|
||||
.replace(&format!("{MIKAN_BANGUMI_POSTER_PATH}/"), ""),
|
||||
poster_data,
|
||||
)
|
||||
.await?;
|
||||
@ -884,7 +899,7 @@ pub fn scrape_mikan_bangumi_meta_stream_from_season_flow_url(
|
||||
) -> impl Stream<Item = RecorderResult<MikanBangumiMeta>> {
|
||||
try_stream! {
|
||||
let mikan_base_url = ctx.mikan().base_url().clone();
|
||||
let mikan_client = ctx.mikan().fork_with_credential(ctx.clone(), credential_id).await?;
|
||||
let mikan_client = ctx.mikan().fork_with_credential_id(ctx.clone(), credential_id).await?;
|
||||
|
||||
let content = fetch_html(&mikan_client, mikan_season_flow_url.clone()).await?;
|
||||
|
||||
@ -971,7 +986,8 @@ mod test {
|
||||
crypto::build_testing_crypto_service,
|
||||
database::build_testing_database_service,
|
||||
mikan::{
|
||||
MikanMockServer, build_testing_mikan_client, build_testing_mikan_credential_form,
|
||||
MikanMockServer, build_testing_mikan_client, build_testing_mikan_credential,
|
||||
build_testing_mikan_credential_form,
|
||||
},
|
||||
storage::build_testing_storage_service,
|
||||
tracing::try_init_testing_tracing,
|
||||
@ -986,22 +1002,19 @@ mod test {
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_scrape_mikan_poster_data_from_image_url(before_each: ()) -> RecorderResult<()> {
|
||||
let mut mikan_server = mockito::Server::new_async().await;
|
||||
let mikan_base_url = Url::parse(&mikan_server.url())?;
|
||||
let mut mikan_server = MikanMockServer::new().await?;
|
||||
|
||||
let resources_mock = mikan_server.mock_resources_with_doppel();
|
||||
|
||||
let mikan_base_url = mikan_server.base_url().clone();
|
||||
let mikan_client = build_testing_mikan_client(mikan_base_url.clone()).await?;
|
||||
|
||||
let bangumi_poster_url = mikan_base_url.join("/images/Bangumi/202309/5ce9fed1.jpg")?;
|
||||
|
||||
let bangumi_poster_mock = mikan_server
|
||||
.mock("GET", bangumi_poster_url.path())
|
||||
.with_body_from_file("tests/resources/mikan/Bangumi-202309-5ce9fed1.jpg")
|
||||
.create_async()
|
||||
.await;
|
||||
|
||||
let bgm_poster_data =
|
||||
scrape_mikan_poster_data_from_image_url(&mikan_client, bangumi_poster_url).await?;
|
||||
|
||||
bangumi_poster_mock.expect(1);
|
||||
resources_mock.shared_resource_mock.expect(1);
|
||||
let image = Image::read(bgm_poster_data.to_vec(), Default::default());
|
||||
assert!(
|
||||
image.is_ok_and(|img| img
|
||||
@ -1017,20 +1030,19 @@ mod test {
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_scrape_mikan_poster_meta_from_image_url(before_each: ()) -> RecorderResult<()> {
|
||||
let mut mikan_server = mockito::Server::new_async().await;
|
||||
let mikan_base_url = Url::parse(&mikan_server.url())?;
|
||||
let mut mikan_server = MikanMockServer::new().await?;
|
||||
|
||||
let mikan_base_url = mikan_server.base_url().clone();
|
||||
|
||||
let resources_mock = mikan_server.mock_resources_with_doppel();
|
||||
|
||||
let mikan_client = build_testing_mikan_client(mikan_base_url.clone()).await?;
|
||||
|
||||
let storage_service = build_testing_storage_service().await?;
|
||||
let storage_operator = storage_service.get_operator()?;
|
||||
|
||||
let bangumi_poster_url = mikan_base_url.join("/images/Bangumi/202309/5ce9fed1.jpg")?;
|
||||
|
||||
let bangumi_poster_mock = mikan_server
|
||||
.mock("GET", bangumi_poster_url.path())
|
||||
.with_body_from_file("tests/resources/mikan/Bangumi-202309-5ce9fed1.jpg")
|
||||
.create_async()
|
||||
.await;
|
||||
|
||||
let bgm_poster = scrape_mikan_poster_meta_from_image_url(
|
||||
&mikan_client,
|
||||
&storage_service,
|
||||
@ -1039,7 +1051,7 @@ mod test {
|
||||
)
|
||||
.await?;
|
||||
|
||||
bangumi_poster_mock.expect(1);
|
||||
resources_mock.shared_resource_mock.expect(1);
|
||||
|
||||
let storage_fullname = storage_service.get_fullname(
|
||||
StorageContentCategory::Image,
|
||||
@ -1051,7 +1063,8 @@ mod test {
|
||||
|
||||
assert!(storage_operator.exists(storage_fullename_str).await?);
|
||||
|
||||
let expected_data = fs::read("tests/resources/mikan/Bangumi-202309-5ce9fed1.jpg")?;
|
||||
let expected_data =
|
||||
fs::read("tests/resources/mikan/doppel/images/Bangumi/202309/5ce9fed1.jpg")?;
|
||||
let found_data = storage_operator.read(storage_fullename_str).await?.to_vec();
|
||||
assert_eq!(expected_data, found_data);
|
||||
|
||||
@ -1100,7 +1113,7 @@ mod test {
|
||||
before_each: (),
|
||||
) -> RecorderResult<()> {
|
||||
let fragment_str =
|
||||
fs::read_to_string("tests/resources/mikan/BangumiCoverFlow-2025-spring-noauth.html")?;
|
||||
fs::read_to_string("tests/resources/mikan/BangumiCoverFlow-noauth.html")?;
|
||||
|
||||
let bangumi_index_meta_list =
|
||||
extract_mikan_bangumi_index_meta_list_from_season_flow_fragment(
|
||||
@ -1114,12 +1127,27 @@ mod test {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test]
|
||||
fn test_extract_mikan_bangumi_meta_from_expand_subscribed_fragment(
|
||||
#[tokio::test]
|
||||
async fn test_extract_mikan_bangumi_meta_from_expand_subscribed_fragment(
|
||||
before_each: (),
|
||||
) -> RecorderResult<()> {
|
||||
let mut mikan_server = MikanMockServer::new().await?;
|
||||
|
||||
let login_mock = mikan_server.mock_get_login_page();
|
||||
let resources_mock = mikan_server.mock_resources_with_doppel();
|
||||
|
||||
let mikan_base_url = mikan_server.base_url().clone();
|
||||
|
||||
let mikan_client = build_testing_mikan_client(mikan_base_url.clone())
|
||||
.await?
|
||||
.fork_with_credential(build_testing_mikan_credential())
|
||||
.await?;
|
||||
|
||||
mikan_client.login().await?;
|
||||
|
||||
let origin_poster_src =
|
||||
Url::parse("https://mikanani.me/images/Bangumi/202504/076c1094.jpg")?;
|
||||
|
||||
let bangumi_index_meta = MikanBangumiIndexMeta {
|
||||
homepage: Url::parse("https://mikanani.me/Home/Bangumi/3599")?,
|
||||
origin_poster_src: Some(origin_poster_src.clone()),
|
||||
@ -1127,10 +1155,17 @@ mod test {
|
||||
mikan_bangumi_id: "3599".to_string(),
|
||||
};
|
||||
|
||||
let fragment_str = fs::read_to_string("tests/resources/mikan/ExpandBangumi-3599.html")?;
|
||||
let fragment_str = fetch_html(
|
||||
&mikan_client,
|
||||
build_mikan_bangumi_expand_subscribed_url(
|
||||
mikan_base_url.clone(),
|
||||
&bangumi_index_meta.mikan_bangumi_id,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let bangumi = extract_mikan_bangumi_meta_from_expand_subscribed_fragment(
|
||||
&Html::parse_document(&fragment_str),
|
||||
&Html::parse_fragment(&fragment_str),
|
||||
bangumi_index_meta.clone(),
|
||||
Url::parse("https://mikanani.me/")?,
|
||||
)
|
||||
@ -1175,7 +1210,7 @@ mod test {
|
||||
fs::read_to_string("tests/resources/mikan/ExpandBangumi-3599-noauth.html")?;
|
||||
|
||||
let bangumi = extract_mikan_bangumi_meta_from_expand_subscribed_fragment(
|
||||
&Html::parse_document(&fragment_str),
|
||||
&Html::parse_fragment(&fragment_str),
|
||||
bangumi_index_meta.clone(),
|
||||
Url::parse("https://mikanani.me/")?,
|
||||
);
|
||||
|
@ -1,14 +1,22 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{self, Path},
|
||||
};
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use fetch::{FetchError, HttpClientConfig, IntoUrl, get_random_ua};
|
||||
use percent_encoding::{AsciiSet, CONTROLS, percent_decode, utf8_percent_encode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
crypto::UserPassCredential,
|
||||
errors::RecorderResult,
|
||||
extract::mikan::{
|
||||
MIKAN_ACCOUNT_MANAGE_PAGE_PATH, MIKAN_LOGIN_PAGE_PATH, MikanClient, MikanConfig,
|
||||
MikanCredentialForm,
|
||||
MIKAN_ACCOUNT_MANAGE_PAGE_PATH, MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH,
|
||||
MIKAN_BANGUMI_HOMEPAGE_PATH, MIKAN_BANGUMI_POSTER_PATH, MIKAN_BANGUMI_RSS_PATH,
|
||||
MIKAN_EPISODE_HOMEPAGE_PATH, MIKAN_EPISODE_TORRENT_PATH, MIKAN_LOGIN_PAGE_PATH,
|
||||
MIKAN_SEASON_FLOW_PAGE_PATH, MikanClient, MikanConfig, MikanCredentialForm,
|
||||
},
|
||||
};
|
||||
|
||||
@ -17,6 +25,20 @@ const TESTING_MIKAN_PASSWORD: &str = "test_password";
|
||||
const TESTING_MIKAN_ANTIFORGERY: &str = "test_antiforgery";
|
||||
const TESTING_MIKAN_IDENTITY: &str = "test_identity";
|
||||
|
||||
const FILE_UNSAFE: &AsciiSet = &CONTROLS
|
||||
.add(b'<')
|
||||
.add(b'>')
|
||||
.add(b':')
|
||||
.add(b'"')
|
||||
.add(b'|')
|
||||
.add(b'?')
|
||||
.add(b'*')
|
||||
.add(b'\\')
|
||||
.add(b'/')
|
||||
.add(b'&')
|
||||
.add(b'=')
|
||||
.add(b'#');
|
||||
|
||||
pub async fn build_testing_mikan_client(
|
||||
base_mikan_url: impl IntoUrl,
|
||||
) -> RecorderResult<MikanClient> {
|
||||
@ -38,6 +60,120 @@ pub fn build_testing_mikan_credential_form() -> MikanCredentialForm {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_testing_mikan_credential() -> UserPassCredential {
|
||||
UserPassCredential {
|
||||
username: String::from(TESTING_MIKAN_USERNAME),
|
||||
password: String::from(TESTING_MIKAN_PASSWORD),
|
||||
user_agent: None,
|
||||
cookies: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MikanDoppelMeta {
|
||||
pub status: u16,
|
||||
}
|
||||
|
||||
pub struct MikanDoppelPath {
|
||||
path: path::PathBuf,
|
||||
}
|
||||
|
||||
impl MikanDoppelPath {
|
||||
pub fn new(source: impl Into<Self>) -> Self {
|
||||
source.into()
|
||||
}
|
||||
|
||||
pub fn exists_any(&self) -> bool {
|
||||
self.exists() || self.exists_meta()
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
self.path().exists()
|
||||
}
|
||||
|
||||
pub fn exists_meta(&self) -> bool {
|
||||
self.meta_path().exists()
|
||||
}
|
||||
|
||||
pub fn write(&self, content: impl AsRef<[u8]>) -> std::io::Result<()> {
|
||||
if let Some(parent) = self.as_ref().parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
std::fs::write(self.as_ref(), content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_meta(&self, meta: MikanDoppelMeta) -> std::io::Result<()> {
|
||||
self.write(serde_json::to_string(&meta)?)
|
||||
}
|
||||
|
||||
pub fn read(&self) -> std::io::Result<Vec<u8>> {
|
||||
let content = std::fs::read(self.as_ref())?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
pub fn read_meta(&self) -> std::io::Result<MikanDoppelMeta> {
|
||||
let content = std::fs::read(self.meta_path())?;
|
||||
Ok(serde_json::from_slice(&content)?)
|
||||
}
|
||||
|
||||
pub fn encode_path_component(component: &str) -> String {
|
||||
utf8_percent_encode(component, FILE_UNSAFE).to_string()
|
||||
}
|
||||
|
||||
pub fn decode_path_component(component: &str) -> Result<String, std::str::Utf8Error> {
|
||||
Ok(percent_decode(component.as_bytes())
|
||||
.decode_utf8()?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn meta_path(&self) -> path::PathBuf {
|
||||
let extension = if let Some(ext) = self.path().extension() {
|
||||
format!("{}.meta.json", ext.to_string_lossy())
|
||||
} else {
|
||||
String::from("meta.json")
|
||||
};
|
||||
self.path.to_path_buf().with_extension(extension)
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &path::Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<path::Path> for MikanDoppelPath {
|
||||
fn as_ref(&self) -> &path::Path {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for MikanDoppelPath {
|
||||
fn from(value: Url) -> Self {
|
||||
let base_path =
|
||||
Path::new("tests/resources/mikan/doppel").join(value.path().trim_matches('/'));
|
||||
let dirname = base_path.parent();
|
||||
let stem = base_path.file_stem();
|
||||
debug_assert!(dirname.is_some() && stem.is_some());
|
||||
let extension = if let Some(ext) = base_path.extension() {
|
||||
ext.to_string_lossy().to_string()
|
||||
} else {
|
||||
String::from("html")
|
||||
};
|
||||
let mut filename = stem.unwrap().to_string_lossy().to_string();
|
||||
if let Some(query) = value.query() {
|
||||
filename.push_str(&format!("-{}", Self::encode_path_component(query)));
|
||||
}
|
||||
if let Some(fragment) = value.fragment() {
|
||||
filename.push_str(&format!("-{}", Self::encode_path_component(fragment)));
|
||||
}
|
||||
filename.push_str(&format!(".{extension}"));
|
||||
|
||||
Self {
|
||||
path: dirname.unwrap().join(filename),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MikanMockServerLoginMock {
|
||||
pub login_get_mock: mockito::Mock,
|
||||
pub login_post_success_mock: mockito::Mock,
|
||||
@ -46,6 +182,14 @@ pub struct MikanMockServerLoginMock {
|
||||
pub account_get_failed_mock: mockito::Mock,
|
||||
}
|
||||
|
||||
pub struct MikanMockServerResourcesMock {
|
||||
pub shared_resource_mock: mockito::Mock,
|
||||
pub shared_resource_not_found_mock: mockito::Mock,
|
||||
pub user_resource_mock: mockito::Mock,
|
||||
pub expand_bangumi_noauth_mock: mockito::Mock,
|
||||
pub season_flow_noauth_mock: mockito::Mock,
|
||||
}
|
||||
|
||||
pub struct MikanMockServer {
|
||||
pub server: mockito::ServerGuard,
|
||||
base_url: Url,
|
||||
@ -80,7 +224,7 @@ impl MikanMockServer {
|
||||
.server
|
||||
.mock("GET", MIKAN_LOGIN_PAGE_PATH)
|
||||
.match_query(mockito::Matcher::Any)
|
||||
.with_status(201)
|
||||
.with_status(200)
|
||||
.with_header("Content-Type", "text/html; charset=utf-8")
|
||||
.with_header(
|
||||
"Set-Cookie",
|
||||
@ -89,6 +233,7 @@ impl MikanMockServer {
|
||||
SameSite=Strict; Path=/"
|
||||
),
|
||||
)
|
||||
.with_body_from_file("tests/resources/mikan/LoginPage.html")
|
||||
.create();
|
||||
|
||||
let test_identity_expires = (Utc::now() + Duration::days(30)).to_rfc2822();
|
||||
@ -170,4 +315,138 @@ impl MikanMockServer {
|
||||
account_get_failed_mock,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_resources_with_doppel(&mut self) -> MikanMockServerResourcesMock {
|
||||
let shared_resource_mock = self
|
||||
.server
|
||||
.mock("GET", mockito::Matcher::Any)
|
||||
.match_request({
|
||||
let mikan_base_url = self.base_url().clone();
|
||||
move |request| {
|
||||
let path = request.path();
|
||||
if path.starts_with(MIKAN_BANGUMI_RSS_PATH)
|
||||
|| path.starts_with(MIKAN_BANGUMI_HOMEPAGE_PATH)
|
||||
|| path.starts_with(MIKAN_EPISODE_HOMEPAGE_PATH)
|
||||
|| path.starts_with(MIKAN_BANGUMI_POSTER_PATH)
|
||||
|| path.starts_with(MIKAN_EPISODE_TORRENT_PATH)
|
||||
{
|
||||
if let Ok(url) = mikan_base_url.join(request.path_and_query()) {
|
||||
let doppel_path = MikanDoppelPath::from(url);
|
||||
doppel_path.exists()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
.with_status(200)
|
||||
.with_body_from_request({
|
||||
let mikan_base_url = self.base_url().clone();
|
||||
move |req| {
|
||||
let path_and_query = req.path_and_query();
|
||||
let url = mikan_base_url.join(path_and_query).unwrap();
|
||||
let doppel_path = MikanDoppelPath::from(url);
|
||||
doppel_path.read().unwrap()
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
let shared_resource_not_found_mock = self
|
||||
.server
|
||||
.mock("GET", mockito::Matcher::Any)
|
||||
.match_request({
|
||||
let mikan_base_url = self.base_url().clone();
|
||||
move |request| {
|
||||
let path = request.path();
|
||||
if path.starts_with(MIKAN_BANGUMI_RSS_PATH)
|
||||
|| path.starts_with(MIKAN_BANGUMI_HOMEPAGE_PATH)
|
||||
|| path.starts_with(MIKAN_EPISODE_HOMEPAGE_PATH)
|
||||
|| path.starts_with(MIKAN_BANGUMI_POSTER_PATH)
|
||||
|| path.starts_with(MIKAN_EPISODE_TORRENT_PATH)
|
||||
{
|
||||
if let Ok(url) = mikan_base_url.join(request.path_and_query()) {
|
||||
let doppel_path = MikanDoppelPath::from(url);
|
||||
doppel_path.exists_meta()
|
||||
&& doppel_path.read_meta().unwrap().status == 404
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
.with_status(404)
|
||||
.create();
|
||||
|
||||
let user_resource_mock = self
|
||||
.server
|
||||
.mock("GET", mockito::Matcher::Any)
|
||||
.match_request({
|
||||
let mikan_base_url = self.base_url().clone();
|
||||
move |req| {
|
||||
if !Self::get_has_auth_matcher()(req) {
|
||||
return false;
|
||||
}
|
||||
let path = req.path();
|
||||
if path.starts_with(MIKAN_SEASON_FLOW_PAGE_PATH)
|
||||
|| path.starts_with(MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH)
|
||||
{
|
||||
if let Ok(url) = mikan_base_url.join(req.path_and_query()) {
|
||||
let doppel_path = MikanDoppelPath::from(url);
|
||||
doppel_path.exists()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
.with_status(200)
|
||||
.with_body_from_request({
|
||||
let mikan_base_url = self.base_url().clone();
|
||||
move |req| {
|
||||
let path_and_query = req.path_and_query();
|
||||
let url = mikan_base_url.join(path_and_query).unwrap();
|
||||
let doppel_path = MikanDoppelPath::from(url);
|
||||
doppel_path.read().unwrap()
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
let expand_bangumi_noauth_mock = self
|
||||
.server
|
||||
.mock("GET", mockito::Matcher::Any)
|
||||
.match_request(move |req| {
|
||||
!Self::get_has_auth_matcher()(req)
|
||||
&& req
|
||||
.path()
|
||||
.starts_with(MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH)
|
||||
})
|
||||
.with_status(200)
|
||||
.with_body_from_file("tests/resources/mikan/ExpandBangumi-noauth.html")
|
||||
.create();
|
||||
|
||||
let season_flow_noauth_mock = self
|
||||
.server
|
||||
.mock("GET", mockito::Matcher::Any)
|
||||
.match_request(move |req| {
|
||||
!Self::get_has_auth_matcher()(req)
|
||||
&& req.path().starts_with(MIKAN_SEASON_FLOW_PAGE_PATH)
|
||||
})
|
||||
.with_status(200)
|
||||
.with_body_from_file("tests/resources/mikan/BangumiCoverFlow-noauth.html")
|
||||
.create();
|
||||
|
||||
MikanMockServerResourcesMock {
|
||||
shared_resource_mock,
|
||||
shared_resource_not_found_mock,
|
||||
user_resource_mock,
|
||||
expand_bangumi_noauth_mock,
|
||||
season_flow_noauth_mock,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load Diff
@ -119,8 +119,7 @@
|
||||
<a id="login-popover-create-account">还没有账号?赶紧来注册一个吧~</a>
|
||||
</div>
|
||||
</div>
|
||||
<input name="__RequestVerificationToken" type="hidden"
|
||||
value="CfDJ8MyNMqFNaC9JmJW13PvY-91NCe3U_rFij5nf6ni2NtxaxhQNSuonDbK7198YMfFdErEyhk-CHiByyDQaq371N3GUx0c8Xma0F0F2J2UQaszZgfjT5vxTV4O4viF6YoPDWMO2yLbeN7ok83_uz1DD-nU" />
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
@ -227,8 +226,7 @@
|
||||
<div id="logmod-forget-password" class="pull-right"><a
|
||||
href="/Account/ForgotPassword" class="forget-password">忘记密码</a></div>
|
||||
</div>
|
||||
<input name="__RequestVerificationToken" type="hidden"
|
||||
value="CfDJ8MyNMqFNaC9JmJW13PvY-91NCe3U_rFij5nf6ni2NtxaxhQNSuonDbK7198YMfFdErEyhk-CHiByyDQaq371N3GUx0c8Xma0F0F2J2UQaszZgfjT5vxTV4O4viF6YoPDWMO2yLbeN7ok83_uz1DD-nU" />
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -260,8 +258,7 @@
|
||||
<div id="logmod-forget-password" class="pull-right"><a
|
||||
href="/Account/ForgotPassword" class="forget-password">忘记密码</a></div>
|
||||
</div>
|
||||
<input name="__RequestVerificationToken" type="hidden"
|
||||
value="CfDJ8MyNMqFNaC9JmJW13PvY-91NCe3U_rFij5nf6ni2NtxaxhQNSuonDbK7198YMfFdErEyhk-CHiByyDQaq371N3GUx0c8Xma0F0F2J2UQaszZgfjT5vxTV4O4viF6YoPDWMO2yLbeN7ok83_uz1DD-nU" />
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -376,8 +373,7 @@
|
||||
<input type="password" class="form-control" aria-label="..." placeholder="密码" name="Password">
|
||||
</div>
|
||||
<button class="form-control" type="submit">登录</button>
|
||||
<input name="__RequestVerificationToken" type="hidden"
|
||||
value="CfDJ8MyNMqFNaC9JmJW13PvY-91NCe3U_rFij5nf6ni2NtxaxhQNSuonDbK7198YMfFdErEyhk-CHiByyDQaq371N3GUx0c8Xma0F0F2J2UQaszZgfjT5vxTV4O4viF6YoPDWMO2yLbeN7ok83_uz1DD-nU" />
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
<div class="m-goto-registry">
|
||||
<a href="/Account/Register" class="w-other-c" style="color:#3bc0c3">立即注册</a>
|
||||
@ -487,8 +483,7 @@
|
||||
name="Password">
|
||||
</div>
|
||||
<button class="form-control" type="submit">登录</button>
|
||||
<input name="__RequestVerificationToken" type="hidden"
|
||||
value="CfDJ8MyNMqFNaC9JmJW13PvY-91NCe3U_rFij5nf6ni2NtxaxhQNSuonDbK7198YMfFdErEyhk-CHiByyDQaq371N3GUx0c8Xma0F0F2J2UQaszZgfjT5vxTV4O4viF6YoPDWMO2yLbeN7ok83_uz1DD-nU" />
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
<div class="m-goto-registry">
|
||||
<a href="/Account/Register" class="w-other-c" style="color:#3bc0c3">立即注册</a>
|
||||
|
553
apps/recorder/tests/resources/mikan/LoginPage.html
Normal file
553
apps/recorder/tests/resources/mikan/LoginPage.html
Normal file
@ -0,0 +1,553 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="keywords" content="新番,动漫,动漫下載,新番下载,animation,bangumi,动画,蜜柑计划,Mikan Project" />
|
||||
<meta name="description" content="蜜柑计划:新一代的动漫下载站" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- 若用户有Google Chrome Frame,那么ie浏览时让IE使用chrome内核 -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<!-- 若是双核浏览器,默认webkit渲染(chrome) -->
|
||||
<meta name="renderer" content="webkit">
|
||||
<title>Mikan Project - 用户登录</title>
|
||||
|
||||
<!-- here put import css lib -->
|
||||
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="/lib/bootstrap/dist/css/bootstrap.min.css?v=7s5uDGW3AHqw6xtJmNNtr-OBRJUlgkNJEo78P4b0yRw" />
|
||||
<link rel="stylesheet"
|
||||
href="/lib/font-awesome/css/font-awesome.min.css?v=3dkvEK0WLHRJ7_Csr0BZjAWxERc5WH7bdeUya2aXxdU" />
|
||||
<link rel="stylesheet" href="/css/thirdparty.min.css?v=c2SZy6n-55iljz60XCAALXejEZvjc43kgwamU5DAYUU" />
|
||||
<link rel="stylesheet" href="/css/animate.min.css?v=w_eXqGX0NdMPQ0LZNhdQ8B-DQMYAxelvLoIP39dzmus" />
|
||||
<link rel="stylesheet" href="/css/mikan.min.css?v=aupBMgBgKRB5chTb5fl8lvHpN3OqX67_gKg3lXZewRw" />
|
||||
|
||||
<script src="/lib/jquery/dist/jquery.min.js?v=BbhdlvQf_xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44"></script>
|
||||
<script src="/lib/bootstrap/dist/js/bootstrap.min.js?v=KXn5puMvxCw-dAYznun-drMdG1IFl3agK0p_pqT9KAo"></script>
|
||||
<script src="/js/thirdparty.min.js?v=NsK_w5fw7Nm4ZPm4eZDgsivasZNgT6ArhIjmj-bRnR0"></script>
|
||||
<script src="/js/darkreader.min.js?v=Lr_8XODLEDSPtT6LqaeLKzREs4jocJUzV8HvQPItIic"></script>
|
||||
<script src="/js/ScrollMagic.min.js?v=1xuIM3UJWEZX_wWN9zrA8W7CWukfsMaEqb759CeHo3U"></script>
|
||||
<script src="/js/jquery.ScrollMagic.min.js?v=SyygQh9gWWfvyS13QwI0SKGAQyHDachlaigiK4X59iw"></script>
|
||||
|
||||
|
||||
<link rel="icon" href="/images/favicon.ico?v=2" />
|
||||
<link rel="apple-touch-icon" href="\Images\apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="\Images\apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="\Images\apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="\Images\apple-touch-icon-144x144.png">
|
||||
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', 'UA-8911610-8', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="main">
|
||||
<div id="sk-header" class="hidden-xs hidden-sm">
|
||||
<div id="sk-top-nav" class="container">
|
||||
<a id="logo" href="/" style="width:205px;"><img id="mikan-pic" src="/images/mikan-pic.png" /><img
|
||||
src="/images/mikan-text.svg" style="height:30px;" /></a>
|
||||
<div id="nav-list">
|
||||
<ul class="list-inline nav-ul">
|
||||
<li class="">
|
||||
<div class="sk-col"><a href="/"><i class="fa fa-home fa-lg"></i>主页</a></div>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="sk-col"><a href="/Home/MyBangumi"><i class="fa fa-rss fa-lg"></i>订阅</a></div>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="sk-col"><a href="/Home/Classic"><i class="fa fa-slack fa-lg"></i>列表</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="search-form">
|
||||
<form method="get" action="/Home/Search">
|
||||
<div class="form-group has-feedback">
|
||||
<label for="search" class="sr-only">搜索</label>
|
||||
<input type="text" class="form-control input-sm" name="searchstr" id="header-search"
|
||||
placeholder="搜索">
|
||||
<span class="glyphicon glyphicon-search form-control-feedback"></span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<section id="login">
|
||||
<div id="user-login" class="pull-right">
|
||||
<a href="/Account/Register" class="text-right">注册</a>
|
||||
<a onclick="ToggleActive(this)" class="text-right" data-toggle="popover-x"
|
||||
data-target="#login-popover" data-placement="bottom bottom-right" rel="popover">登录</a>
|
||||
<form action="/Account/Login?ReturnUrl=%2FAccount%2FLogin" class="form-vertical" method="post">
|
||||
<div id="login-popover" class="popover popover-default">
|
||||
<div class="arrow"></div>
|
||||
<div id="login-popover-conent">
|
||||
<div id="login-popover-input">
|
||||
<div id="login-popover-div-username">
|
||||
<img src="/images/user-name_login_icon.png" />
|
||||
<input type="text" placeholder="用户名" id="login-popover-input-username"
|
||||
name="UserName" />
|
||||
</div>
|
||||
<div id="login-popover-div-password">
|
||||
<img src="/images/password_login_icon.png" style="margin-left:3px;" />
|
||||
<input type="password" placeholder="密码" id="login-popover-input-password"
|
||||
name="Password" />
|
||||
</div>
|
||||
</div>
|
||||
<button id="login-popover-submit" type="submit"
|
||||
class="btn">登 录</button>
|
||||
<div class="checkbox" id="login-popover-password">
|
||||
<label id="login-popover-remember-password"><input type="checkbox" value="true"
|
||||
name="RememberMe"><input type="hidden" value="false"
|
||||
name="RememberMe">记住密码</label>
|
||||
<div id="login-popover-forget-password" class="pull-right"><a
|
||||
href="/Account/ForgotPassword" class="forget-password">忘记密码</a></div>
|
||||
</div>
|
||||
|
||||
<a id="login-popover-create-account">还没有账号?赶紧来注册一个吧~</a>
|
||||
</div>
|
||||
</div>
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
var AdvancedSubscriptionEnabled = false;
|
||||
</script>
|
||||
</section>
|
||||
</div>
|
||||
<div class="ribbon">
|
||||
<span class="ribbon-color1"></span>
|
||||
<span class="ribbon-color2"></span>
|
||||
<span class="ribbon-color3"></span>
|
||||
<span class="ribbon-color4"></span>
|
||||
<span class="ribbon-color5"></span>
|
||||
<span class="ribbon-color6"></span>
|
||||
<span class="ribbon-color7"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-home-nav hidden-lg hidden-md" id="sk-mobile-header">
|
||||
<div class="m-home-tool-left clickable" data-toggle="modal" data-target="#modal-nav">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="m-home-tool-left"></div>
|
||||
<div style="text-align: center; height:100%;flex:1;">
|
||||
<a href="/" style="text-decoration:none">
|
||||
<img src="/images/mikan-pic.png" style="height: 3rem;margin-top: 0.5rem;">
|
||||
<img src="/images/mikan-text.png" style="height: 1.5rem;margin-top: 0.5rem;">
|
||||
</a>
|
||||
</div>
|
||||
<div class="m-home-tool-right clickable" data-toggle="modal" data-target="#modal-login">
|
||||
<i class="fa fa-user" aria-hidden="true" style="margin-right: 1rem;"></i>
|
||||
</div>
|
||||
<div class="m-home-tool-right clickable" onclick="ShowNavSearch()">
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-nav-search" style="width: 100%;">
|
||||
<div style="flex: 1;">
|
||||
<form method="get" action="/Home/Search">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="sizing-addon1" style="border: none;background-color: white;">
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" placeholder="搜索" name="searchstr"
|
||||
aria-describedby="sizing-addon1" style="border: none;font-size:16px;">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div style="width: 4rem;" onclick="HideNavSearch()">
|
||||
<span style="font-size: 1.25rem;">取消</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@media only screen and (min-device-width : 768px) {
|
||||
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div id="account-bg-wrapper" class="hidden-sm hidden-xs">
|
||||
<div class="logmod">
|
||||
<div class="logmod__wrapper">
|
||||
<div class="logmod__container">
|
||||
<ul class="logmod__tabs">
|
||||
<li data-tabtar="lgm-1"><a href="#">Mikan 账号注册</a></li>
|
||||
<li data-tabtar="lgm-2"><a href="#">登录</a></li>
|
||||
</ul>
|
||||
<div class="logmod__tab-wrapper">
|
||||
<div class="logmod__tab lgm-1">
|
||||
<div class="logmod__heading">
|
||||
<span class="logmod__heading-subtitle"></span>
|
||||
</div>
|
||||
<div class="logmod__form">
|
||||
<form action="/Account/Register?ReturnUrl=%2FAccount%2FLogout" class="simform"
|
||||
id="registerForm" method="post">
|
||||
<div class="logmod__inputs full ">
|
||||
<input type="text" class="logmod-input-control" placeholder="用户名*"
|
||||
name="UserName" />
|
||||
</div>
|
||||
<div class="logmod__inputs full">
|
||||
<input type="password" class="logmod-input-control" placeholder="设置密码*"
|
||||
name="Password" id="register-password" />
|
||||
</div>
|
||||
<div class="logmod__inputs full">
|
||||
<input type="password" class="logmod-input-control" placeholder="确认密码*"
|
||||
name="ConfirmPassword" />
|
||||
</div>
|
||||
<div class="logmod__inputs full">
|
||||
<input type="text" class="logmod-input-control" placeholder="设置邮箱*"
|
||||
name="Email" />
|
||||
</div>
|
||||
<div class="logmod__inputs full">
|
||||
<input type="text" class="logmod-input-control" placeholder="QQ" name="QQ" />
|
||||
</div>
|
||||
<button class="logmod-submit btn" type="submit"
|
||||
value="Register">注 册</button>
|
||||
<div class="checkbox" id="logmod-password">
|
||||
<div id="logmod-forget-password" class="pull-right"><a
|
||||
href="/Account/ForgotPassword" class="forget-password">忘记密码</a></div>
|
||||
</div>
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logmod__tab lgm-2">
|
||||
<div class="logmod__heading">
|
||||
<span class="logmod__heading-subtitle">Hi,欢迎回来!</span>
|
||||
</div>
|
||||
<div class="logmod__form">
|
||||
<form action="/Account/Login?ReturnUrl=%2FAccount%2FLogout" class="simform"
|
||||
id="loginForm" method="post">
|
||||
<div class="logmod__inputs full left-addon">
|
||||
<img src="/images/user-name_login_icon.png" class="logmod-icon" />
|
||||
<input type="text" class="logmod-input-control" placeholder="用户名"
|
||||
name="UserName" />
|
||||
</div>
|
||||
<div class="logmod__inputs full left-addon">
|
||||
<img src="/images/password_login_icon.png" class="logmod-icon password" />
|
||||
<input type="password" class="logmod-input-control" placeholder="密码"
|
||||
name="Password" />
|
||||
</div>
|
||||
<button class="logmod-submit btn" type="submit"
|
||||
value="Log in">登 录</button>
|
||||
<div class="checkbox" id="logmod-password">
|
||||
<label id="logmod-remember-password"><input type="checkbox" value="true"
|
||||
name="RememberMe"><input type="hidden" value="false"
|
||||
name="RememberMe">记住密码</label>
|
||||
<div id="logmod-forget-password" class="pull-right"><a
|
||||
href="/Account/ForgotPassword" class="forget-password">忘记密码</a></div>
|
||||
</div>
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$.validator.addMethod("username", function (value, element) {
|
||||
return this.optional(element) || /^[\u4e00-\u9fa5_a-zA-Z0-9_]{3,15}$/i.test(value);
|
||||
}, "用户名只能使用中英文,数字和下划线,长度请控制在3-15字节以内");
|
||||
|
||||
$.validator.addMethod("usernameTaken", function (value, element) {
|
||||
var valid = true;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/Account/VerifyUserName',
|
||||
data: JSON.stringify(value),
|
||||
async: false,
|
||||
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
||||
//alert("Request: " + XMLHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
|
||||
},
|
||||
success: function (data) {
|
||||
valid = data.nottaken;
|
||||
}
|
||||
});
|
||||
|
||||
return valid;
|
||||
}, "用户名已经被使用");
|
||||
|
||||
$("#registerForm").validate({
|
||||
rules: {
|
||||
UserName: {
|
||||
required: true,
|
||||
username: "用户名只能使用中英文,数字和下划线,长度请控制在3-15字节以内",
|
||||
usernameTaken: "用户名已经被使用"
|
||||
},
|
||||
Password: {
|
||||
required: true,
|
||||
minlength: 6
|
||||
},
|
||||
ConfirmPassword: {
|
||||
equalTo: "#register-password"
|
||||
},
|
||||
Email: {
|
||||
required: true,
|
||||
email: true
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
UserName: {
|
||||
required: "请输入用户名",
|
||||
username: "用户名只能使用中英文,数字和下划线,长度请控制在3-15字节以内",
|
||||
usernameTaken: "用户名已经被使用"
|
||||
},
|
||||
Password: {
|
||||
required: "请输入密码",
|
||||
minlength: "密码设置错误,密码长度必须大于6位"
|
||||
},
|
||||
ConfirmPassword: {
|
||||
equalTo: "两次输入的密码不一致,请再输入一次您之前输入的密码"
|
||||
},
|
||||
Email: {
|
||||
required: "请输入Email地址",
|
||||
email: "提供的Email地址无效,请检查并重试"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#loginForm").validate({
|
||||
rules: {
|
||||
UserName: {
|
||||
required: true,
|
||||
username: "用户名只能使用中英文,数字和下划线,长度请控制在3-15字节以内"
|
||||
},
|
||||
Password: {
|
||||
required: true,
|
||||
minlength: 6
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
UserName: {
|
||||
required: "请输入用户名",
|
||||
username: "用户名只能使用中英文,数字和下划线,长度请控制在3-15字节以内"
|
||||
},
|
||||
Password: {
|
||||
required: "请输入密码",
|
||||
minlength: "密码设置错误,密码长度必须大于6位"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div style="margin: auto;width:100%;height:85vh;" class="hidden-lg hidden-md">
|
||||
<div class="m-login">
|
||||
<div class="m-tool-title" style="padding-top: 7rem; color:#555;">
|
||||
登陆mikan账号
|
||||
</div>
|
||||
<div style="text-align: center;margin-top: 2rem;">
|
||||
<img src="/images/mikan-pic.png" style="width: 6rem;">
|
||||
</div>
|
||||
<form action="/Account/Login?ReturnUrl=%2FAccount%2FLogout" id="mobileLoginForm" method="post">
|
||||
<div id="mobileLoginInput">
|
||||
<input type="text" class="form-control" aria-label="..." placeholder="用户名" name="UserName">
|
||||
<input type="password" class="form-control" aria-label="..." placeholder="密码" name="Password">
|
||||
</div>
|
||||
<button class="form-control" type="submit">登录</button>
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
<div class="m-goto-registry">
|
||||
<a href="/Account/Register" class="w-other-c" style="color:#3bc0c3">立即注册</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#mobileLoginForm").validate({
|
||||
rules: {
|
||||
UserName: {
|
||||
required: true,
|
||||
},
|
||||
Password: {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
UserName: {
|
||||
required: "用户名或密码错误",
|
||||
},
|
||||
Password: {
|
||||
required: "用户名或密码错误",
|
||||
}
|
||||
},
|
||||
groups: {
|
||||
username: "UserName Password"
|
||||
},
|
||||
errorPlacement: function (error, element) {
|
||||
error.insertAfter("#mobileLoginInput");
|
||||
},
|
||||
errorClass: "m-login-error"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
LoginModalController.initialize(1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="modal modal-fullscreen fade" id="modal-nav" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
|
||||
aria-hidden="true" style="background-color:#3bc0c3;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body" style="margin: auto;width:100%;">
|
||||
<div class="m-tool">
|
||||
<span class="m-close clickable"><i class="fa fa-times" aria-hidden="true" data-toggle="modal"
|
||||
data-target="#modal-nav"></i></span>
|
||||
<div class="m-tool-toolbar">
|
||||
<img src="/images/mikan-pic.png" style="width: 3rem;">
|
||||
<img src="/images/mikan-text.png" style="width: 7rem;">
|
||||
</div>
|
||||
<div class="m-tool-list">
|
||||
<ul>
|
||||
<li><a href="/" class="link">主页</a></li>
|
||||
<li class="m-tool-search-change"><a href="/Home/MyBangumi" class="link">订阅</a></li>
|
||||
<li onclick="tool.clickSearch()" class="m-tool-search-change">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> 搜索站内
|
||||
</li>
|
||||
<li class="m-tool-search-input">
|
||||
<form method="get" action="/Home/Search">
|
||||
<div style="display: flex;height: 100%;">
|
||||
<input type="text" class="form-control" name="searchstr"
|
||||
style="font-size:16px;" />
|
||||
<span style="width: 5rem;" onclick="tool.resetSearch()">取消</span>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal modal-fullscreen fade" id="modal-login" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
|
||||
aria-hidden="true" style="background-color:#edf1f2;">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body" style="margin: auto;width:100%;height:85vh;">
|
||||
<div class="m-login">
|
||||
<span class="m-left clickable"><i class="fa fa-angle-left" aria-hidden="true"
|
||||
data-toggle="modal" data-target="#modal-login"></i></span>
|
||||
|
||||
<div class="m-tool-title">
|
||||
登陆mikan账号
|
||||
</div>
|
||||
<div style="text-align: center;margin-top: 2rem;">
|
||||
<img src="/images/mikan-pic.png" style="width: 6rem;">
|
||||
</div>
|
||||
<form action="/Account/Login?ReturnUrl=%2FAccount%2FLogin" method="post">
|
||||
<div>
|
||||
<input type="text" class="form-control" aria-label="..." placeholder="用户名"
|
||||
name="UserName">
|
||||
<input type="password" class="form-control" aria-label="..." placeholder="密码"
|
||||
name="Password">
|
||||
</div>
|
||||
<button class="form-control" type="submit">登录</button>
|
||||
<input name="__RequestVerificationToken" type="hidden" value="test_antiforgery" />
|
||||
</form>
|
||||
<div class="m-goto-registry">
|
||||
<a href="/Account/Register" class="w-other-c" style="color:#3bc0c3">立即注册</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer hidden-xs hidden-sm">
|
||||
<div id="sk-footer" class="container text-center">
|
||||
<div>Powered by Mikan Project <a href="/Home/Contact" target="_blank">联系我们</a></div>
|
||||
<div>Cooperate by PlaymateCat@Lisa</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
var tool = {};
|
||||
(function () {
|
||||
|
||||
var inputPEl = $('.m-tool-search-input');
|
||||
var inputEl = inputPEl.find('input');
|
||||
var changeEl = $('.m-tool-search-change');
|
||||
inputPEl.hide();
|
||||
tool.clickSearch = clickSearch;
|
||||
tool.resetSearch = resetSearch;
|
||||
|
||||
function clickSearch() {
|
||||
changeEl.hide();
|
||||
inputPEl.show();
|
||||
inputEl.focus();
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
changeEl.show();
|
||||
inputPEl.hide();
|
||||
inputEl.val('');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var pageUtil;
|
||||
|
||||
(function () {
|
||||
pageUtil = {
|
||||
isMobile: isMobile
|
||||
};
|
||||
|
||||
function isMobile() {
|
||||
var check = false;
|
||||
(function (a) {
|
||||
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
|
||||
})(navigator.userAgent || navigator.vendor || window.opera);
|
||||
return check;
|
||||
}
|
||||
})();
|
||||
|
||||
//detect if page is mobile
|
||||
if (pageUtil.isMobile()) {
|
||||
document.getElementsByTagName('html')[0].style['font-size'] = window.innerWidth / 32 + 'px';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<!-- here put your own javascript -->
|
||||
|
||||
|
||||
<script src="/js/mikan.min.js?v=7USd_hfRE7KH46vQBdF29boa3ENWKMVFRTyD9a8XEDg"></script>
|
||||
|
||||
|
||||
|
||||
</html>
|
586
apps/recorder/tests/resources/mikan/MyBangumi-2025-spring.rss
Normal file
586
apps/recorder/tests/resources/mikan/MyBangumi-2025-spring.rss
Normal file
@ -0,0 +1,586 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Mikan Project - 我的番组</title>
|
||||
<link>http://mikanani.me/RSS/MyBangumi?token=token</link>
|
||||
<description>Mikan Project - 我的番组</description>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 天才治疗师退队作为无照治疗师快乐过活 / Yami Healer - 09 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/f56e926c4d07f37d80a5206a1cea9b20344df513</link>
|
||||
<title>[LoliHouse] 天才治疗师退队作为无照治疗师快乐过活 / Yami Healer - 09 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 天才治疗师退队作为无照治疗师快乐过活 / Yami Healer - 09 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕][215.69 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/f56e926c4d07f37d80a5206a1cea9b20344df513</link>
|
||||
<contentLength>226167360</contentLength>
|
||||
<pubDate>2025-05-30T06:33:42.034</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="226167360"
|
||||
url="https://mikanani.me/Download/20250530/f56e926c4d07f37d80a5206a1cea9b20344df513.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu
|
||||
Made - 09 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/df5e27aaf5dd59b2ceb427e5eaeae97a37be2196</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu Made - 09 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu Made - 09
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕][425.51 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/df5e27aaf5dd59b2ceb427e5eaeae97a37be2196</link>
|
||||
<contentLength>446179584</contentLength>
|
||||
<pubDate>2025-05-29T21:40:37.835</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="446179584"
|
||||
url="https://mikanani.me/Download/20250529/df5e27aaf5dd59b2ceb427e5eaeae97a37be2196.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 测不准的阿波连同学 第二季 / Aharen-san wa Hakarenai
|
||||
S2 - 08 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/348a1c6e4c163b6c99eba34746e531d17090d00b</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 测不准的阿波连同学 第二季 / Aharen-san wa Hakarenai S2 - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 测不准的阿波连同学 第二季 / Aharen-san wa Hakarenai S2 - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕][289.1MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/348a1c6e4c163b6c99eba34746e531d17090d00b</link>
|
||||
<contentLength>303143328</contentLength>
|
||||
<pubDate>2025-05-29T20:51:00</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="303143328"
|
||||
url="https://mikanani.me/Download/20250529/348a1c6e4c163b6c99eba34746e531d17090d00b.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 杂旅 / 随兴旅-That's Journey- / Zatsu Tabi:
|
||||
That's Journey - 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/99424d24551987ac0fa9d76fd17ad0f9922ecd91</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 杂旅 / 随兴旅-That's Journey- / Zatsu Tabi: That's Journey - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 杂旅 / 随兴旅-That's Journey- / Zatsu Tabi: That's Journey
|
||||
- 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕][429.82 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/99424d24551987ac0fa9d76fd17ad0f9922ecd91</link>
|
||||
<contentLength>450698944</contentLength>
|
||||
<pubDate>2025-05-29T19:42:32.048</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="450698944"
|
||||
url="https://mikanani.me/Download/20250529/99424d24551987ac0fa9d76fd17ad0f9922ecd91.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 时光流逝,饭菜依旧美味 / Hibi wa Sugiredo Meshi
|
||||
Umashi - 07 [WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/64cb1e3ea9d5708bba818872daaaa7bec8072e2e</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 时光流逝,饭菜依旧美味 / Hibi wa Sugiredo Meshi Umashi - 07 [WebRip
|
||||
1080p HEVC-10bit AAC ASSx2][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 时光流逝,饭菜依旧美味 / Hibi wa Sugiredo Meshi Umashi - 07
|
||||
[WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕][705.65 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/64cb1e3ea9d5708bba818872daaaa7bec8072e2e</link>
|
||||
<contentLength>739927680</contentLength>
|
||||
<pubDate>2025-05-29T13:15:16.11</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="739927680"
|
||||
url="https://mikanani.me/Download/20250529/64cb1e3ea9d5708bba818872daaaa7bec8072e2e.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[MingY] 莉可丽丝:友谊是时间的窃贼 / Lycoris Recoil - Friends are thieves
|
||||
of time. [01-06][WebRip][1080p][简繁日内封]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/f16c568d27acd858c281faa2ad3725b46011ec88</link>
|
||||
<title>[MingY] 莉可丽丝:友谊是时间的窃贼 / Lycoris Recoil - Friends are thieves of time.
|
||||
[01-06][WebRip][1080p][简繁日内封]</title>
|
||||
<description>[MingY] 莉可丽丝:友谊是时间的窃贼 / Lycoris Recoil - Friends are thieves of time.
|
||||
[01-06][WebRip][1080p][简繁日内封][278.95 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/f16c568d27acd858c281faa2ad3725b46011ec88</link>
|
||||
<contentLength>292500288</contentLength>
|
||||
<pubDate>2025-05-28T20:14:00.623</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="292500288"
|
||||
url="https://mikanani.me/Download/20250528/f16c568d27acd858c281faa2ad3725b46011ec88.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[MingY] 莉可丽丝:友谊是时间的窃贼 / Lycoris Recoil - Friends are thieves
|
||||
of time. [01-06][WebRip][1080p][简日内嵌]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/ba6308b3394680f75c410fe998ad7134b25689ca</link>
|
||||
<title>[MingY] 莉可丽丝:友谊是时间的窃贼 / Lycoris Recoil - Friends are thieves of time.
|
||||
[01-06][WebRip][1080p][简日内嵌]</title>
|
||||
<description>[MingY] 莉可丽丝:友谊是时间的窃贼 / Lycoris Recoil - Friends are thieves of time.
|
||||
[01-06][WebRip][1080p][简日内嵌][304.13 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/ba6308b3394680f75c410fe998ad7134b25689ca</link>
|
||||
<contentLength>318903424</contentLength>
|
||||
<pubDate>2025-05-28T20:13:31.764</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="318903424"
|
||||
url="https://mikanani.me/Download/20250528/ba6308b3394680f75c410fe998ad7134b25689ca.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[SweetSub&LoliHouse] 机动战士高达 GQuuuuuuX / Mobile Suit Gundam
|
||||
GQuuuuuuX - 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/631132921dae5c1708deff1cf23d0c0f309836d1</link>
|
||||
<title>[SweetSub&LoliHouse] 机动战士高达 GQuuuuuuX / Mobile Suit Gundam GQuuuuuuX - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[SweetSub&LoliHouse] 机动战士高达 GQuuuuuuX / Mobile Suit Gundam GQuuuuuuX -
|
||||
08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕][707.15 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/631132921dae5c1708deff1cf23d0c0f309836d1</link>
|
||||
<contentLength>741500544</contentLength>
|
||||
<pubDate>2025-05-28T18:38:58.772</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="741500544"
|
||||
url="https://mikanani.me/Download/20250528/631132921dae5c1708deff1cf23d0c0f309836d1.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 紫云寺家的兄弟姊妹 / Shiunji-ke no Kodomotachi - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕](检索用:紫云寺家的孩子们)</guid>
|
||||
<link>https://mikanani.me/Home/Episode/a0abd76c58e60dfedfb8a099d9dcbc974d5f3423</link>
|
||||
<title>[LoliHouse] 紫云寺家的兄弟姊妹 / Shiunji-ke no Kodomotachi - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕](检索用:紫云寺家的孩子们)</title>
|
||||
<description>[LoliHouse] 紫云寺家的兄弟姊妹 / Shiunji-ke no Kodomotachi - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕](检索用:紫云寺家的孩子们)[370.39 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/a0abd76c58e60dfedfb8a099d9dcbc974d5f3423</link>
|
||||
<contentLength>388382080</contentLength>
|
||||
<pubDate>2025-05-28T10:26:15.421</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="388382080"
|
||||
url="https://mikanani.me/Download/20250528/a0abd76c58e60dfedfb8a099d9dcbc974d5f3423.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 末日后酒店 / Apocalypse Hotel - 07 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/0b774ca43763e81fcd194df3a3763a0e0e69d782</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 末日后酒店 / Apocalypse Hotel - 07 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 末日后酒店 / Apocalypse Hotel - 07 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁日内封字幕][510.24 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/0b774ca43763e81fcd194df3a3763a0e0e69d782</link>
|
||||
<contentLength>535025408</contentLength>
|
||||
<pubDate>2025-05-26T14:11:08.855</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="535025408"
|
||||
url="https://mikanani.me/Download/20250526/0b774ca43763e81fcd194df3a3763a0e0e69d782.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 快藏好!玛琪娜同学!!(无修版) / Kakushite! Makina-san!!
|
||||
(UNCENSORED) - 08 [WebRip 1080p HEVC-10bit AAC][无字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/3d90f032388bf841912ddb4838e8109132a2a762</link>
|
||||
<title>[LoliHouse] 快藏好!玛琪娜同学!!(无修版) / Kakushite! Makina-san!! (UNCENSORED) - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][无字幕]</title>
|
||||
<description>[LoliHouse] 快藏好!玛琪娜同学!!(无修版) / Kakushite! Makina-san!! (UNCENSORED) - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][无字幕][186.87 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/3d90f032388bf841912ddb4838e8109132a2a762</link>
|
||||
<contentLength>195947392</contentLength>
|
||||
<pubDate>2025-05-26T14:10:37.054</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="195947392"
|
||||
url="https://mikanani.me/Download/20250526/3d90f032388bf841912ddb4838e8109132a2a762.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 爱有点沉重的暗黑精灵从异世界紧追不放(无修版) / Yandere Dark Elf
|
||||
(UNCENSORED) - 08 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/987b6ab7edfe39991928f8a3c24d2db49fe9ed31</link>
|
||||
<title>[LoliHouse] 爱有点沉重的暗黑精灵从异世界紧追不放(无修版) / Yandere Dark Elf (UNCENSORED) - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 爱有点沉重的暗黑精灵从异世界紧追不放(无修版) / Yandere Dark Elf (UNCENSORED) - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕][463.18 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/987b6ab7edfe39991928f8a3c24d2db49fe9ed31</link>
|
||||
<contentLength>485679424</contentLength>
|
||||
<pubDate>2025-05-26T14:10:28.481</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="485679424"
|
||||
url="https://mikanani.me/Download/20250526/987b6ab7edfe39991928f8a3c24d2db49fe9ed31.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 离开A级队伍的我,和从前的弟子往迷宫深处迈进 / Aparida - 19 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/7b4fad3788e88f2951c30cd22cae51f032f99a03</link>
|
||||
<title>[LoliHouse] 离开A级队伍的我,和从前的弟子往迷宫深处迈进 / Aparida - 19 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 离开A级队伍的我,和从前的弟子往迷宫深处迈进 / Aparida - 19 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕][559.4MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/7b4fad3788e88f2951c30cd22cae51f032f99a03</link>
|
||||
<contentLength>586573440</contentLength>
|
||||
<pubDate>2025-05-26T14:09:00</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="586573440"
|
||||
url="https://mikanani.me/Download/20250526/7b4fad3788e88f2951c30cd22cae51f032f99a03.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[MingY] 小市民系列 第二季 / Shoushimin Series S2 [18][1080p][简繁日内封]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/06df3ee89af57075a99f140282259b1605a85448</link>
|
||||
<title>[MingY] 小市民系列 第二季 / Shoushimin Series S2 [18][1080p][简繁日内封]</title>
|
||||
<description>[MingY] 小市民系列 第二季 / Shoushimin Series S2 [18][1080p][简繁日内封][172.28 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/06df3ee89af57075a99f140282259b1605a85448</link>
|
||||
<contentLength>180648672</contentLength>
|
||||
<pubDate>2025-05-26T13:30:30.832</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="180648672"
|
||||
url="https://mikanani.me/Download/20250526/06df3ee89af57075a99f140282259b1605a85448.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo
|
||||
- 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/fdba5072bf904774ece9d7e97a714a01cc9f849b</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕][514.69 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/fdba5072bf904774ece9d7e97a714a01cc9f849b</link>
|
||||
<contentLength>539691584</contentLength>
|
||||
<pubDate>2025-05-26T12:51:08.746</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="539691584"
|
||||
url="https://mikanani.me/Download/20250526/fdba5072bf904774ece9d7e97a714a01cc9f849b.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo
|
||||
- 07 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/d0f6870db31841c29d270613ce57198452c01c09</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 07 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 07 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕][405.5 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/d0f6870db31841c29d270613ce57198452c01c09</link>
|
||||
<contentLength>425197568</contentLength>
|
||||
<pubDate>2025-05-26T12:50:40.957</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="425197568"
|
||||
url="https://mikanani.me/Download/20250526/d0f6870db31841c29d270613ce57198452c01c09.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo
|
||||
- 06 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/0a0eb68429d9b21d710a1490e0055c1b9793e64b</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 06 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 06 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕][501.2 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/0a0eb68429d9b21d710a1490e0055c1b9793e64b</link>
|
||||
<contentLength>525546304</contentLength>
|
||||
<pubDate>2025-05-26T12:50:07.888</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="525546304"
|
||||
url="https://mikanani.me/Download/20250526/0a0eb68429d9b21d710a1490e0055c1b9793e64b.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 我是星际国家的恶德领主! / Ore wa Seikan Kokka no Akutoku
|
||||
Ryoushu! - 08 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/e38810a82efc94e45162986554a9a8ff025e8f6a</link>
|
||||
<title>[LoliHouse] 我是星际国家的恶德领主! / Ore wa Seikan Kokka no Akutoku Ryoushu! - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 我是星际国家的恶德领主! / Ore wa Seikan Kokka no Akutoku Ryoushu! - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕][323.89 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/e38810a82efc94e45162986554a9a8ff025e8f6a</link>
|
||||
<contentLength>339623296</contentLength>
|
||||
<pubDate>2025-05-26T12:46:31.902</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="339623296"
|
||||
url="https://mikanani.me/Download/20250526/e38810a82efc94e45162986554a9a8ff025e8f6a.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 药师少女的独语 / 药屋少女的呢喃 / Kusuriya no
|
||||
Hitorigoto - 43 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/483c6063ed79d444a3909fd16c5ac9efa1a261d7</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 药师少女的独语 / 药屋少女的呢喃 / Kusuriya no Hitorigoto - 43 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 药师少女的独语 / 药屋少女的呢喃 / Kusuriya no Hitorigoto - 43
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕][353.5 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/483c6063ed79d444a3909fd16c5ac9efa1a261d7</link>
|
||||
<contentLength>370671616</contentLength>
|
||||
<pubDate>2025-05-26T12:39:16.446</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="370671616"
|
||||
url="https://mikanani.me/Download/20250526/483c6063ed79d444a3909fd16c5ac9efa1a261d7.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 魔女与使魔 / 魔女守护者 / Witch Watch / ウィッチウォッチ - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/f1e67c3ec8f4852d349936549c7ef13c1a80b8ef</link>
|
||||
<title>[LoliHouse] 魔女与使魔 / 魔女守护者 / Witch Watch / ウィッチウォッチ - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 魔女与使魔 / 魔女守护者 / Witch Watch / ウィッチウォッチ - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕][538.25 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/f1e67c3ec8f4852d349936549c7ef13c1a80b8ef</link>
|
||||
<contentLength>564396032</contentLength>
|
||||
<pubDate>2025-05-26T09:19:26.061</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="564396032"
|
||||
url="https://mikanani.me/Download/20250526/f1e67c3ec8f4852d349936549c7ef13c1a80b8ef.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 赛马娘 芦毛灰姑娘 / Uma Musume Cinderella Gray - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/b26a55e760e9542239a2d692d0cd4589ba2297db</link>
|
||||
<title>[LoliHouse] 赛马娘 芦毛灰姑娘 / Uma Musume Cinderella Gray - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 赛马娘 芦毛灰姑娘 / Uma Musume Cinderella Gray - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕][657.89 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/b26a55e760e9542239a2d692d0cd4589ba2297db</link>
|
||||
<contentLength>689847680</contentLength>
|
||||
<pubDate>2025-05-25T23:42:09.71</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="689847680"
|
||||
url="https://mikanani.me/Download/20250525/b26a55e760e9542239a2d692d0cd4589ba2297db.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[MingY] 小市民系列 第二季 / Shoushimin Series S2 [18][1080p][简日内嵌]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/8758e40668beaa203af8653f806ac5d167589fd1</link>
|
||||
<title>[MingY] 小市民系列 第二季 / Shoushimin Series S2 [18][1080p][简日内嵌]</title>
|
||||
<description>[MingY] 小市民系列 第二季 / Shoushimin Series S2 [18][1080p][简日内嵌][285.23 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/8758e40668beaa203af8653f806ac5d167589fd1</link>
|
||||
<contentLength>299085344</contentLength>
|
||||
<pubDate>2025-05-25T23:04:52.357</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="299085344"
|
||||
url="https://mikanani.me/Download/20250525/8758e40668beaa203af8653f806ac5d167589fd1.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[奶²&LoliHouse] Everyday Host - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/3e737d687a390d5bd25d78a50f3c66533a44775f</link>
|
||||
<title>[奶²&LoliHouse] Everyday Host - 08 [WebRip 1080p HEVC-10bit AAC][简日内封字幕]</title>
|
||||
<description>[奶²&LoliHouse] Everyday Host - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简日内封字幕][76.81 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/3e737d687a390d5bd25d78a50f3c66533a44775f</link>
|
||||
<contentLength>80541120</contentLength>
|
||||
<pubDate>2025-05-25T15:59:23.623</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="80541120"
|
||||
url="https://mikanani.me/Download/20250525/3e737d687a390d5bd25d78a50f3c66533a44775f.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[百冬练习组&LoliHouse] 战队大失格 / Sentai Daishikkaku - 18 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕](检索用:战队大失格 第二季)</guid>
|
||||
<link>https://mikanani.me/Home/Episode/031de846083f69de2d3a9bed624a449e1b147fe7</link>
|
||||
<title>[百冬练习组&LoliHouse] 战队大失格 / Sentai Daishikkaku - 18 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕](检索用:战队大失格 第二季)</title>
|
||||
<description>[百冬练习组&LoliHouse] 战队大失格 / Sentai Daishikkaku - 18 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕](检索用:战队大失格 第二季)[573.07 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/031de846083f69de2d3a9bed624a449e1b147fe7</link>
|
||||
<contentLength>600907456</contentLength>
|
||||
<pubDate>2025-05-25T14:16:11.927</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="600907456"
|
||||
url="https://mikanani.me/Download/20250525/031de846083f69de2d3a9bed624a449e1b147fe7.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] Kowloon Generic Romance / 九龙大众浪漫 - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/82885897a6d491c1ae947a2e4350d8680fb137d6</link>
|
||||
<title>[LoliHouse] Kowloon Generic Romance / 九龙大众浪漫 - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] Kowloon Generic Romance / 九龙大众浪漫 - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕][310.51 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/82885897a6d491c1ae947a2e4350d8680fb137d6</link>
|
||||
<contentLength>325593344</contentLength>
|
||||
<pubDate>2025-05-25T13:31:30.336</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="325593344"
|
||||
url="https://mikanani.me/Download/20250525/82885897a6d491c1ae947a2e4350d8680fb137d6.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 推理要在晚餐后 / Nazotoki wa Dinner no Ato de - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/aa03b64cecba10c5545199fdbd6e06574f6583db</link>
|
||||
<title>[LoliHouse] 推理要在晚餐后 / Nazotoki wa Dinner no Ato de - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 推理要在晚餐后 / Nazotoki wa Dinner no Ato de - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕][785.51 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/aa03b64cecba10c5545199fdbd6e06574f6583db</link>
|
||||
<contentLength>823666944</contentLength>
|
||||
<pubDate>2025-05-25T07:49:41.952</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="823666944"
|
||||
url="https://mikanani.me/Download/20250525/aa03b64cecba10c5545199fdbd6e06574f6583db.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 炎炎消防队 叁之章 / Enen no Shouboutai S3 / Fire Force
|
||||
Season 3 - 08 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕](检索用:炎炎消防队 第三季)</guid>
|
||||
<link>https://mikanani.me/Home/Episode/289f4d37f049939c4d7fafe3aa717b8fde3338cd</link>
|
||||
<title>[LoliHouse] 炎炎消防队 叁之章 / Enen no Shouboutai S3 / Fire Force Season 3 - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕](检索用:炎炎消防队 第三季)</title>
|
||||
<description>[LoliHouse] 炎炎消防队 叁之章 / Enen no Shouboutai S3 / Fire Force Season 3 - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕](检索用:炎炎消防队 第三季)[387.68 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/289f4d37f049939c4d7fafe3aa717b8fde3338cd</link>
|
||||
<contentLength>406511936</contentLength>
|
||||
<pubDate>2025-05-25T02:18:37.717</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="406511936"
|
||||
url="https://mikanani.me/Download/20250525/289f4d37f049939c4d7fafe3aa717b8fde3338cd.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 乡下大叔成为剑圣 / Katainaka no Ossan, Kensei ni Naru - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/5207a98660963fbfa73a5e2cebab8302408ea620</link>
|
||||
<title>[LoliHouse] 乡下大叔成为剑圣 / Katainaka no Ossan, Kensei ni Naru - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 乡下大叔成为剑圣 / Katainaka no Ossan, Kensei ni Naru - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕][518.08 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/5207a98660963fbfa73a5e2cebab8302408ea620</link>
|
||||
<contentLength>543246272</contentLength>
|
||||
<pubDate>2025-05-25T01:20:03.124</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="543246272"
|
||||
url="https://mikanani.me/Download/20250525/5207a98660963fbfa73a5e2cebab8302408ea620.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 记忆缝线 / Your Forma / ユア・フォルマ - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/065681735c97649ea5ca912d0564a3cf67436336</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 记忆缝线 / Your Forma / ユア・フォルマ - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 记忆缝线 / Your Forma / ユア・フォルマ - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁日内封字幕][765.22 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/065681735c97649ea5ca912d0564a3cf67436336</link>
|
||||
<contentLength>802391296</contentLength>
|
||||
<pubDate>2025-05-24T22:32:32.664</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="802391296"
|
||||
url="https://mikanani.me/Download/20250524/065681735c97649ea5ca912d0564a3cf67436336.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[北宇治字幕组&LoliHouse] 地。-关于地球的运动- / Chi. Chikyuu no Undou ni
|
||||
Tsuite [01-25 修正合集][WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕][Fin]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/68ebf1641e69a5b5c9eddbe75fc918ccda5ebf88</link>
|
||||
<title>[北宇治字幕组&LoliHouse] 地。-关于地球的运动- / Chi. Chikyuu no Undou ni Tsuite [01-25
|
||||
修正合集][WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕][Fin]</title>
|
||||
<description>[北宇治字幕组&LoliHouse] 地。-关于地球的运动- / Chi. Chikyuu no Undou ni Tsuite [01-25
|
||||
修正合集][WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕][Fin][4.98 GB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/68ebf1641e69a5b5c9eddbe75fc918ccda5ebf88</link>
|
||||
<contentLength>5347234304</contentLength>
|
||||
<pubDate>2025-05-24T12:29:54.387</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="5347234304"
|
||||
url="https://mikanani.me/Download/20250524/68ebf1641e69a5b5c9eddbe75fc918ccda5ebf88.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 再见,地球 / Bye Bye, Earth - 18 [WebRip 1080p
|
||||
HEVC-10bit AAC][英语内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/22728a5779adeacb356f7c8bb06b337688cca0af</link>
|
||||
<title>[LoliHouse] 再见,地球 / Bye Bye, Earth - 18 [WebRip 1080p HEVC-10bit AAC][英语内封字幕]</title>
|
||||
<description>[LoliHouse] 再见,地球 / Bye Bye, Earth - 18 [WebRip 1080p HEVC-10bit
|
||||
AAC][英语内封字幕][1 GB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/22728a5779adeacb356f7c8bb06b337688cca0af</link>
|
||||
<contentLength>1073741824</contentLength>
|
||||
<pubDate>2025-05-24T08:59:04.545</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="1073741824"
|
||||
url="https://mikanani.me/Download/20250524/22728a5779adeacb356f7c8bb06b337688cca0af.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 摇滚乐是淑女的嗜好 / Rock wa Lady no Tashinami Deshite - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/548a652eafc07835927caa73ad051cdba2f79837</link>
|
||||
<title>[LoliHouse] 摇滚乐是淑女的嗜好 / Rock wa Lady no Tashinami Deshite - 08 [WebRip 1080p
|
||||
HEVC-10bit AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 摇滚乐是淑女的嗜好 / Rock wa Lady no Tashinami Deshite - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕][528.28 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/548a652eafc07835927caa73ad051cdba2f79837</link>
|
||||
<contentLength>553941760</contentLength>
|
||||
<pubDate>2025-05-24T02:07:09.58</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="553941760"
|
||||
url="https://mikanani.me/Download/20250524/548a652eafc07835927caa73ad051cdba2f79837.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 男女之间存在纯友情吗?(不,不存在!) / 男女の友情は成立する?(いや、しないっ!!) /
|
||||
Danjoru - 08 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/a070101cd473badc4c474f06eddcb8f057e7e459</link>
|
||||
<title>[LoliHouse] 男女之间存在纯友情吗?(不,不存在!) / 男女の友情は成立する?(いや、しないっ!!) / Danjoru - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 男女之间存在纯友情吗?(不,不存在!) / 男女の友情は成立する?(いや、しないっ!!) / Danjoru - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁内封字幕][284.9 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/a070101cd473badc4c474f06eddcb8f057e7e459</link>
|
||||
<contentLength>298739296</contentLength>
|
||||
<pubDate>2025-05-23T23:50:55.453</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="298739296"
|
||||
url="https://mikanani.me/Download/20250523/a070101cd473badc4c474f06eddcb8f057e7e459.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[MingY&Billion Meta Lab] mono女孩 / mono [06][1080p][简繁日内封]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/7623fe2bf57bc7326d9b68e3e0634d5a9c59214b</link>
|
||||
<title>[MingY&Billion Meta Lab] mono女孩 / mono [06][1080p][简繁日内封]</title>
|
||||
<description>[MingY&Billion Meta Lab] mono女孩 / mono [06][1080p][简繁日内封][219.82 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/7623fe2bf57bc7326d9b68e3e0634d5a9c59214b</link>
|
||||
<contentLength>230497984</contentLength>
|
||||
<pubDate>2025-05-23T18:33:22.455</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="230497984"
|
||||
url="https://mikanani.me/Download/20250523/7623fe2bf57bc7326d9b68e3e0634d5a9c59214b.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[MingY&Billion Meta Lab] mono女孩 / mono [06][1080p][简日内嵌]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/6884d21665497566a6ef7de8c7dddad77a3fc319</link>
|
||||
<title>[MingY&Billion Meta Lab] mono女孩 / mono [06][1080p][简日内嵌]</title>
|
||||
<description>[MingY&Billion Meta Lab] mono女孩 / mono [06][1080p][简日内嵌][363.78 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/6884d21665497566a6ef7de8c7dddad77a3fc319</link>
|
||||
<contentLength>381450976</contentLength>
|
||||
<pubDate>2025-05-23T15:24:38.661</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="381450976"
|
||||
url="https://mikanani.me/Download/20250523/6884d21665497566a6ef7de8c7dddad77a3fc319.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[LoliHouse] 天才治疗师退队作为无照治疗师快乐过活 / Yami Healer - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/175e0b5cceec1d368abac73301b687e2a9f66983</link>
|
||||
<title>[LoliHouse] 天才治疗师退队作为无照治疗师快乐过活 / Yami Healer - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕]</title>
|
||||
<description>[LoliHouse] 天才治疗师退队作为无照治疗师快乐过活 / Yami Healer - 08 [WebRip 1080p HEVC-10bit
|
||||
AAC][简繁内封字幕][220.39 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/175e0b5cceec1d368abac73301b687e2a9f66983</link>
|
||||
<contentLength>231095664</contentLength>
|
||||
<pubDate>2025-05-23T08:42:03.073</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="231095664"
|
||||
url="https://mikanani.me/Download/20250523/175e0b5cceec1d368abac73301b687e2a9f66983.torrent" />
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="false">[喵萌奶茶屋&LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu
|
||||
Made - 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
|
||||
<link>https://mikanani.me/Home/Episode/1b0777aead1c19c0cf31fae317a9638ca8419148</link>
|
||||
<title>[喵萌奶茶屋&LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu Made - 08 [WebRip
|
||||
1080p HEVC-10bit AAC][简繁日内封字幕]</title>
|
||||
<description>[喵萌奶茶屋&LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu Made - 08
|
||||
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕][348.98 MB]</description>
|
||||
<torrent xmlns="https://mikanani.me/0.1/">
|
||||
<link>https://mikanani.me/Home/Episode/1b0777aead1c19c0cf31fae317a9638ca8419148</link>
|
||||
<contentLength>365932064</contentLength>
|
||||
<pubDate>2025-05-23T00:20:40.732</pubDate>
|
||||
</torrent>
|
||||
<enclosure type="application/x-bittorrent" length="365932064"
|
||||
url="https://mikanani.me/Download/20250523/1b0777aead1c19c0cf31fae317a9638ca8419148.torrent" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
d8:announce35:http://tr.bangumi.moe:6969/announce13:announce-listll35:http://tr.bangumi.moe:6969/announceel33:http://t.nyaatracker.com/announceel40:http://open.acgtracker.com:1096/announceel43:http://open.nyaatorrents.info:6544/announceel32:http://t2.popgo.org:7456/annonceel35:http://share.camoe.cn:8080/announceel36:http://opentracker.acgnx.se/announceel32:http://tracker.acgnx.se/announceel36:http://nyaa.tracker.wf:7777/announceel39:http://sukebei.tracker.wf:8888/announceel41:udp://tracker.torrent.eu.org:451/announceel33:udp://open.stealth.si:80/announceel42:udp://tracker.opentrackr.org:1337/announceel30:http://t.acg.rip:6699/announceel44:http://share.hkg-fansub.info:80/announce.phpel38:http://tracker.sbsub.com:2710/announceee10:created by12:rin-pr/0.5.113:creation datei1741504567e8:encoding5:UTF-84:hash40:85d9da563dd806588b6ea78ca1d6d3d564a848cb4:infod6:lengthi503080617e4:name101:[LoliHouse] NEET Kunoichi to Nazeka Dousei Hajimemashita - 10 [WebRip 1080p HEVC-10bit AAC SRTx2].mkv12:piece lengthi16777216e6:pieces600:ЀÄû&ÑnT™¥»úsο~b,B@×´Kܱ¾,žíU wA1ò°
|
||||
¯Ê#ŠÀ‘öèz¸ºñj
ÉB%<³ÚKaXæÁöŒz|Ö›‰—U¼à³g\˜Ç0ØSýão1¿‚ŠVr
m‡Uá
|
||||
‰»„±ÖÄÕ·u‡ˆïbh‘<68>þ<EFBFBD>ÍdD½4€ó(›`þ¦E¸"Fì·à'Û'fédâ>Ò®ÇèV õDµ2Ö 8^Í]˜çÛaöäÚ&ûŽ‘ÜFÑöÃ\YVÀ”éE×Ç›UTÒ"ί‘s`hü8&<dâ2|eš)j6&ÏQ~î^Y<>Šx&3c‰ÆÁÛi-$J ž —SQ}’9ƒs<C692>¦eJ2^4¼~7Õ²/Û?ö!òTiêúé‡0Åæˆá{ЧyW;Ž—<04>7øËDËÐìÝz|L+I:¯<µiÍ8Ð\ªý2«y[Ö~üµ.º'D~Éç“ÍN“}êGEN)qO³ëiÄG£Ú<C2A3>ÛSU‚±'°q OÂñ4êÞ*iOA¦(e¤í/®¤kÝ}lï£zÞ!Ž]á<>$!¸ØÛzEÞìnHœCø<>¼$£§5ýsÃÛòéämÌ<3óàÜüœGkXè¼ä”ÁbðK3ó¤r2Æcð£½Yµ©B£ÛB‰ŒÏ³<C38F>ÍðÛ9P§d~6DŒdP€#˜•Û é•óÔ ™49¥"È SyðÿeÍŠ8—/#•*<2A>®¸ZºE6_ˆ(’Ù£ëËUHÝXKU?ùtÉÄ<TlxWÔee
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"status":404}
|
@ -0,0 +1 @@
|
||||
{"status":404}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,5 @@
|
||||
d8:announce40:http://open.acgtracker.com:1096/announce13:announce-listll40:http://open.acgtracker.com:1096/announceel36:http://nyaa.tracker.wf:7777/announceel39:http://sukebei.tracker.wf:8888/announceel41:udp://tracker.torrent.eu.org:451/announceel33:udp://open.stealth.si:80/announceel42:udp://tracker.opentrackr.org:1337/announceel30:http://t.acg.rip:6699/announceel44:http://share.hkg-fansub.info:80/announce.phpel38:http://tracker.sbsub.com:2710/announceel44:udp://tracker.openbittorrent.com:80/announceel38:udp://tracker.publicbt.com:80/announceel32:udp://tracker.prq.to:80/announceel35:udp://104.238.198.186:8000/announceel36:http://104.238.198.186:8000/announceel29:http://94.228.192.98/announceel30:http://share.dmhy.org/annonuceel34:http://tracker.btcake.com/announceel37:http://tracker.ktxp.com:6868/announceel37:http://tracker.ktxp.com:7070/announceel33:http://bt.sc-ol.com:2710/announceel35:http://btfile.sdo.com:6961/announceel40:https://t-115.rhcloud.com/only_for_ylbudel35:http://tr.bangumi.moe:6969/announceel38:http://exodus.desync.com:6969/announceel35:udp://coppersurfer.tk:6969/announceel39:http://tracker3.torrentino.com/announceel39:http://tracker2.torrentino.com/announceel36:udp://open.demonii.com:1337/announceel31:udp://tracker.ex.ua:80/announceel29:http://pubt.net:2710/announceel32:http://tracker.tfile.me/announceel44:http://bigfoot1942.sektori.org:6969/announceel33:http://t.nyaatracker.com/announceel32:udp://bt.sc-ol.com:2710/announceee10:created by38:https://github.com/airium/TorrentUtils13:creation datei1743960055e8:encoding5:UTF-84:hash40:e6124a37985cd30b35314bd96da72a448990c1ad4:infod6:lengthi645807697e4:name64:[LoliHouse] Aparida - 12 [WebRip 1080p HEVC-10bit AAC SRTx2].mkv12:piece lengthi16777216e6:pieces780:ól3ySë`ýAS±å´æ…É!§LVÈëòi“Ý€¿LòÒú¡ú¯r´Š$t؟ד¬<E2809C>.‰@.ÿZ‚ßÑf
|
||||
w³g ˆ”‰m;úÝ,€8¯<38>ÒŠ~º¢Åˆ-xS¿<µ
|
||||
¿–¯u¹ºeZSŒ°ÈìåªùöÄÉÕ}W«ù¦äþ<C3A4>#Ü%Àþ¤ÿQ2jQ€`Oç¹îVâfà6>úz™®Ïý<d¸âîSÝcǹc!#.µX¶äÏ9Ñ®7;©¿½–ÝÐõŽª‡Šô+h¬±ÐžF&Ç2qpVêÔ·ºu«,<2C>Ò—-ð‡ó›Ù®Ã£÷¨¸ç<C2B8>µþ’3ß–„ª06ý×ܬ†îyY{1^ÛÁ‹u¹ý”Љ“Œxÿ¬4w\%ó$S:”¨¼jFÕ²B#¨½˜µã÷|<7C>´Ï`{74x²M¿‚Äcí´‹Ûy¡,FR7½¤ìµénž]ƒþ¥ÒÕb¡MG¡òþŒé’&ÄßÚÅFH©2b$¾L
îtò"ü™¶o\ýu0çaï—L~ýÙ9׺QÑc5Wµ~ô=5b’UØ8büÍ\×Ó¸Èâ>¤‰¿({ž;)~#ÆŽôa.+/=ø*àÍ4q%ÒžÍðFæ«û7T,¥ƒ‰þš²Ûä¹¼u&æùæQOŸ97 Ìh*û’Ùýl‚ðÃט¶<CB9C>
|
||||
:{:5}šf°ð«!tv_ùêªyï6ÕŽ ‘êW[1Xôüo½}ÿü'µý|'Þ>0<>
|
||||
ÆfrÁ@ÛÄM&…¼ó.0âî7Vhþ—竟œ²‹7Á0
Ü!j²¸{áeã•m0GÒç•óŒnÁþù6-+¡)¿@Õ¦©¯C˨ñ)‹Å‚Kî;±E˜ÐraT|£.SÖ¶…@ÁÿÒ•Èj¨1?ãJ)H†–/va<>˜TÿTq¡f Ъ€™o<17>'vmu¥ÜÛ4ð
).‡[ª¥B·žµ?«÷îà q$ëöR¨<52>åKtá—VŠßƒí<15>×<1A>aŽÅcW üQw”˜_Zb·Uœy<C593>¼÷P<C3B7>·!ee
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
d8:announce35:http://tr.bangumi.moe:6969/announce13:announce-listll35:http://tr.bangumi.moe:6969/announceel33:http://t.nyaatracker.com/announceel40:http://open.acgtracker.com:1096/announceel43:http://open.nyaatorrents.info:6544/announceel32:http://t2.popgo.org:7456/annonceel35:http://share.camoe.cn:8080/announceel36:http://opentracker.acgnx.se/announceel32:http://tracker.acgnx.se/announceel36:http://nyaa.tracker.wf:7777/announceel39:http://sukebei.tracker.wf:8888/announceel41:udp://tracker.torrent.eu.org:451/announceel33:udp://open.stealth.si:80/announceel42:udp://tracker.opentrackr.org:1337/announceel30:http://t.acg.rip:6699/announceel44:http://share.hkg-fansub.info:80/announce.phpel38:http://tracker.sbsub.com:2710/announceee10:created by12:rin-pr/0.5.113:creation datei1744261256e8:encoding5:UTF-84:hash40:e8f6e8017e52d09d6a308007b4a455815b0e00474:infod6:lengthi80105065e4:name74:[MILKs&LoliHouse] Everyday Host - 01 [WebRip 1080p HEVC-10bit AAC ASS].mkv12:piece lengthi16777216e6:pieces100:Ž”¾„4Þ·óÙÞEOVŽr÷üaXKÛÐŒ´V¶W ÓM3ê5<C3AA>utµÎAM¼1²´±ÔÛ:¬@
|
||||
ÛíJ8]µsñöâ&ÑQ:3›%CbÀ¦ƒŒ_h¬¹xiÍ=õ}UGù@ee
|
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user