fix: fix testsuite

This commit is contained in:
master 2025-05-26 02:44:46 +08:00
parent 313b1bf1ba
commit 22a2ce0559
1001 changed files with 299176 additions and 5417 deletions

159
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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 }

View 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(())
}

View 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(())
}

View File

@ -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,

View File

@ -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?;

View File

@ -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";

View File

@ -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::{

View File

@ -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/")?,
);

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff

View File

@ -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>

View 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 - &#x7528;&#x6237;&#x767B;&#x5F55;</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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<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">登&nbsp;&nbsp;&nbsp;</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">注&nbsp;&nbsp;&nbsp;</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">登&nbsp;&nbsp;&nbsp;</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>&nbsp;&nbsp;搜索站内
</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>

View 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">[喵萌奶茶屋&amp;LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu
Made - 09 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/df5e27aaf5dd59b2ceb427e5eaeae97a37be2196</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu Made - 09 [WebRip
1080p HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;LoliHouse] 测不准的阿波连同学 第二季 / Aharen-san wa Hakarenai
S2 - 08 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/348a1c6e4c163b6c99eba34746e531d17090d00b</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 测不准的阿波连同学 第二季 / Aharen-san wa Hakarenai S2 - 08 [WebRip
1080p HEVC-10bit AAC][简繁内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;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>[喵萌奶茶屋&amp;LoliHouse] 杂旅 / 随兴旅-That's Journey- / Zatsu Tabi: That's Journey - 08
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;LoliHouse] 时光流逝,饭菜依旧美味 / Hibi wa Sugiredo Meshi
Umashi - 07 [WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/64cb1e3ea9d5708bba818872daaaa7bec8072e2e</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 时光流逝,饭菜依旧美味 / Hibi wa Sugiredo Meshi Umashi - 07 [WebRip
1080p HEVC-10bit AAC ASSx2][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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&amp;LoliHouse] 机动战士高达 GQuuuuuuX / Mobile Suit Gundam
GQuuuuuuX - 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/631132921dae5c1708deff1cf23d0c0f309836d1</link>
<title>[SweetSub&amp;LoliHouse] 机动战士高达 GQuuuuuuX / Mobile Suit Gundam GQuuuuuuX - 08
[WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[SweetSub&amp;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">[喵萌奶茶屋&amp;LoliHouse] 末日后酒店 / Apocalypse Hotel - 07 [WebRip
1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/0b774ca43763e81fcd194df3a3763a0e0e69d782</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 末日后酒店 / Apocalypse Hotel - 07 [WebRip 1080p HEVC-10bit
AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo
- 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/fdba5072bf904774ece9d7e97a714a01cc9f849b</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 08 [WebRip 1080p
HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo
- 07 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/d0f6870db31841c29d270613ce57198452c01c09</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 07 [WebRip 1080p
HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo
- 06 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/0a0eb68429d9b21d710a1490e0055c1b9793e64b</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 圣女因太过完美一点也不讨人喜欢而被废除婚约卖到邻国 / Kanpekiseijo - 06 [WebRip 1080p
HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[喵萌奶茶屋&amp;LoliHouse] 药师少女的独语 / 药屋少女的呢喃 / Kusuriya no
Hitorigoto - 43 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/483c6063ed79d444a3909fd16c5ac9efa1a261d7</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 药师少女的独语 / 药屋少女的呢喃 / Kusuriya no Hitorigoto - 43 [WebRip
1080p HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[奶²&amp;LoliHouse] Everyday Host - 08 [WebRip 1080p HEVC-10bit
AAC][简日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/3e737d687a390d5bd25d78a50f3c66533a44775f</link>
<title>[奶²&amp;LoliHouse] Everyday Host - 08 [WebRip 1080p HEVC-10bit AAC][简日内封字幕]</title>
<description>[奶²&amp;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">[百冬练习组&amp;LoliHouse] 战队大失格 / Sentai Daishikkaku - 18 [WebRip
1080p HEVC-10bit AAC][简繁内封字幕](检索用:战队大失格 第二季)</guid>
<link>https://mikanani.me/Home/Episode/031de846083f69de2d3a9bed624a449e1b147fe7</link>
<title>[百冬练习组&amp;LoliHouse] 战队大失格 / Sentai Daishikkaku - 18 [WebRip 1080p HEVC-10bit
AAC][简繁内封字幕](检索用:战队大失格 第二季)</title>
<description>[百冬练习组&amp;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">[喵萌奶茶屋&amp;LoliHouse] 记忆缝线 / Your Forma / ユア・フォルマ - 08 [WebRip
1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/065681735c97649ea5ca912d0564a3cf67436336</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 记忆缝线 / Your Forma / ユア・フォルマ - 08 [WebRip 1080p HEVC-10bit
AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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">[北宇治字幕组&amp;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>[北宇治字幕组&amp;LoliHouse] 地。-关于地球的运动- / Chi. Chikyuu no Undou ni Tsuite [01-25
修正合集][WebRip 1080p HEVC-10bit AAC ASSx2][简繁日内封字幕][Fin]</title>
<description>[北宇治字幕组&amp;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&amp;Billion Meta Lab] mono女孩 / mono [06][1080p][简繁日内封]</guid>
<link>https://mikanani.me/Home/Episode/7623fe2bf57bc7326d9b68e3e0634d5a9c59214b</link>
<title>[MingY&amp;Billion Meta Lab] mono女孩 / mono [06][1080p][简繁日内封]</title>
<description>[MingY&amp;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&amp;Billion Meta Lab] mono女孩 / mono [06][1080p][简日内嵌]</guid>
<link>https://mikanani.me/Home/Episode/6884d21665497566a6ef7de8c7dddad77a3fc319</link>
<title>[MingY&amp;Billion Meta Lab] mono女孩 / mono [06][1080p][简日内嵌]</title>
<description>[MingY&amp;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">[喵萌奶茶屋&amp;LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu
Made - 08 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]</guid>
<link>https://mikanani.me/Home/Episode/1b0777aead1c19c0cf31fae317a9638ca8419148</link>
<title>[喵萌奶茶屋&amp;LoliHouse] 直至魔女消逝 / 直到某魔女死去 / Aru Majo ga Shinu Made - 08 [WebRip
1080p HEVC-10bit AAC][简繁日内封字幕]</title>
<description>[喵萌奶茶屋&amp;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>

View File

@ -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 mUá
‰»„±ÖÄÕ·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 ñ4êÞ*iOA¦(e¤í/®¤kÝ}lï£zÞ!Ž]á<>$!¸Ø Û zEÞìnHœCø<>¼$£§5ýsÃÛòéämÌ<3óàÜüœGkXè¼äÁbðK3ó¤r2Æcð£½Yµ©B£ÛB‰ŒÏ³<C38F>ÍðÛ9 P§d~6DŒdP€#˜•Û é•óÔ ™49¥"È SyðÿeÍŠ8—/#•*<2A>®¸ZºE6_ˆ(’Ù£ëËUHÝXKU?ùtÉÄ<TlxWÔee

File diff suppressed because one or more lines are too long

View File

@ -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>µþ„ª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µ~ô=5bUØ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ŽÅc W üQw”˜_Zb·Uœy<C593>¼÷P<C3B7>·!ee

File diff suppressed because one or more lines are too long

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More