a bunc of stuff
This commit is contained in:
1 parent
ef2a685561
commit
c9f35ae27a
41 files changed
+1054
-134
No files matched your search
+79
-5
@@ -870,6 +870,75 @@
|
||||
|
||||
loadHeroProxyCache();
|
||||
|
||||
// --- POSTER IMAGE BLOB CACHE (IndexedDB + in-memory) ---
|
||||
const _posterBlobCache = new Map(); // url -> blobURL, lives for session
|
||||
const _IMAGE_DB_NAME = 'animex-poster-cache';
|
||||
const _IMAGE_DB_STORE = 'posters';
|
||||
let _imageDb = null;
|
||||
|
||||
(function initImageDb() {
|
||||
try {
|
||||
const req = indexedDB.open(_IMAGE_DB_NAME, 1);
|
||||
req.onupgradeneeded = (e) => {
|
||||
e.target.result.createObjectStore(_IMAGE_DB_STORE, { keyPath: 'url' });
|
||||
};
|
||||
req.onsuccess = (e) => { _imageDb = e.target.result; };
|
||||
req.onerror = () => {};
|
||||
} catch (e) {}
|
||||
})();
|
||||
|
||||
function _getImageBlob(url) {
|
||||
return new Promise((resolve) => {
|
||||
if (!_imageDb) return resolve(null);
|
||||
try {
|
||||
const tx = _imageDb.transaction(_IMAGE_DB_STORE, 'readonly');
|
||||
const req = tx.objectStore(_IMAGE_DB_STORE).get(url);
|
||||
req.onsuccess = () => resolve(req.result?.blob || null);
|
||||
req.onerror = () => resolve(null);
|
||||
} catch (e) { resolve(null); }
|
||||
});
|
||||
}
|
||||
|
||||
function _saveImageBlob(url, blob) {
|
||||
if (!_imageDb) return;
|
||||
try {
|
||||
const tx = _imageDb.transaction(_IMAGE_DB_STORE, 'readwrite');
|
||||
tx.objectStore(_IMAGE_DB_STORE).put({ url, blob });
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function loadPosterImage(imgEl, proxyUrl) {
|
||||
if (!proxyUrl || !imgEl) return;
|
||||
|
||||
// 1. In-memory hit — instant
|
||||
if (_posterBlobCache.has(proxyUrl)) {
|
||||
imgEl.src = _posterBlobCache.get(proxyUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. IndexedDB hit — fast, no network
|
||||
const cachedBlob = await _getImageBlob(proxyUrl);
|
||||
if (cachedBlob) {
|
||||
const blobUrl = URL.createObjectURL(cachedBlob);
|
||||
_posterBlobCache.set(proxyUrl, blobUrl);
|
||||
imgEl.src = blobUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Network fetch — display immediately, then cache the response
|
||||
imgEl.src = proxyUrl;
|
||||
try {
|
||||
const res = await fetch(proxyUrl);
|
||||
if (res.ok) {
|
||||
const blob = await res.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
_posterBlobCache.set(proxyUrl, blobUrl);
|
||||
imgEl.src = blobUrl; // swap to blob URL
|
||||
_saveImageBlob(proxyUrl, blob); // persist for next visit
|
||||
}
|
||||
} catch (e) { /* keep original src on failure */ }
|
||||
}
|
||||
|
||||
// --- ELEMENT SELECTORS ---
|
||||
const mainContentArea = document.getElementById("main-content-area");
|
||||
|
||||
@@ -1256,16 +1325,20 @@
|
||||
<div class="poster-image-wrapper">
|
||||
<div class="episode-indicator">EP ${lastWatchedEp} • ${indicatorText}</div>
|
||||
<button class="poster-info-btn" title="Series Info"><i class="fas fa-info-circle"></i></button>
|
||||
<img src="${getProxyImageUrl(
|
||||
animeData.images?.jpg?.large_image_url ||
|
||||
animeData.images?.jpg?.image_url)
|
||||
}" alt="${title}" loading="lazy">
|
||||
<img alt="${title}" loading="lazy">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-inner" style="width: ${progressPercent}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="poster-title" title="${title}">${title}</p>
|
||||
`;
|
||||
loadPosterImage(
|
||||
posterContainer.querySelector('img'),
|
||||
getProxyImageUrl(
|
||||
animeData.images?.jpg?.large_image_url ||
|
||||
animeData.images?.jpg?.image_url
|
||||
)
|
||||
);
|
||||
posterContainer.addEventListener("click", () =>
|
||||
openSeriesOverlay(animeData.mal_id, episodeToOpen, true)
|
||||
);
|
||||
@@ -1310,10 +1383,11 @@
|
||||
posterContainer.dataset.malId = anime.id;
|
||||
posterContainer.innerHTML = `
|
||||
<div class="poster-image-wrapper">
|
||||
<img src="${getProxyImageUrl(anime.image)}" alt="${anime.name}" loading="lazy">
|
||||
<img alt="${anime.name}" loading="lazy">
|
||||
</div>
|
||||
<p class="poster-title" title="${anime.name}">${anime.name}</p>
|
||||
`;
|
||||
loadPosterImage(posterContainer.querySelector('img'), getProxyImageUrl(anime.image));
|
||||
posterContainer.addEventListener("click", () =>
|
||||
openSeriesOverlay(anime.id)
|
||||
);
|
||||
|
||||
@@ -390,6 +390,12 @@
|
||||
border: 1px solid #00b4d8;
|
||||
}
|
||||
|
||||
.mod-badge.weebcentral {
|
||||
background: rgba(0, 216, 7, 0.2);
|
||||
color: #00d819;
|
||||
border: 1px solid #00d819;
|
||||
}
|
||||
|
||||
.chapter-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
+8
-15
@@ -1743,21 +1743,14 @@ iframe { width: 100%; height: 100%; border: none; }
|
||||
async function fetchAndRenderEpisodes() {
|
||||
let episodes = [];
|
||||
try {
|
||||
const mapRes = await fetch(`${serverUrl}/map/mal/${currentMalId}`);
|
||||
if (mapRes.ok) {
|
||||
const mapData = await mapRes.json();
|
||||
if (mapData.kitsu_id) {
|
||||
// Optimized paging for kitsu (Handles 1000+ eps)
|
||||
let url = `/proxy?url=https://kitsu.io/api/edge/anime/${mapData.kitsu_id}/episodes?page[limit]=20`;
|
||||
let count = 0;
|
||||
while (url && count < 60) { // Safety cap for non-cached sessions
|
||||
const r = await fetch(url);
|
||||
const d = await r.json();
|
||||
episodes = [...episodes, ...d.data];
|
||||
url = d.links?.next;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
// Single request to the backend cache endpoint — returns the complete episode
|
||||
// list for the whole series, regardless of episode count. The server handles
|
||||
// Kitsu pagination, smart staleness TTLs based on airing status, and incremental
|
||||
// background refreshes so currently-airing shows always stay up to date.
|
||||
const epRes = await fetch(`${serverUrl}/anime/${currentMalId}/episodes`);
|
||||
if (epRes.ok) {
|
||||
const epData = await epRes.json();
|
||||
episodes = epData.episodes || [];
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
|
||||
+231
-34
@@ -331,14 +331,40 @@
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
overflow-x: auto;
|
||||
padding: 10px 0 20px 0;
|
||||
padding: 10px 0 16px 0;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
/* Touch devices: hide scrollbar (they use native swipe) */
|
||||
scrollbar-width: none;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
.episode-scroller::-webkit-scrollbar { display: none; }
|
||||
.episode-scroller.is-dragging { cursor: grabbing; scroll-behavior: auto; }
|
||||
|
||||
/* Desktop (pointer device): show a thin styled scrollbar */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.episode-scroller {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,149,0,0.5) rgba(255,255,255,0.05);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.episode-scroller::-webkit-scrollbar {
|
||||
display: none;
|
||||
} /* Chrome/Safari */
|
||||
display: block;
|
||||
height: 4px;
|
||||
}
|
||||
.episode-scroller::-webkit-scrollbar-track {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.episode-scroller::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,149,0,0.5);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.episode-scroller::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
.ep-card {
|
||||
flex: 0 0 220px;
|
||||
@@ -458,6 +484,42 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Locate (crosshairs) button in scroller header */
|
||||
.locate-btn {
|
||||
background: var(--surface-color);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.locate-btn:hover {
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
background: rgba(255,149,0,0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.locate-btn:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
/* Highlight pulse — applied briefly when the card is focused */
|
||||
@keyframes ep-highlight-pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(255,149,0,0.7), 0 0 20px rgba(255,149,0,0.15); }
|
||||
40% { box-shadow: 0 0 0 6px rgba(255,149,0,0), 0 0 30px rgba(255,149,0,0.3); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(255,149,0,0), 0 0 20px rgba(255,149,0,0.15); }
|
||||
}
|
||||
.ep-card.active.ep-highlight {
|
||||
animation: ep-highlight-pulse 0.75s cubic-bezier(0.23, 1, 0.32, 1) forwards;
|
||||
}
|
||||
|
||||
/* Synopsis Section */
|
||||
#synopsis-container {
|
||||
background: var(--surface-color);
|
||||
@@ -554,14 +616,12 @@
|
||||
<div class="scroller-section">
|
||||
<div class="scroller-header">
|
||||
<h3>Episodes</h3>
|
||||
<div
|
||||
id="scroller-count"
|
||||
style="
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-secondary);
|
||||
"
|
||||
></div>
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
<div id="scroller-count" style="font-size:0.8rem; font-weight:700; color:var(--text-secondary);"></div>
|
||||
<button id="locate-ep-btn" class="locate-btn" title="Jump to current episode">
|
||||
<i class="fas fa-location-crosshairs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="episode-scroller" id="episode-scroller">
|
||||
<!-- Episodes & Boundary cards injected here -->
|
||||
@@ -577,7 +637,11 @@
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const jikanId = params.get("id");
|
||||
const currentEp = parseInt(params.get("ep"));
|
||||
let currentEp = parseInt(params.get("ep"));
|
||||
|
||||
// Episode list cache — populated once by loadEpisodeScroller()
|
||||
let allEpsData = []; // flat array of Kitsu episode objects
|
||||
const epCards = {}; // { epNumber: <DOM card element> } for O(1) active-state updates
|
||||
|
||||
const DOM = {
|
||||
player: document.getElementById("player-container"),
|
||||
@@ -601,6 +665,37 @@
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
if (!jikanId || !currentEp) return;
|
||||
|
||||
// ── Desktop scroller: wheel → horizontal scroll, mouse drag ──────
|
||||
const scroller = document.getElementById("episode-scroller");
|
||||
|
||||
scroller.addEventListener("wheel", (e) => {
|
||||
// Only hijack pure vertical wheel events (not touchpad horizontal swipes)
|
||||
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
|
||||
e.preventDefault();
|
||||
scroller.scrollLeft += e.deltaY * 1.5;
|
||||
}, { passive: false });
|
||||
|
||||
let drag = { active: false, startX: 0, scrollLeft: 0 };
|
||||
scroller.addEventListener("mousedown", (e) => {
|
||||
drag.active = true;
|
||||
drag.startX = e.pageX - scroller.offsetLeft;
|
||||
drag.scrollLeft = scroller.scrollLeft;
|
||||
scroller.classList.add("is-dragging");
|
||||
});
|
||||
const endDrag = () => { drag.active = false; scroller.classList.remove("is-dragging"); };
|
||||
scroller.addEventListener("mouseleave", endDrag);
|
||||
scroller.addEventListener("mouseup", endDrag);
|
||||
scroller.addEventListener("mousemove", (e) => {
|
||||
if (!drag.active) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - scroller.offsetLeft;
|
||||
const walk = (x - drag.startX) * 1.2;
|
||||
scroller.scrollLeft = drag.scrollLeft - walk;
|
||||
});
|
||||
// ── Locate button: scroll to & flash the active episode card ─────
|
||||
document.getElementById("locate-ep-btn").addEventListener("click", locateCurrentEpisode);
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
setupUI();
|
||||
await initialize();
|
||||
});
|
||||
@@ -839,7 +934,110 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* --- PHASE 5: EPISODE SCROLLER & CROSS-SEASON --- */
|
||||
/* ─── Locate current episode ─────────────────────────────────────
|
||||
Scrolls the active card into view and fires a brief glow pulse
|
||||
so the user's eye is drawn to it. */
|
||||
function locateCurrentEpisode() {
|
||||
const card = epCards[currentEp];
|
||||
if (!card) return;
|
||||
|
||||
card.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
|
||||
|
||||
// Re-trigger the animation by removing + re-adding the class
|
||||
card.classList.remove("ep-highlight");
|
||||
// Force reflow so removing the class is visible before re-adding
|
||||
void card.offsetWidth;
|
||||
card.classList.add("ep-highlight");
|
||||
card.addEventListener("animationend", () => card.classList.remove("ep-highlight"), { once: true });
|
||||
}
|
||||
|
||||
/* ─── In-page episode switching ─────────────────────────────────
|
||||
Called from: episode card clicks, animex-next-episode messages,
|
||||
and the browser back/forward buttons (popstate).
|
||||
Never does a full page reload — everything is updated in place. */
|
||||
async function switchEpisode(num) {
|
||||
num = parseInt(num);
|
||||
if (isNaN(num) || num === currentEp) return;
|
||||
|
||||
// 0. Kill the current iframe immediately so audio/video stops now,
|
||||
// not after the loading overlay finishes fading in.
|
||||
const liveIframe = DOM.player.querySelector("iframe");
|
||||
if (liveIframe) liveIframe.src = "about:blank";
|
||||
|
||||
const prevEp = currentEp;
|
||||
currentEp = num;
|
||||
|
||||
// 1. Update URL (no reload)
|
||||
params.set("ep", num);
|
||||
history.pushState({ ep: num }, "", "?" + params.toString());
|
||||
|
||||
// 2. Update the meta line episode number
|
||||
DOM.epNum.textContent = num;
|
||||
|
||||
// 3. Swap active state on scroller cards
|
||||
const prevCard = epCards[prevEp];
|
||||
const nextCard = epCards[num];
|
||||
|
||||
if (prevCard) {
|
||||
prevCard.classList.remove("active");
|
||||
prevCard.querySelector(".now-playing-badge")?.remove();
|
||||
}
|
||||
if (nextCard) {
|
||||
nextCard.classList.add("active");
|
||||
const thumbEl = nextCard.querySelector(".ep-card-thumb");
|
||||
if (thumbEl && !thumbEl.querySelector(".now-playing-badge")) {
|
||||
const badge = document.createElement("div");
|
||||
badge.className = "now-playing-badge";
|
||||
badge.textContent = "Playing";
|
||||
thumbEl.appendChild(badge);
|
||||
}
|
||||
setTimeout(() =>
|
||||
nextCard.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }),
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Update per-episode metadata (synopsis, loading backdrop)
|
||||
updateEpisodeMeta(num);
|
||||
|
||||
// 5. Reload the player for the new episode
|
||||
await loadPlayer(userPref === "dub", currentModule);
|
||||
|
||||
// 6. Persist progress
|
||||
logProgress();
|
||||
}
|
||||
|
||||
/* Updates synopsis and loading-screen thumbnail for a given episode
|
||||
using the already-fetched allEpsData array — no extra network call. */
|
||||
function updateEpisodeMeta(num) {
|
||||
const ep = allEpsData.find(e => e.attributes?.number == num);
|
||||
if (!ep) return;
|
||||
|
||||
const attrs = ep.attributes || {};
|
||||
|
||||
// Loading screen backdrop
|
||||
const thumb = attrs.thumbnail?.original;
|
||||
if (thumb) {
|
||||
DOM.loading.style.backgroundImage =
|
||||
`url(/proxy-image?url=${encodeURIComponent(thumb)})`;
|
||||
} else if (cachedAnimeData?.poster) {
|
||||
DOM.loading.style.backgroundImage = `url(${cachedAnimeData.poster})`;
|
||||
}
|
||||
|
||||
// Episode synopsis
|
||||
const desc = attrs.synopsis || attrs.description || cachedAnimeData?.synopsis || "";
|
||||
DOM.synopsis.textContent = desc || "No description available.";
|
||||
|
||||
// Loading screen synopsis mirror
|
||||
const loadSyn = document.getElementById("loading-synopsis");
|
||||
if (loadSyn) loadSyn.textContent = DOM.synopsis.textContent;
|
||||
}
|
||||
|
||||
/* Browser back/forward: re-sync state to what's in the URL */
|
||||
window.addEventListener("popstate", (event) => {
|
||||
const ep = event.state?.ep ?? parseInt(new URLSearchParams(window.location.search).get("ep"));
|
||||
if (ep && ep !== currentEp) switchEpisode(ep);
|
||||
});
|
||||
async function loadEpisodeScroller() {
|
||||
// Movies don't have an episode list
|
||||
if (cachedAnimeData && cachedAnimeData.type === "Movie") return;
|
||||
@@ -848,16 +1046,16 @@
|
||||
'<div class="loader-spinner" style="position:relative; transform:none; left:0; top:0; margin: 20px auto;"></div>';
|
||||
|
||||
try {
|
||||
// 1. Fetch current season episodes (Using the new cached endpoint logic)
|
||||
const mapRes = await fetch(`/map/mal/${jikanId}`);
|
||||
const map = await mapRes.json();
|
||||
// 1. Fetch ALL episodes from the backend cache endpoint.
|
||||
// The server handles Kitsu pagination, smart TTLs per airing status,
|
||||
// and background refreshes — so we always get the complete list instantly.
|
||||
const epRes = await fetch(`/anime/${jikanId}/episodes`);
|
||||
if (!epRes.ok) throw new Error("Episode cache unavailable");
|
||||
const epData = await epRes.json();
|
||||
const eps = epData.episodes || [];
|
||||
|
||||
// We fetch the full list of episodes from Kitsu (highly cached on backend)
|
||||
const kitsuEpRes = await fetch(
|
||||
`/proxy?url=https://kitsu.io/api/edge/anime/${map.kitsu_id}/episodes?page[limit]=20&sort=number`,
|
||||
);
|
||||
const kitsuData = await kitsuEpRes.json();
|
||||
const eps = kitsuData.data;
|
||||
// Keep a module-level copy for updateEpisodeMeta() lookups
|
||||
allEpsData = eps;
|
||||
|
||||
// 2. Fetch Season Mapping for Boundaries
|
||||
const seasonRes = await fetch(`/anime/${jikanId}/seasons`);
|
||||
@@ -905,11 +1103,10 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
card.onclick = () => {
|
||||
if (num == currentEp) return;
|
||||
params.set("ep", num);
|
||||
window.location.search = params.toString();
|
||||
};
|
||||
card.onclick = () => switchEpisode(num);
|
||||
|
||||
// Register in lookup map so switchEpisode() can find this card in O(1)
|
||||
epCards[num] = card;
|
||||
|
||||
DOM.scroller.appendChild(card);
|
||||
|
||||
@@ -948,7 +1145,10 @@
|
||||
}
|
||||
|
||||
document.getElementById("scroller-count").textContent =
|
||||
`${eps.length} Episodes Total`;
|
||||
`${eps.length} Episodes`;
|
||||
|
||||
// Auto-locate on initial load so the active card is visible + highlighted
|
||||
setTimeout(locateCurrentEpisode, 400);
|
||||
} catch (e) {
|
||||
DOM.scroller.innerHTML =
|
||||
'<p style="color:var(--text-secondary); font-size:0.8rem;">Episode list unavailable.</p>';
|
||||
@@ -987,13 +1187,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for messages from the player iframe
|
||||
// Listen for messages from the player iframe (e.g. "next episode" button inside player)
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data && event.data.type === "animex-next-episode") {
|
||||
const newParams = new URLSearchParams(window.location.search);
|
||||
const nextEp = currentEp + 1;
|
||||
newParams.set("ep", nextEp);
|
||||
window.location.search = newParams.toString();
|
||||
switchEpisode(currentEp + 1);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -837,27 +837,65 @@ async def mal_to_kitsu(mal_id: int):
|
||||
|
||||
raise HTTPException(status_code=404, detail="Mapping not found for this MAL ID.")
|
||||
|
||||
async def get_jikan_anime_status(mal_id: int) -> str:
|
||||
"""
|
||||
Returns the airing status string from Jikan (e.g. 'Finished Airing',
|
||||
'Currently Airing', 'Not yet aired'). Uses the existing Jikan disk cache
|
||||
so we never make redundant network requests.
|
||||
"""
|
||||
url = f"https://api.jikan.moe/v4/anime/{mal_id}"
|
||||
cached = load_cache(url)
|
||||
if cached:
|
||||
return cached.get("data", {}).get("status", "unknown")
|
||||
try:
|
||||
r = await hybrid_client.get(url, timeout=12)
|
||||
if r.status_code == 200:
|
||||
data = r.json()
|
||||
save_cache(url, data)
|
||||
return data.get("data", {}).get("status", "unknown")
|
||||
except Exception:
|
||||
pass
|
||||
return "unknown"
|
||||
|
||||
async def refresh_kitsu_episodes_cache(mal_id: int, kitsu_id: int):
|
||||
"""
|
||||
Background task to crawl Kitsu and update the local JSON cache.
|
||||
Optimized for 1000+ episode series.
|
||||
Optimized for 1000+ episode series (One Piece, Naruto, etc.).
|
||||
|
||||
Strategy:
|
||||
- Finished shows → full cache is permanent, never re-fetched.
|
||||
- Currently Airing → overlap the last 20 episodes so new ones are caught.
|
||||
- Unknown → same overlap strategy as airing.
|
||||
"""
|
||||
cache_path = get_episodes_cache_path(mal_id)
|
||||
existing_data = {"episodes": [], "last_updated": 0, "status": "unknown"}
|
||||
existing_data = {"episodes": [], "last_updated": 0, "airing_status": "unknown"}
|
||||
|
||||
if os.path.exists(cache_path):
|
||||
try:
|
||||
with open(cache_path, 'r', encoding='utf-8') as f:
|
||||
existing_data = json.load(f)
|
||||
except: pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Get the latest episode count we currently have
|
||||
# We'll start fetching from a point that ensures we don't miss anything
|
||||
# but don't re-download the whole show.
|
||||
# --- Resolve airing status (Jikan) ---
|
||||
airing_status = await get_jikan_anime_status(mal_id)
|
||||
|
||||
# Finished shows: cache is already complete — nothing to do
|
||||
if airing_status in ("Finished Airing", "Finished") and existing_data.get("episodes"):
|
||||
print(f"MAL:{mal_id} is finished airing — skipping Kitsu refresh (cache complete)")
|
||||
# Ensure the cached file carries the resolved status
|
||||
if existing_data.get("airing_status") != airing_status:
|
||||
existing_data["airing_status"] = airing_status
|
||||
with open(cache_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(existing_data, f)
|
||||
return
|
||||
|
||||
# --- Determine fetch start offset ---
|
||||
current_count = len(existing_data.get("episodes", []))
|
||||
offset = max(0, current_count - 20) # Overlap by 20 to catch updates
|
||||
# Overlap by 20 to catch any last-minute title/thumbnail updates
|
||||
offset = max(0, current_count - 20)
|
||||
|
||||
new_episodes = []
|
||||
new_episodes: list = []
|
||||
base_url = f"https://kitsu.io/api/edge/anime/{kitsu_id}/episodes"
|
||||
|
||||
try:
|
||||
@@ -865,74 +903,103 @@ async def refresh_kitsu_episodes_cache(mal_id: int, kitsu_id: int):
|
||||
|
||||
while current_url:
|
||||
r = await hybrid_client.get(current_url, timeout=15)
|
||||
if r.status_code != 200: break
|
||||
if r.status_code != 200:
|
||||
break
|
||||
|
||||
resp_json = r.json()
|
||||
batch = resp_json.get("data", [])
|
||||
if not batch: break
|
||||
if not batch:
|
||||
break
|
||||
|
||||
new_episodes.extend(batch)
|
||||
current_url = resp_json.get("links", {}).get("next")
|
||||
|
||||
# Safety for infinite loops
|
||||
if len(new_episodes) > 2000: break
|
||||
# Hard safety cap — Kitsu pagination should never exceed this
|
||||
if len(new_episodes) > 5000:
|
||||
break
|
||||
|
||||
# Merge Logic: Use a dict keyed by episode number to overwrite/append
|
||||
merged = {ep["attributes"]["number"]: ep for ep in existing_data.get("episodes", [])}
|
||||
# --- Merge: keyed by episode number so updates overwrite stale entries ---
|
||||
merged: dict = {ep["attributes"]["number"]: ep for ep in existing_data.get("episodes", [])}
|
||||
for ep in new_episodes:
|
||||
merged[ep["attributes"]["number"]] = ep
|
||||
num = ep.get("attributes", {}).get("number")
|
||||
if num is not None:
|
||||
merged[num] = ep
|
||||
|
||||
# Sort and save
|
||||
sorted_episodes = [merged[k] for k in sorted(merged.keys())]
|
||||
|
||||
updated_cache = {
|
||||
"episodes": sorted_episodes,
|
||||
"last_updated": time.time(),
|
||||
"kitsu_id": kitsu_id
|
||||
"kitsu_id": kitsu_id,
|
||||
"airing_status": airing_status,
|
||||
}
|
||||
|
||||
with open(cache_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(updated_cache, f)
|
||||
|
||||
print(f"Successfully cached {len(sorted_episodes)} episodes for MAL:{mal_id}")
|
||||
print(f"Cached {len(sorted_episodes)} episodes for MAL:{mal_id} (status: {airing_status})")
|
||||
except Exception as e:
|
||||
print(f"Background refresh failed for MAL:{mal_id}: {e}")
|
||||
|
||||
@app.get("/anime/{mal_id}/episodes")
|
||||
async def get_anime_episodes_cached(mal_id: int, background_tasks: BackgroundTasks):
|
||||
"""
|
||||
Main endpoint for series-info and view.html scroller.
|
||||
Returns cached data instantly, triggers background refresh if stale.
|
||||
Main episode list endpoint used by both view.html and series-info.html.
|
||||
|
||||
Returns the full cached episode list instantly, then triggers a background
|
||||
refresh when the data is considered stale. Staleness thresholds are driven
|
||||
by the show's Jikan airing status so we never waste bandwidth on a series
|
||||
that finished airing years ago:
|
||||
|
||||
- Finished Airing / Finished → permanent (never stale once cached)
|
||||
- Currently Airing → 6-hour TTL (weekly episodes)
|
||||
- Not yet aired / unknown → 12-hour TTL (default)
|
||||
|
||||
Response shape (same Kitsu episode objects the frontends already consume):
|
||||
{
|
||||
"episodes": [ <kitsu episode objects> ],
|
||||
"kitsu_id": <int>,
|
||||
"airing_status": "Currently Airing" | "Finished Airing" | ...,
|
||||
"last_updated": <unix timestamp>
|
||||
}
|
||||
"""
|
||||
cache_path = get_episodes_cache_path(mal_id)
|
||||
|
||||
# 1. Check mapping
|
||||
# 1. Resolve Kitsu mapping (fast — uses its own disk cache)
|
||||
map_data = await mal_to_kitsu(mal_id)
|
||||
kitsu_id = map_data["kitsu_id"]
|
||||
|
||||
# 2. Check if cache exists
|
||||
# 2. Load disk cache
|
||||
cache_exists = os.path.exists(cache_path)
|
||||
cached_data = None
|
||||
cached_data: Optional[dict] = None
|
||||
|
||||
if cache_exists:
|
||||
try:
|
||||
with open(cache_path, 'r', encoding='utf-8') as f:
|
||||
cached_data = json.load(f)
|
||||
except: cache_exists = False
|
||||
except Exception:
|
||||
cache_exists = False
|
||||
|
||||
# 3. Decision Logic
|
||||
now = time.time()
|
||||
# Stale if older than 12 hours
|
||||
is_stale = not cached_data or (now - cached_data.get("last_updated", 0) > 43200)
|
||||
|
||||
if not cache_exists:
|
||||
# First time loading: Must wait for at least one batch
|
||||
# 3. Cold start — block until we have at least one full fetch
|
||||
if not cache_exists or not cached_data:
|
||||
await refresh_kitsu_episodes_cache(mal_id, kitsu_id)
|
||||
with open(cache_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
# 4. Determine staleness based on airing status
|
||||
airing_status = cached_data.get("airing_status", "unknown")
|
||||
now = time.time()
|
||||
last_updated = cached_data.get("last_updated", 0)
|
||||
|
||||
if airing_status in ("Finished Airing", "Finished"):
|
||||
is_stale = False # permanent — never re-fetch
|
||||
elif airing_status == "Currently Airing":
|
||||
is_stale = (now - last_updated) > 21600 # 6 hours
|
||||
else:
|
||||
is_stale = (now - last_updated) > 43200 # 12 hours (default)
|
||||
|
||||
# 5. Kick off a background refresh if stale, but serve cached data right away
|
||||
if is_stale:
|
||||
# Return stale data immediately, but update in background
|
||||
background_tasks.add_task(refresh_kitsu_episodes_cache, mal_id, kitsu_id)
|
||||
|
||||
return cached_data
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"episodes": [{"id": "103482", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/103482"}, "attributes": {"createdAt": "2013-03-26T08:42:37.108Z", "updatedAt": "2021-09-17T04:19:12.419Z", "synopsis": "A group of pirates lead by Alvida plunder a ship, only to find a barrel which contains a strange boy named Luffy. Luffy is on a mission to find the legendary treasure \"One Piece\" and he befriends the ship's cabin-boy, Coby, who actually wants to become a marine officer. Luffy defeats Alvida and the two journey on in search of crewmates. ", "description": "A group of pirates lead by Alvida plunder a ship, only to find a barrel which contains a strange boy named Luffy. Luffy is on a mission to find the legendary treasure \"One Piece\" and he befriends the ship's cabin-boy, Coby, who actually wants to become a marine officer. Luffy defeats Alvida and the two journey on in search of crewmates. ", "titles": {"en_jp": "Ore wa Luffy! Kaizoku Ou ni Naru Otoko Da!", "en_us": "I'm Luffy! The Man Who's Gonna Be King of the Pirates!", "ja_jp": "\u4ffa\u306f\u30eb\u30d5\u30a3!\u6d77\u8cca\u738b\u306b\u306a\u308b\u7537\u3060!"}, "canonicalTitle": "I'm Luffy! The Man Who's Gonna Be King of the Pirates!", "seasonNumber": 1, "number": 1, "relativeNumber": 1, "airdate": "1999-10-20", "length": 24, "thumbnail": {"original": "https://media.kitsu.app/episodes/thumbnails/103482/original.jpg", "meta": {"dimensions": {}}}}, "relationships": {"media": {"links": {"self": "https://kitsu.io/api/edge/episodes/103482/relationships/media", "related": "https://kitsu.io/api/edge/episodes/103482/media"}}, "videos": {"links": {"self": "https://kitsu.io/api/edge/episodes/103482/relationships/videos", "related": "https://kitsu.io/api/edge/episodes/103482/videos"}}}}, {"id": "103483", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/103483"}, "attributes": {"createdAt": "2013-03-26T08:42:37.120Z", "updatedAt": "2021-09-17T04:19:12.936Z", "synopsis": "Luffy and Coby arrive at Shell Island, a Marine base. They meet the bounty hunter Roronoa Zoro, held under arrest by the infamous marine, Captain Morgan, and his son Helmeppo.", "description": "Luffy and Coby arrive at Shell Island, a Marine base. They meet the bounty hunter Roronoa Zoro, held under arrest by the infamous marine, Captain Morgan, and his son Helmeppo.", "titles": {"en_jp": "Daikengou Arawaru! Kaizokugari Roronoa Zoro", "en_us": "Enter the Great Swordsman! Pirate Hunter Roronoa Zoro!", "ja_jp": "\u5927\u5263\u8c6a\u73fe\u308b!\u6d77\u8cca\u72e9\u308a\u30ed\u30ed\u30ce\u30a2\u00b7\u30be\u30ed"}, "canonicalTitle": "Enter the Great Swordsman! Pirate Hunter Roronoa Zoro!", "seasonNumber": 1, "number": 2, "relativeNumber": 2, "airdate": "1999-11-17", "length": 24, "thumbnail": {"original": "https://media.kitsu.app/episodes/thumbnails/103483/original.jpg", "meta": {"dimensions": {}}}}, "relationships": {"media": {"links": {"self": "https://kitsu.io/api/edge/episodes/103483/relationships/media", "related": "https://kitsu.io/api/edge/episodes/103483/media"}}, "videos": {"links": {"self": "https://kitsu.io/api/edge/episodes/103483/relationships/videos", "related": "https://kitsu.io/api/edge/episodes/103483/videos"}}}}, {"id": "103484", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/103484"}, "attributes": {"createdAt": "2013-03-26T08:42:37.234Z", "updatedAt": "2021-09-17T04:19:13.606Z", "synopsis": "Luffy convinces Zoro to join his crew, but they must first defeat Captain Morgan. After freeing the island from Morgan's oppressive rule, Coby is accepted as a marine while the other two continue on their journey. ", "description": "Luffy convinces Zoro to join his crew, but they must first defeat Captain Morgan. After freeing the island from Morgan's oppressive rule, Coby is accepted as a marine while the other two continue on their journey. ", "titles": {"en_jp": "Morgan vs. Luffy! Nazo no Bishoujo wa Dare?", "en_us": "Morgan versus Luffy! Who's the Mysterious Pretty Girl?", "ja_jp": "\u30e2\u30fc\u30ac\u30f3VS\u30eb\u30d5\u30a3!\u8b0e\u306e\u7f8e\u5c11\u5973\u306f\u8ab0?"}, "canonicalTitle": "Morgan versus Luffy! Who's the Mysterious Pretty Girl?", "seasonNumber": 1, "number": 3, "relativeNumber": 3, "airdate": "1999-11-24", "length": 24, "thumbnail": {"original": "https://media.kitsu.app/episodes/thumbnails/103484/original.jpg", "meta": {"dimensions": {}}}}, "relationships": {"media": {"links": {"self": "https://kitsu.io/api/edge/episodes/103484/relationships/media", "related": "https://kitsu.io/api/edge/episodes/103484/media"}}, "videos": {"links": {"self": "https://kitsu.io/api/edge/episodes/103484/relationships/videos", "related": "https://kitsu.io/api/edge/episodes/103484/videos"}}}}, {"id": "103485", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/103485"}, "attributes": {"createdAt": "2013-03-26T08:42:37.247Z", "updatedAt": "2021-09-17T04:19:14.196Z", "synopsis": "Out on the sea, Luffy aLine truncated
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"episodes": [{"id": "362164", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/362164"}, "attributes": {"createdAt": "2023-12-28T21:24:33.791Z", "updatedAt": "2026-01-08T20:11:59.603Z", "synopsis": "While Yuji grapples with the weight of his sins, Naoya Zen'in and Yuta Okkotsu set out to execute him.\n\n(Source: Crunchyroll)", "description": "While Yuji grapples with the weight of his sins, Naoya Zen'in and Yuta Okkotsu set out to execute him.\n\n(Source: Crunchyroll)", "titles": {"en": "Execution"}, "canonicalTitle": "Execution", "seasonNumber": 1, "number": 1, "relativeNumber": null, "airdate": "2026-01-08", "length": 24, "thumbnail": {"tiny": "https://media.kitsu.app/episode/362164/thumbnail/tiny-dfc6a51bb928f7be5d550875ec9463cd.jpeg", "large": "https://media.kitsu.app/episode/362164/thumbnail/large-c9d90aa22a336b4796dd20103ed50b9a.jpeg", "small": "https://media.kitsu.app/episode/362164/thumbnail/small-336d1e20e7ceb81b42b99688226efe23.jpeg", "medium": "https://media.kitsu.app/episode/362164/thumbnail/medium-fbfdea714771463e7af709eaae90b1c8.jpeg", "original": "https://media.kitsu.app/episode/362164/thumbnail/87177eeeb00aa33ec9c4159e651352ec.jpg", "meta": {"dimensions": {"tiny": {"width": 160, "height": 90}, "large": {"width": 960, "height": 540}, "small": {"width": 320, "height": 180}, "medium": {"width": 640, "height": 360}}}}}, "relationships": {"media": {"links": {"self": "https://kitsu.io/api/edge/episodes/362164/relationships/media", "related": "https://kitsu.io/api/edge/episodes/362164/media"}}, "videos": {"links": {"self": "https://kitsu.io/api/edge/episodes/362164/relationships/videos", "related": "https://kitsu.io/api/edge/episodes/362164/videos"}}}}, {"id": "362165", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/362165"}, "attributes": {"createdAt": "2023-12-28T21:24:33.811Z", "updatedAt": "2026-01-08T20:12:19.956Z", "synopsis": "After fighting Yuta, Yuji learns his real objective. Megumi asks for Yuji's help to save his sister.\n\n(Source: Crunchyroll)", "description": "After fighting Yuta, Yuji learns his real objective. Megumi asks for Yuji's help to save his sister.\n\n(Source: Crunchyroll)", "titles": {"en": "One More Time"}, "canonicalTitle": "One More Time", "seasonNumber": 1, "number": 2, "relativeNumber": null, "airdate": "2026-01-08", "length": 24, "thumbnail": {"tiny": "https://media.kitsu.app/episode/362165/thumbnail/tiny-5948b076c73ccb49ff77d689f97fd0d0.jpeg", "large": "https://media.kitsu.app/episode/362165/thumbnail/large-ff55fe13956253cd0b5a1ae1ddaa0d7d.jpeg", "small": "https://media.kitsu.app/episode/362165/thumbnail/small-c7de93a1ac53445cd164bc5727a864b8.jpeg", "medium": "https://media.kitsu.app/episode/362165/thumbnail/medium-c6660e5c03ba113e601eb448e8b438c7.jpeg", "original": "https://media.kitsu.app/episode/362165/thumbnail/d7879761e157249069b6bbe4521950d1.jpg", "meta": {"dimensions": {"tiny": {"width": 160, "height": 90}, "large": {"width": 960, "height": 540}, "small": {"width": 320, "height": 180}, "medium": {"width": 640, "height": 360}}}}}, "relationships": {"media": {"links": {"self": "https://kitsu.io/api/edge/episodes/362165/relationships/media", "related": "https://kitsu.io/api/edge/episodes/362165/media"}}, "videos": {"links": {"self": "https://kitsu.io/api/edge/episodes/362165/relationships/videos", "related": "https://kitsu.io/api/edge/episodes/362165/videos"}}}}, {"id": "362166", "type": "episodes", "links": {"self": "https://kitsu.io/api/edge/episodes/362166"}, "attributes": {"createdAt": "2023-12-28T21:24:33.828Z", "updatedAt": "2026-01-15T17:12:11.065Z", "synopsis": "Itadori and his friends speak with Tengen about Kenjaku's objectives and their plans for the Culling Game.\n\n(Source: Crunchyroll)", "description": "Itadori and his friends speak with Tengen about Kenjaku's objectives and their plans for the Culling Game.\n\n(Source: Crunchyroll)", "titles": {"en": "About the Culling Game"}, "canonicalTitle": "About the Culling Game", "seasonNumber": 1, "number": 3, "relativeNumber": null, "airdate": "2026-01-15", "length": 24, "thumbnail": {"tiny": "https://media.kitsu.app/episode/362166/thumbnail/tiny-64e767e8af48432765ee6f2ac4629a06.jpeg", "large": "https://media.kitsu.app/episode/362166/thumbnail/large-70bb11b4f5ccb3aeba6665a43f991374.jpeg", "small": "https://media.kitsu.app/episode/362166/thumbnail/small-00a23edb61c4edb6ceb55604aef5b949.jpeg", "medium": "https://media.kitsu.app/episode/362166/thumbnail/medium-4aff8dea1499fdd5bf053aa1142d12c7.jpeg", "original": "https://media.kitsu.app/episode/362166/thumbnail/bd7961c0b9006e95e84e44095e3eb93a.jpg", "meta": {"dimensions": {"tiny": {"width": 160, "height": 90}, "large": {"width": 960, "height": 540}, "small": {"width": 320, "height": 180}, "medium": {"width": 640, "height": 360}}}}}, "relationships": {"media": {"links": {"self": "https://kitsu.io/api/edge/episodes/362166/relationships/media", "related": "https://kitsu.io/api/edge/episodes/362166/media"}}, Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 11179, "mal_id": 11179, "title_romaji": "Papa no Iukoto wo Kikinasai!", "title_english": "Listen to Me, Girls. I Am Your Father!", "title_native": "\u30d1\u30d1\u306e\u3044\u3046\u3053\u3068\u3092\u805e\u304d\u306a\u3055\u3044\uff01", "format": "TV", "start_date": {"year": 2012, "month": 1, "day": 11}, "season": "WINTER", "season_year": 2012, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Listen to Me, Girls. I Am Your Father!", "_start_tuple": [2012, 1, 11], "is_final_season": false}, {"anilist_id": 12673, "mal_id": 12673, "title_romaji": "Papa no Iukoto wo Kikinasai!: Pokkapoka", "title_english": null, "title_native": "\u30d1\u30d1\u306e\u3044\u3046\u3053\u3068\u3092\u805e\u304d\u306a\u3055\u3044\uff01\u307d\u3063\u304b\u307d\u304b", "format": "SPECIAL", "start_date": {"year": 2012, "month": 7, "day": 11}, "season": "SUMMER", "season_year": 2012, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Papa no Iukoto wo Kikinasai!: Pokkapoka", "_start_tuple": [2012, 7, 11], "is_final_season": false}, {"anilist_id": 17875, "mal_id": 17875, "title_romaji": "Papa no Iukoto wo Kikinasai! OVA", "title_english": null, "title_native": "\u30d1\u30d1\u306e\u3044\u3046\u3053\u3068\u3092\u805e\u304d\u306a\u3055\u3044! OVA", "format": "OVA", "start_date": {"year": 2013, "month": 6, "day": 25}, "season": "SUMMER", "season_year": 2013, "episodes": 2, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Papa no Iukoto wo Kikinasai! OVA", "_start_tuple": [2013, 6, 25], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Listen to Me, Girls. I Am Your Father!", "mal_id": 11179, "anilist_id": 11179, "start_date": {"year": 2012, "month": 1, "day": 11}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Papa no Iukoto wo Kikinasai!: Pokkapoka", "mal_id": 12673, "anilist_id": 12673, "format": "SPECIAL", "start_date": {"year": 2012, "month": 7, "day": 11}}, {"title": "Papa no Iukoto wo Kikinasai! OVA", "mal_id": 17875, "anilist_id": 17875, "format": "OVA", "start_date": {"year": 2013, "month": 6, "day": 25}}]}, "_timestamp": 1775005857.750725}
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mal_id": 2, "url": "https://myanimelist.net/manga/2/Berserk", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/1/157897.jpg", "small_image_url": "https://myanimelist.net/images/manga/1/157897t.jpg", "large_image_url": "https://myanimelist.net/images/manga/1/157897l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/1/157897.webp", "small_image_url": "https://myanimelist.net/images/manga/1/157897t.webp", "large_image_url": "https://myanimelist.net/images/manga/1/157897l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Berserk"}, {"type": "Synonym", "title": "Berserk: The Prototype"}, {"type": "Japanese", "title": "\u30d9\u30eb\u30bb\u30eb\u30af"}, {"type": "English", "title": "Berserk"}], "title": "Berserk", "title_english": "Berserk", "title_japanese": "\u30d9\u30eb\u30bb\u30eb\u30af", "title_synonyms": ["Berserk: The Prototype"], "type": "Manga", "chapters": null, "volumes": null, "status": "Publishing", "publishing": true, "published": {"from": "1989-08-25T00:00:00+00:00", "to": null, "prop": {"from": {"day": 25, "month": 8, "year": 1989}, "to": {"day": null, "month": null, "year": null}}, "string": "Aug 25, 1989 to ?"}, "score": 9.46, "scored": 9.46, "scored_by": 396593, "rank": 1, "popularity": 1, "members": 792161, "favorites": 137418, "synopsis": "Guts, a former mercenary now known as the Black Swordsman, is out for revenge. After a tumultuous childhood, he finally finds someone he respects and believes he can trust, only to have everything fall apart when this person takes away everything important to Guts for the purpose of fulfilling his own desires. Now marked for death, Guts becomes condemned to a fate in which he is relentlessly pursued by demonic beings.\n\nSetting out on a dreadful quest riddled with misfortune, Guts, armed with a massive sword and monstrous strength, will let nothing stop him, not even death itself, until he is finally able to take the head of the one who stripped him\u2014and his loved one\u2014of their humanity.\n\n[Written by MAL Rewrite]\n\nIncluded one-shot:\nVolume 14: Berserk: The Prototype", "background": "Berserk won the Excellence Award at the sixth Tezuka Osamu Cultural Prize in 2002. As of August 2025, over 70 million copies of the manga are in circulation. The series has been published in English by Dark Horse Comics since November 4, 2003. It has also been released in Argentina, Brazil, Czech Republic, France, Germany, Greece, Hong Kong, Italy, M\u00e9xico, Poland, South Korea, Spain, Taiwan, Thailand, and Turkey. In May 2021, the author Kentaro Miura suddenly died at the age of 54. Chapter 364 of Berserk was published posthumously on September 10, 2021. Miura would often share details about the series' story with his childhood friend and fellow mangaka Kouji Mori. The series resumed on June 24, 2022, with Studio Gaga handling the art and Kouji Mori's supervision.", "authors": [{"mal_id": 1868, "type": "people", "name": "Miura, Kentarou", "url": "https://myanimelist.net/people/1868/Kentarou_Miura"}, {"mal_id": 49592, "type": "people", "name": "Studio Gaga", "url": "https://myanimelist.net/people/49592/Studio_Gaga"}], "serializations": [{"mal_id": 2, "type": "manga", "name": "Young Animal", "url": "https://myanimelist.net/manga/magazine/2/Young_Animal"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 2, "type": "manga", "name": "Adventure", "url": "https://myanimelist.net/manga/genre/2/Adventure"}, {"mal_id": 46, "type": "manga", "name": "Award Winning", "url": "https://myanimelist.net/manga/genre/46/Award_Winning"}, {"mal_id": 8, "type": "manga", "name": "Drama", "url": "https://myanimelist.net/manga/genre/8/Drama"}, {"mal_id": 10, "type": "manga", "name": "Fantasy", "url": "https://myanimelist.net/manga/genre/10/Fantasy"}, {"mal_id": 14, "type": "manga", "name": "Horror", "url": "https://myanimelist.net/manga/genre/14/Horror"}], "explicit_genres": [], "themes": [{"mal_id": 58, "type": "manga", "name": "Gore", "url": "https://myanimelist.net/manga/genre/58/Gore"}, {"mal_id": 38, "type": "manga", "name": "Military", "url": "https://myanimelist.net/manga/genre/38/Military"}, {"mal_id": 40, "type": "manga", "name": "Psychological", "url": "https://myanimelist.net/manga/genre/40/Psychological"}], "demographics": [{"mal_id": 41, "type": "manga", "name": "Seinen", "url": "https://myanimelist.net/manga/genre/41/Seinen"}], "relations": [{"relation": "Adaptation", "entry": [{"mal_id": 33, "type": "anime", "name": "Kenpuu Denki Berserk", "url": "https://myanimelist.net/anime/33/Kenpuu_Denki_Berserk"}, {"mal_id": 10218, "type": "anime", "name": "Berserk: Ougon Jidai-hen I - Haou no Tamago", "url": "https://myanimelist.net/anime/10218/Berserk__Ougon_Jidai-hen_I_-_Haou_no_Tamago"}, {"mal_id": 12113, "type": "anime", "name": "Berserk: Ougon Jidai-hen II - Doldrey Kouryaku", "url": "https://myanimelist.net/anime/12113/Berserk__Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 172463, "mal_id": 57658, "title_romaji": "Jujutsu Kaisen: Shimetsu Kaiyuu - Zenpen", "title_english": "JUJUTSU KAISEN Season 3: The Culling Game Part 1", "title_native": "\u546a\u8853\u5efb\u6226 \u6b7b\u6ec5\u56de\u6e38 \u524d\u7de8", "format": "TV", "start_date": {"year": 2026, "month": 1, "day": 9}, "season": "WINTER", "season_year": 2026, "episodes": 12, "relation_type_from_parent": null, "inferred_part": 3, "is_season": true, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN Season 3: The Culling Game Part 1", "_start_tuple": [2026, 1, 9], "is_final_season": false}, {"anilist_id": 209895, "mal_id": null, "title_romaji": "Jujutsu Kaisen: Shimetsu Kaiyuu - Kouhen", "title_english": null, "title_native": "\u546a\u8853\u5efb\u6226 \u6b7b\u6ec5\u56de\u6e38 \u5f8c\u7de8", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Jujutsu Kaisen: Shimetsu Kaiyuu - Kouhen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 204066, "mal_id": 62392, "title_romaji": "Jujutsu Kaisen: Shibuya Jihen Tokubetsu Henshuu-ban x Shimetsu Kaiyuu Senkou Jouei", "title_english": "JUJUTSU KAISEN: Execution -Shibuya Incident x The Culling Game Begins-", "title_native": "\u546a\u8853\u5efb\u6226\u300e\u6e0b\u8c37\u4e8b\u5909 \u7279\u5225\u7de8\u96c6\u7248\u300f\u00d7\u300e\u6b7b\u6ec5\u56de\u6e38 \u5148\u884c\u4e0a\u6620\u300f", "format": "MOVIE", "start_date": {"year": 2025, "month": 11, "day": 7}, "season": "FALL", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN: Execution -Shibuya Incident x The Culling Game Begins-", "_start_tuple": [2025, 11, 7], "is_final_season": false}, {"anilist_id": 192809, "mal_id": 59654, "title_romaji": "Jujutsu Kaisen: Kaigyoku\u30fbGyokusetsu", "title_english": "JUJUTSU KAISEN: Hidden Inventory / Premature Death \u2013 The Movie", "title_native": "\u546a\u8853\u5efb\u6226 \u61d0\u7389\u30fb\u7389\u6298", "format": "MOVIE", "start_date": {"year": 2025, "month": 5, "day": 30}, "season": "SPRING", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN: Hidden Inventory / Premature Death \u2013 The Movie", "_start_tuple": [2025, 5, 30], "is_final_season": false}, {"anilist_id": 145064, "mal_id": 51009, "title_romaji": "Jujutsu Kaisen 2nd Season", "title_english": "JUJUTSU KAISEN Season 2", "title_native": "\u546a\u8853\u5efb\u6226 \u7b2c2\u671f", "format": "TV", "start_date": {"year": 2023, "month": 7, "day": 6}, "season": "SUMMER", "season_year": 2023, "episodes": 23, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN Season 2", "_start_tuple": [2023, 7, 6], "is_final_season": false}, {"anilist_id": 113415, "mal_id": 40748, "title_romaji": "Jujutsu Kaisen", "title_english": "JUJUTSU KAISEN", "title_native": "\u546a\u8853\u5efb\u6226", "format": "TV", "start_date": {"year": 2020, "month": 10, "day": 3}, "season": "FALL", "season_year": 2020, "episodes": 24, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN", "_start_tuple": [2020, 10, 3], "is_final_season": false}, {"anilist_id": 131573, "mal_id": 48561, "title_romaji": "Jujutsu Kaisen 0", "title_english": "JUJUTSU KAISEN 0", "title_native": "\u546a\u8853\u5efb\u6226 0", "format": "MOVIE", "start_date": {"year": 2021, "month": 12, "day": 24}, "season": "FALL", "season_year": 2021, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN 0", "_start_tuple": [2021, 12, 24], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "JUJUTSU KAISEN", "mal_id": 40748, "anilist_id": 113415, "start_date": {"year": 2020, "month": 10, "day": 3}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "JUJUTSU KAISEN Season 2", "mal_id": 51009, "anilist_id": 145064, "start_date": {"year": 2023, "month": 7, "day": 6}, "format": "TV", "is_final": false}]}, {"season_index": 3, "group_label": "Season 3", "parts": [{"short_label": "S3", "title": "JUJUTSU KAISEN Season 3: The Culling Game Part 1", "mal_id": 57658, "anilist_id": 172463, "start_date": {"year": 2026, "month": 1, "day": 9}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Jujutsu Kaisen: Shimetsu Kaiyuu - Kouhen", "mal_id": null, "anilist_id": 209895, "format": null, "Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"comix": [{"title": "S3 Start", "url": "emqg8:solo-leveling:8872908", "chapter_number": "201", "is_external": false}, {"title": "Chapter 200", "url": "emqg8:solo-leveling:2749754", "chapter_number": "200", "is_external": false}, {"title": "Chapter 199", "url": "emqg8:solo-leveling:2749680", "chapter_number": "199", "is_external": false}, {"title": "Chapter 198", "url": "emqg8:solo-leveling:2749654", "chapter_number": "198", "is_external": false}, {"title": "Chapter 197", "url": "emqg8:solo-leveling:2749621", "chapter_number": "197", "is_external": false}, {"title": "Chapter 196", "url": "emqg8:solo-leveling:2749588", "chapter_number": "196", "is_external": false}, {"title": "Chapter 195", "url": "emqg8:solo-leveling:2749516", "chapter_number": "195", "is_external": false}, {"title": "Chapter 194", "url": "emqg8:solo-leveling:2749486", "chapter_number": "194", "is_external": false}, {"title": "Chapter 193", "url": "emqg8:solo-leveling:2749419", "chapter_number": "193", "is_external": false}, {"title": "Chapter 192", "url": "emqg8:solo-leveling:2749352", "chapter_number": "192", "is_external": false}, {"title": "Chapter 191", "url": "emqg8:solo-leveling:2749287", "chapter_number": "191", "is_external": false}, {"title": "Chapter 190", "url": "emqg8:solo-leveling:2749249", "chapter_number": "190", "is_external": false}, {"title": "Chapter 189", "url": "emqg8:solo-leveling:2749217", "chapter_number": "189", "is_external": false}, {"title": "Chapter 188", "url": "emqg8:solo-leveling:2749148", "chapter_number": "188", "is_external": false}, {"title": "Chapter 187", "url": "emqg8:solo-leveling:2749064", "chapter_number": "187", "is_external": false}, {"title": "Chapter 186", "url": "emqg8:solo-leveling:2748994", "chapter_number": "186", "is_external": false}, {"title": "Chapter 185", "url": "emqg8:solo-leveling:2748965", "chapter_number": "185", "is_external": false}, {"title": "Chapter 184", "url": "emqg8:solo-leveling:2748933", "chapter_number": "184", "is_external": false}, {"title": "Chapter 183", "url": "emqg8:solo-leveling:2748875", "chapter_number": "183", "is_external": false}, {"title": "Chapter 182", "url": "emqg8:solo-leveling:2748842", "chapter_number": "182", "is_external": false}, {"title": "Chapter 181", "url": "emqg8:solo-leveling:2748772", "chapter_number": "181", "is_external": false}, {"title": "Chapter 180", "url": "emqg8:solo-leveling:2748744", "chapter_number": "180", "is_external": false}, {"title": "Chapter 179", "url": "emqg8:solo-leveling:2748672", "chapter_number": "179", "is_external": false}, {"title": "Chapter 178", "url": "emqg8:solo-leveling:2748640", "chapter_number": "178", "is_external": false}, {"title": "Chapter 177", "url": "emqg8:solo-leveling:2748574", "chapter_number": "177", "is_external": false}, {"title": "Chapter 176", "url": "emqg8:solo-leveling:2748545", "chapter_number": "176", "is_external": false}, {"title": "Chapter 175", "url": "emqg8:solo-leveling:2748506", "chapter_number": "175", "is_external": false}, {"title": "Chapter 174", "url": "emqg8:solo-leveling:2748447", "chapter_number": "174", "is_external": false}, {"title": "Chapter 173", "url": "emqg8:solo-leveling:2748393", "chapter_number": "173", "is_external": false}, {"title": "Chapter 172", "url": "emqg8:solo-leveling:2748295", "chapter_number": "172", "is_external": false}, {"title": "Chapter 171", "url": "emqg8:solo-leveling:2748234", "chapter_number": "171", "is_external": false}, {"title": "Chapter 170", "url": "emqg8:solo-leveling:2748161", "chapter_number": "170", "is_external": false}, {"title": "Chapter 169", "url": "emqg8:solo-leveling:2748137", "chapter_number": "169", "is_external": false}, {"title": "Chapter 168", "url": "emqg8:solo-leveling:2748062", "chapter_number": "168", "is_external": false}, {"title": "Chapter 167", "url": "emqg8:solo-leveling:2747987", "chapter_number": "167", "is_external": false}, {"title": "Chapter 166", "url": "emqg8:solo-leveling:2747949", "chapter_number": "166", "is_external": false}, {"title": "Chapter 165", "url": "emqg8:solo-leveling:2747890", "chapter_number": "165", "is_external": false}, {"title": "Chapter 164", "url": "emqg8:solo-leveling:2747817", "chapter_number": "164", "is_external": false}, {"title": "Chapter 163", "url": "emqg8:solo-leveling:2747751", "chapter_number": "163", "is_external": false}, {"title": "Chapter 162", "url": "emqg8:solo-leveling:2747670", "chapter_number": "162", "is_external": false}, {"title": "Chapter 161", "url": "emqg8:solo-leveling:2747640", "chapter_number": "161", "is_external": false}, {"title": "Chapter 160", "url": "emqg8:solo-leveling:2747606", "chapter_number": "160", "is_external": false}, {"title": "Chapter 159", "url": "emqg8:solo-leveling:2747545", "chapter_number": "159", "is_external": false}, {"title": "Chapter 158", "url": "emqg8:solo-leveling:2747511", "chapter_number": "158", "is_external": false}, {"title": "Chapter 157", "url": "emqg8:solo-leveling:2747486", "chapter_number": "157", "is_extLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mangadex": [{"title": "Ch. 327 - The Man Named Tadoki", "url": "0754c218-0240-4752-a688-5e7d9bc74b55", "chapter_number": "327", "is_external": false}, {"title": "Ch. 326 - To Be A Samurai", "url": "d10d4340-47f3-4002-9762-e148885243db", "chapter_number": "326", "is_external": false}, {"title": "Ch. 325 - Noted Treasure", "url": "bdfeee7a-9a50-4285-a158-e1d42f25ce49", "chapter_number": "325", "is_external": false}, {"title": "Ch. 324 - Kokura's Treasure", "url": "ee157af7-1272-4692-af7c-9e1c2d73e7fc", "chapter_number": "324", "is_external": false}, {"title": "Ch. 323 - Travel Clothes", "url": "4503402b-9562-49bf-b09c-6c5fdaef4b67", "chapter_number": "323", "is_external": false}, {"title": "Ch. 322 - Dragonflies", "url": "62698608-0c6a-452e-adab-e7489a1183a4", "chapter_number": "322", "is_external": false}, {"title": "Ch. 321 - Shusaku Collapses", "url": "b024743f-fdad-4f21-88af-b26a1c32ec77", "chapter_number": "321", "is_external": false}, {"title": "Ch. 320 - Footpath", "url": "593e733a-9b5a-461a-a73b-ffef691240c2", "chapter_number": "320", "is_external": false}, {"title": "Ch. 319 - Something Frail", "url": "d145098b-5ef2-4ba9-aa8c-fa399dd25438", "chapter_number": "319", "is_external": false}, {"title": "Ch. 318 - Rice Seeplings", "url": "e36b3b0b-87de-4d57-851d-148ceea8523f", "chapter_number": "318", "is_external": false}, {"title": "Ch. 317 - Pure and Clear", "url": "323d6ea1-df08-448f-85aa-505e7af53f28", "chapter_number": "317", "is_external": false}, {"title": "Ch. 316 - Spring Thunder", "url": "56c25f86-e8f4-42fa-ad48-0480b08cd312", "chapter_number": "316", "is_external": false}, {"title": "Ch. 315 - When the Water Warms", "url": "a65efb9c-242c-4415-806c-cbf1e6b71133", "chapter_number": "315", "is_external": false}, {"title": "Ch. 314 - Breath", "url": "e42fd4e3-5487-4a1b-a1d6-f00f9637ef0d", "chapter_number": "314", "is_external": false}, {"title": "Ch. 313 - A Corpse and Lives", "url": "0bd4b821-a930-4a01-b772-244955b4cad7", "chapter_number": "313", "is_external": false}, {"title": "Ch. 312 - The Fool's Paddy Field", "url": "7cfa7d29-3b94-4db1-bbc8-2d934bf56dbb", "chapter_number": "312", "is_external": false}, {"title": "Ch. 311 - A Lively Life", "url": "5b95374f-f28b-4a27-b8ee-b6505e4c1894", "chapter_number": "311", "is_external": false}, {"title": "Ch. 310 - Late Autumn", "url": "bef7dfa9-289d-4496-9e33-f9465c521d6f", "chapter_number": "310", "is_external": false}, {"title": "Ch. 309 - Earth", "url": "d757d104-1b75-49df-ba5f-b71c318f565a", "chapter_number": "309", "is_external": false}, {"title": "Ch. 308 - Locusts", "url": "86e77df4-b287-4d17-ab24-5b37cc9c6aa9", "chapter_number": "308", "is_external": false}, {"title": "Ch. 307 - The Tip", "url": "76477ba6-6e9b-4f95-b502-e77999d56a35", "chapter_number": "307", "is_external": false}, {"title": "Ch. 306 - Infinite Smiles", "url": "2c580bdd-0b72-4734-8c11-8a47d7525c7f", "chapter_number": "306", "is_external": false}, {"title": "Ch. 305 - The Water Path", "url": "f6af3934-9dec-44e1-83c7-c0199d380bfe", "chapter_number": "305", "is_external": false}, {"title": "Ch. 304 - Overflowing Water", "url": "da812103-25c5-40ab-85cb-49038bfd35db", "chapter_number": "304", "is_external": false}, {"title": "Ch. 303 - Rainy Soil", "url": "dde72257-bcc9-44f5-915a-a94aa3b7b1e2", "chapter_number": "303", "is_external": false}, {"title": "Ch. 302 - Children of the Earth", "url": "a8a3451e-c995-4683-9f25-a937c60d09ce", "chapter_number": "302", "is_external": false}, {"title": "Ch. 301 - At the End of the Jorney", "url": "30d84d66-3fa5-4368-ab88-75bef0e57238", "chapter_number": "301", "is_external": false}, {"title": "Ch. 300 - The Future of Our Fiefdom", "url": "5f1ec5fd-97ca-4d16-a140-13aff40e6a10", "chapter_number": "300", "is_external": false}, {"title": "Ch. 299 - Mogura", "url": "fb3a08e4-f7cc-43d3-93f8-ec1574db845c", "chapter_number": "299", "is_external": false}, {"title": "Ch. 298 - Instructor", "url": "7af2ca7d-3e9c-4139-8ec0-42ca8431ad84", "chapter_number": "298", "is_external": false}, {"title": "Ch. 297 - Too Flashy", "url": "04588cc8-2436-4a1b-9fdb-c2d2269bc814", "chapter_number": "297", "is_external": false}, {"title": "Ch. 296 - Kojirou's City", "url": "02696f3a-da65-4050-afbb-c8674357faf4", "chapter_number": "296", "is_external": false}, {"title": "Ch. 295 - Paper Balloon", "url": "2eced732-8f39-4bc3-9075-5e462a96ede6", "chapter_number": "295", "is_external": false}, {"title": "Ch. 294 - Give It Up", "url": "be3c1807-98a0-47a4-995f-3956700a8d59", "chapter_number": "294", "is_external": false}, {"title": "Ch. 293 - Haze", "url": "1200fc9b-bb28-419f-8a5f-fe32d1ff2183", "chapter_number": "293", "is_external": false}, {"title": "Ch. 292 - Profile", "url": "0805daad-fdec-4e71-8250-410cc3d08525", "chapter_number": "292", "is_external": false}, {"title": "Ch. 291 - 7 Years", "url": "2f818391-1eef-4e8c-a848-a226ec8ea010", "chapter_number": "291", "is_external": false}, {"title": "Ch. 290 - The World of People", "url": "c11f946b-d6b4-4f27-b2bf-cf81e0Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"mal_id": 121496, "url": "https://myanimelist.net/manga/121496/Solo_Leveling", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/3/222295.jpg", "small_image_url": "https://myanimelist.net/images/manga/3/222295t.jpg", "large_image_url": "https://myanimelist.net/images/manga/3/222295l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/3/222295.webp", "small_image_url": "https://myanimelist.net/images/manga/3/222295t.webp", "large_image_url": "https://myanimelist.net/images/manga/3/222295l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Solo Leveling"}, {"type": "Synonym", "title": "Na Honjaman Level Up"}, {"type": "Synonym", "title": "I Level Up Alone"}, {"type": "Japanese", "title": "\ub098 \ud63c\uc790\ub9cc \ub808\ubca8\uc5c5"}, {"type": "English", "title": "Solo Leveling"}], "title": "Solo Leveling", "title_english": "Solo Leveling", "title_japanese": "\ub098 \ud63c\uc790\ub9cc \ub808\ubca8\uc5c5", "title_synonyms": ["Na Honjaman Level Up", "I Level Up Alone"], "type": "Manhwa", "chapters": 201, "volumes": null, "status": "Finished", "publishing": false, "published": {"from": "2018-03-04T00:00:00+00:00", "to": "2023-05-31T00:00:00+00:00", "prop": {"from": {"day": 4, "month": 3, "year": 2018}, "to": {"day": 31, "month": 5, "year": 2023}}, "string": "Mar 4, 2018 to May 31, 2023"}, "score": 8.58, "scored": 8.58, "scored_by": 366082, "rank": 122, "popularity": 5, "members": 613063, "favorites": 47318, "synopsis": "Ten years ago, \"the Gate\" appeared and connected the real world with the realm of magic and monsters. To combat these vile beasts, ordinary people received superhuman powers and became known as \"Hunters.\" Twenty-year-old Sung Jin-Woo is one such Hunter, but he is known as the \"World's Weakest,\" owing to his pathetic power compared to even a measly E-Rank. Still, he hunts monsters tirelessly in low-rank Gates to pay for his mother's medical bills. \n\nHowever, this miserable lifestyle changes when Jin-Woo\u2014believing himself to be the only one left to die in a mission gone terribly wrong\u2014awakens in a hospital three days later to find a mysterious screen floating in front of him. This \"Quest Log\" demands that Jin-Woo completes an unrealistic and intense training program, or face an appropriate penalty. Initially reluctant to comply because of the quest's rigor, Jin-Woo soon finds that it may just transform him into one of the world's most fearsome Hunters. \n\n[Written by MAL Rewrite]", "background": "Solo Leveling is a webtoon series which has been officially published in book format by D&C Media (\ub514\uc564\uc528\ubbf8\ub514\uc5b4) since September 26, 2019. It originally ended with 179 chapters on December 29, 2021, and side stories began serialization on January 20, 2023. The side stories are drawn by Disciples after the death of original artist Sung-rak Jang. The series has been published digitally in English by Tappytoon since June 4, 2020, and in print by Yen Press since March 2, 2021. It has also been published in French by Delcourt since April 7, 2021; and in Italian by Edizioni Star Comics since April 21, 2021.", "authors": [{"mal_id": 49667, "type": "people", "name": "Chugong", "url": "https://myanimelist.net/people/49667/Chugong"}, {"mal_id": 49668, "type": "people", "name": "Jang, Sung-rak", "url": "https://myanimelist.net/people/49668/Sung-rak_Jang"}, {"mal_id": 63820, "type": "people", "name": "Disciples", "url": "https://myanimelist.net/people/63820/Disciples"}], "serializations": [{"mal_id": 1439, "type": "manga", "name": "KakaoPage", "url": "https://myanimelist.net/manga/magazine/1439/KakaoPage"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 2, "type": "manga", "name": "Adventure", "url": "https://myanimelist.net/manga/genre/2/Adventure"}, {"mal_id": 10, "type": "manga", "name": "Fantasy", "url": "https://myanimelist.net/manga/genre/10/Fantasy"}], "explicit_genres": [], "themes": [{"mal_id": 50, "type": "manga", "name": "Adult Cast", "url": "https://myanimelist.net/manga/genre/50/Adult_Cast"}, {"mal_id": 83, "type": "manga", "name": "Urban Fantasy", "url": "https://myanimelist.net/manga/genre/83/Urban_Fantasy"}], "demographics": [], "relations": [{"relation": "Sequel", "entry": [{"mal_id": 172429, "type": "manga", "name": "Solo Leveling: Ragnarok", "url": "https://myanimelist.net/manga/172429/Solo_Leveling__Ragnarok"}]}, {"relation": "Adaptation", "entry": [{"mal_id": 119184, "type": "manga", "name": "Solo Leveling", "url": "https://myanimelist.net/manga/119184/Solo_Leveling"}, {"mal_id": 51321, "type": "anime", "name": "Echo", "url": "https://myanimelist.net/anime/51321/Echo"}, {"mal_id": 52299, "type": "anime", "name": "Ore dake Level Up na Ken", "url": "https://myanimelist.net/anime/52299/Ore_dake_Level_Up_na_Ken"}, {"mal_id": 59841, "type": "anime", "name": "Ore dake Level Up na Ken: ReAwakening", "url": "https:/Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mangadex": [{"title": "Ch. 383 - Anadi-avidya", "url": "8ceadc31-e41e-4038-8aef-d002aab344a5", "chapter_number": "383", "is_external": false}, {"title": "Ch. 382 - Burial Chamber", "url": "5dc17107-dfb6-4bca-97c7-a8d9634afb86", "chapter_number": "382", "is_external": false}, {"title": "Ch. 381 - A Crescent Moon Shines on the Exile's Back", "url": "b9507b33-3330-4293-b6f1-939696e9e0f1", "chapter_number": "381", "is_external": false}, {"title": "Ch. 380 - Shadows Die Twice", "url": "099594d4-a9c2-403d-9ad3-1865da115268", "chapter_number": "380", "is_external": false}, {"title": "Ch. 379 - Those Who Dance Through the Depths of Hell", "url": "a5170650-08a9-4083-96bd-219d3d16194d", "chapter_number": "379", "is_external": false}, {"title": "Ch. 378 - Invited Assassin", "url": "8bf9c7c2-4566-43df-b8b6-57fba74416ec", "chapter_number": "378", "is_external": false}, {"title": "Ch. 377 - The Viper in His Bosom", "url": "a9de9bd2-d726-488d-ad9f-cfb3ed607204", "chapter_number": "377", "is_external": false}, {"title": "Ch. 376 - Sea's quivering surface and calamitous war's shadow", "url": "8a19a805-94e1-456f-9ec2-d26b8691712e", "chapter_number": "376", "is_external": false}, {"title": "Ch. 375 - Dawn Breaks on the Unyielding Fog of Night", "url": "43e03af7-bcb6-42a5-a943-23fff07537a2", "chapter_number": "375", "is_external": false}, {"title": "Ch. 374 - Is the sleeping black beast but biding its time?", "url": "838e7e74-75dc-48cd-b9e1-7fc6535ef62d", "chapter_number": "374", "is_external": false}, {"title": "Ch. 373 - Rusted Iron Bands Unable To Push On", "url": "960a0883-11e2-4c51-9ae2-d44e475aee5b", "chapter_number": "373", "is_external": false}, {"title": "Ch. 372 - The Red Raven Sleeps in the Birdcage", "url": "ddd36c60-e3fa-4cc1-82ca-859c7a71eb61", "chapter_number": "372", "is_external": false}, {"title": "Ch. 371 - A Dying Light in the Oppressive Dark Night", "url": "c9e27928-258a-4a13-841b-56997de30d50", "chapter_number": "371", "is_external": false}, {"title": "Ch. 370 - Refugees from the Western Sea", "url": "9bdf19d6-20f2-4e88-b04f-7c8bfea83162", "chapter_number": "370", "is_external": false}, {"title": "Ch. 369 - Fading Isle", "url": "1de98fa9-a8ec-422d-8827-3160637233d9", "chapter_number": "369", "is_external": false}, {"title": "Ch. 368 - The Worm-Eaten", "url": "41072f7b-6c7c-4fad-9dd1-13d7e4961052", "chapter_number": "368", "is_external": false}, {"title": "Ch. 367 - Mists Fade as Cherry Blossoms Fall", "url": "cc3eb14a-ae0d-4cc4-81c0-c5b5df25572f", "chapter_number": "367", "is_external": false}, {"title": "Ch. 366 - The Maelstrom's Eye", "url": "eb54af7a-923f-4a9a-863c-63732f7692f8", "chapter_number": "366", "is_external": false}, {"title": "Ch. 365 - Waning Moon's Quietude", "url": "a1dce8b1-8abd-44d0-b55e-78a1176f8e6c", "chapter_number": "365", "is_external": false}, {"title": "Ch. 364 - A Tear Like Morning Dew", "url": "662a4e07-2029-4f83-861a-8fb4e09ebb43", "chapter_number": "364", "is_external": false}, {"title": "Ch. 363 - Leaping Monkey", "url": "4ca52f70-c1d3-495d-9c34-aa5668cbcae8", "chapter_number": "363", "is_external": false}, {"title": "Ch. 362 - Illusive Death", "url": "361f763c-14ea-4a95-a998-888073fd3c06", "chapter_number": "362", "is_external": false}, {"title": "Ch. 361 - Crevice", "url": "9b577300-55b8-4b76-8fc4-08f8f610854f", "chapter_number": "361", "is_external": false}, {"title": "Ch. 360 - Cherry Tree Garden", "url": "bf0df76c-7279-419b-b626-9e364de3c1c9", "chapter_number": "360", "is_external": false}, {"title": "Ch. 359 - A Wall", "url": "d0c34c6a-9a5d-4783-b5a3-dc3d11799de7", "chapter_number": "359", "is_external": false}, {"title": "Ch. 358 - Dawn of an Empire", "url": "71e2ba9e-4bc4-4bf4-a5a9-d0b83aa96c9e", "chapter_number": "358", "is_external": false}, {"title": "Ch. 357 - Triumphant Return by Dawn", "url": "64410237-9063-43d1-bb36-516bfa8e456f", "chapter_number": "357", "is_external": false}, {"title": "Ch. 356.2 - J\u00f6tunn (Part Two)", "url": "eb6597cc-40e6-4e32-a8c1-760e5b9f761d", "chapter_number": "356.2", "is_external": false}, {"title": "Ch. 356.1 - J\u00f6tunn (Part One)", "url": "93680817-4505-4b82-9ade-1587844fae15", "chapter_number": "356.1", "is_external": false}, {"title": "Ch. 355 - Beneath Sun-Dappled Trees", "url": "e65dbb94-be3f-43fa-8c27-6e34d188fd08", "chapter_number": "355", "is_external": false}, {"title": "Ch. 354 - Awakening", "url": "c8ce7885-6c51-40ac-898e-1d58de7ddc91", "chapter_number": "354", "is_external": false}, {"title": "Ch. 353 - The Final Fragment", "url": "4383180d-0921-466e-aff8-106cfd5a2055", "chapter_number": "353", "is_external": false}, {"title": "Ch. 352 - The Cause", "url": "4c64ee30-4e3a-487d-bca0-0d79a13eef9d", "chapter_number": "352", "is_external": false}, {"title": "Ch. 351 - Forest of Corpses and Needling Pines", "url": "cddbee09-777c-4c44-9e58-ed378ee0e034", "chapter_number": "351", "is_external": false}, {"title": "Ch. 350 - Memory Fragments", "url": "67e4adab-1863-4211-b5b0-d654cbc96c4f", "chapter_number": "Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mal_id": 23390, "url": "https://myanimelist.net/manga/23390/Shingeki_no_Kyojin", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/2/37846.jpg", "small_image_url": "https://myanimelist.net/images/manga/2/37846t.jpg", "large_image_url": "https://myanimelist.net/images/manga/2/37846l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/2/37846.webp", "small_image_url": "https://myanimelist.net/images/manga/2/37846t.webp", "large_image_url": "https://myanimelist.net/images/manga/2/37846l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Shingeki no Kyojin"}, {"type": "Japanese", "title": "\u9032\u6483\u306e\u5de8\u4eba"}, {"type": "English", "title": "Attack on Titan"}, {"type": "German", "title": "Attack on Titan"}, {"type": "Spanish", "title": "Ataque a los titanes"}, {"type": "French", "title": "L'Attaque des Titans"}], "title": "Shingeki no Kyojin", "title_english": "Attack on Titan", "title_japanese": "\u9032\u6483\u306e\u5de8\u4eba", "title_synonyms": [], "type": "Manga", "chapters": 141, "volumes": 34, "status": "Finished", "publishing": false, "published": {"from": "2009-09-09T00:00:00+00:00", "to": "2021-04-09T00:00:00+00:00", "prop": {"from": {"day": 9, "month": 9, "year": 2009}, "to": {"day": 9, "month": 4, "year": 2021}}, "string": "Sep 9, 2009 to Apr 9, 2021"}, "score": 8.56, "scored": 8.56, "scored_by": 430199, "rank": 134, "popularity": 4, "members": 691984, "favorites": 69788, "synopsis": "Hundreds of years ago, horrifying creatures which resembled humans appeared. These mindless, towering giants, called Titans, proved to be an existential threat, as they preyed on whatever humans they could find in order to satisfy a seemingly unending appetite. Unable to effectively combat the Titans, mankind was forced to barricade themselves within large walls surrounding what may very well be humanity's last safe haven in the world.\n\nIn the present day, life within the walls has finally found peace, since the residents have not dealt with Titans for many years. Eren Yeager, Mikasa Ackerman, and Armin Arlert are three young children who dream of experiencing all that the world has to offer, having grown up hearing stories of the wonders beyond the walls. But when the state of tranquility is suddenly shattered by the attack of a massive 60-meter Titan, they quickly learn just how cruel the world can be. On that day, Eren makes a promise to himself that he will do whatever it takes to eradicate every single Titan off the face of the Earth, with the hope that one day, humanity will once again be able to live outside the walls without fear.\n\n[Written by MAL Rewrite]", "background": "Shingeki no Kyojin won the Kodansha Manga Award in the shounen category in 2011. It was nominated for both the 16th and 18th annual Tezuka Osamu Cultural Prize as well as the fourth Manga Taishou Award. The series was the second highest-selling manga series of 2013, with 15,933,801 copies sold. The series was published in English as Attack on Titan by Kodansha Comics USA from June 19, 2012, to October 19, 2021. The publisher also released the manga as large-size 5-in-1 omnibus editions with the subtitle Colossal Edition, and as 3-in-1 omnibus volumes. Kodansha Comics USA's English release won the 2014 Harvey Award for Best American Edition of Foreign Material. Crunchyroll simulpublished the manga digitally. The manga has also been published in French by Pika \u00c9dition; in Italian by Planet Manga; in German by Carlsen Comics; in Spanish by Norma Editorial; in Portuguese by Panini Comics; in Polish by Japonica Polonica Fantastica; in Russian by Azbooka; and in Romanian by Nezumi. It has received several adaptations. Two guidebooks titled Inside and Outside were released on April 9, 2013, and September 9, 2013, respectively. Both feature concept art, character profiles, and interviews. The guidebooks were combined into one and published by Kodansha Comics USA on September 16, 2014. A drama CD created by the anime's staff was included in the January 2014 issue of the magazine. A two-part live-action film directed by Shinji Higuchi was released in Japanese theaters on August 1, 2015, and September 15, 2015, respectively.", "authors": [{"mal_id": 11705, "type": "people", "name": "Isayama, Hajime", "url": "https://myanimelist.net/people/11705/Hajime_Isayama"}], "serializations": [{"mal_id": 450, "type": "manga", "name": "Bessatsu Shounen Magazine", "url": "https://myanimelist.net/manga/magazine/450/Bessatsu_Shounen_Magazine"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 46, "type": "manga", "name": "Award Winning", "url": "https://myanimelist.net/manga/genre/46/Award_Winning"}, {"mal_id": 8, "type": "manga", "name": "Drama", "url": "https://myanimelist.net/manga/genre/8/Drama"}, {"mal_id": 45, "type": "manga", "name": "Suspense", "url": "https://myanimelist.net/manga/genre/45/SuspenseLine truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 153288, "mal_id": 52588, "title_romaji": "Kaijuu 8-gou", "title_english": "Kaiju No. 8", "title_native": "\u602a\u7363\uff18\u53f7", "format": "TV", "start_date": {"year": 2024, "month": 4, "day": 13}, "season": "SPRING", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8", "_start_tuple": [2024, 4, 13], "is_final_season": false}, {"anilist_id": 178754, "mal_id": 59177, "title_romaji": "Kaijuu 8-gou 2nd Season", "title_english": "Kaiju No. 8 Season 2", "title_native": "\u602a\u7363\uff18\u53f7 \u7b2c\uff12\u671f", "format": "TV", "start_date": {"year": 2025, "month": 7, "day": 19}, "season": "SUMMER", "season_year": 2025, "episodes": 11, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8 Season 2", "_start_tuple": [2025, 7, 19], "is_final_season": false}, {"anilist_id": 204362, "mal_id": 63136, "title_romaji": "Kaijuu 8-gou: Kanketsu-hen", "title_english": null, "title_native": "\u602a\u7363\uff18\u53f7 \u5b8c\u7d50\u7de8", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaijuu 8-gou: Kanketsu-hen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 179998, "mal_id": 59489, "title_romaji": "Kaijuu 8-gou Movie", "title_english": "Kaiju No. 8: Mission Recon", "title_native": "\u602a\u73638\u53f7 \u7b2c1\u671f\u7dcf\u96c6\u7de8", "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}, "season": "WINTER", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaiju No. 8: Mission Recon", "_start_tuple": [2025, 3, 28], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Kaiju No. 8", "mal_id": 52588, "anilist_id": 153288, "start_date": {"year": 2024, "month": 4, "day": 13}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Kaiju No. 8 Season 2", "mal_id": 59177, "anilist_id": 178754, "start_date": {"year": 2025, "month": 7, "day": 19}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Kaijuu 8-gou: Kanketsu-hen", "mal_id": 63136, "anilist_id": 204362, "format": null, "start_date": {"year": null, "month": null, "day": null}}, {"title": "Kaiju No. 8: Mission Recon", "mal_id": 59489, "anilist_id": 179998, "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}}]}, "_timestamp": 1774929755.5327868}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 178754, "mal_id": 59177, "title_romaji": "Kaijuu 8-gou 2nd Season", "title_english": "Kaiju No. 8 Season 2", "title_native": "\u602a\u7363\uff18\u53f7 \u7b2c\uff12\u671f", "format": "TV", "start_date": {"year": 2025, "month": 7, "day": 19}, "season": "SUMMER", "season_year": 2025, "episodes": 11, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8 Season 2", "_start_tuple": [2025, 7, 19], "is_final_season": false}, {"anilist_id": 204362, "mal_id": 63136, "title_romaji": "Kaijuu 8-gou: Kanketsu-hen", "title_english": null, "title_native": "\u602a\u7363\uff18\u53f7 \u5b8c\u7d50\u7de8", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaijuu 8-gou: Kanketsu-hen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 179998, "mal_id": 59489, "title_romaji": "Kaijuu 8-gou Movie", "title_english": "Kaiju No. 8: Mission Recon", "title_native": "\u602a\u73638\u53f7 \u7b2c1\u671f\u7dcf\u96c6\u7de8", "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}, "season": "WINTER", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaiju No. 8: Mission Recon", "_start_tuple": [2025, 3, 28], "is_final_season": false}, {"anilist_id": 153288, "mal_id": 52588, "title_romaji": "Kaijuu 8-gou", "title_english": "Kaiju No. 8", "title_native": "\u602a\u7363\uff18\u53f7", "format": "TV", "start_date": {"year": 2024, "month": 4, "day": 13}, "season": "SPRING", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8", "_start_tuple": [2024, 4, 13], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Kaiju No. 8", "mal_id": 52588, "anilist_id": 153288, "start_date": {"year": 2024, "month": 4, "day": 13}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Kaiju No. 8 Season 2", "mal_id": 59177, "anilist_id": 178754, "start_date": {"year": 2025, "month": 7, "day": 19}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Kaijuu 8-gou: Kanketsu-hen", "mal_id": 63136, "anilist_id": 204362, "format": null, "start_date": {"year": null, "month": null, "day": null}}, {"title": "Kaiju No. 8: Mission Recon", "mal_id": 59489, "anilist_id": 179998, "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}}]}, "_timestamp": 1774929794.0022862}
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mal_id": 656, "url": "https://myanimelist.net/manga/656/Vagabond", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/1/259070.jpg", "small_image_url": "https://myanimelist.net/images/manga/1/259070t.jpg", "large_image_url": "https://myanimelist.net/images/manga/1/259070l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/1/259070.webp", "small_image_url": "https://myanimelist.net/images/manga/1/259070t.webp", "large_image_url": "https://myanimelist.net/images/manga/1/259070l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Vagabond"}, {"type": "Japanese", "title": "\u30d0\u30ac\u30dc\u30f3\u30c9"}, {"type": "English", "title": "Vagabond"}], "title": "Vagabond", "title_english": "Vagabond", "title_japanese": "\u30d0\u30ac\u30dc\u30f3\u30c9", "title_synonyms": [], "type": "Manga", "chapters": 327, "volumes": 37, "status": "On Hiatus", "publishing": false, "published": {"from": "1998-09-03T00:00:00+00:00", "to": "2015-05-21T00:00:00+00:00", "prop": {"from": {"day": 3, "month": 9, "year": 1998}, "to": {"day": 21, "month": 5, "year": 2015}}, "string": "Sep 3, 1998 to May 21, 2015"}, "score": 9.27, "scored": 9.27, "scored_by": 174602, "rank": 3, "popularity": 12, "members": 455240, "favorites": 48512, "synopsis": "In 16th-century Japan, Shinmen Takezou is a wild, rough young man in both his appearance and his actions. His aggressive nature has won him the collective reproach and fear of his village, leading him and his best friend, Matahachi Honiden, to run away in search of something grander than provincial life. The pair enlist in the Toyotomi army, yearning for glory\u2014but when the Toyotomi suffer a crushing defeat at the hands of the Tokugawa Clan at the Battle of Sekigahara, the friends barely make it out alive.\n\nAfter the two are separated, Shinmen returns home on a self-appointed mission to notify the Hon'iden family of Matahachi's survival. He instead finds himself a wanted criminal, framed for his friend's supposed murder based on his history of violence. Upon being captured, he is strung up on a tree and left to die. An itinerant monk, the distinguished Takuan Soho, takes pity on the \"devil child,\" secretly freeing Shinmen and christening him with a new name to avoid pursuit by the authorities: Musashi Miyamoto.\n\nVagabond is the fictitious retelling of the life of one of Japan's most renowned swordsmen, the \"Sword Saint\" Musashi Miyamoto\u2014his rise from a swordsman with no desire other than to become \"Invincible Under the Heavens\" to an enlightened warrior who slowly learns of the importance of close friends, self-reflection, and life itself.\n\n[Written by MAL Rewrite]", "background": "Vagabond won the Japan Media Arts Festival Manga Grand Prize and the 24th Kodansha Manga Award in the General category in 2000. The series won the Tezuka Osamu Cultural Prize in 2002 and has had over 82 million copies sold worldwide. Vagabond was published in English by VIZ Media under the VIZ Signature imprint from April 5, 2002, to April 21, 2015, and in large 3-in-1 omnibuses under the VIZBIG imprint from September 16, 2008, to April 21, 2015. The English release got Takehiko Inoue a nomination for the Eisner Award in the Best Writer/Artist category in 2003. It was also published in Brazilian Portuguese by Panini Comics from February 2016 to February 2019.", "authors": [{"mal_id": 1911, "type": "people", "name": "Inoue, Takehiko", "url": "https://myanimelist.net/people/1911/Takehiko_Inoue"}, {"mal_id": 5760, "type": "people", "name": "Yoshikawa, Eiji", "url": "https://myanimelist.net/people/5760/Eiji_Yoshikawa"}], "serializations": [{"mal_id": 72, "type": "manga", "name": "Morning", "url": "https://myanimelist.net/manga/magazine/72/Morning"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 2, "type": "manga", "name": "Adventure", "url": "https://myanimelist.net/manga/genre/2/Adventure"}, {"mal_id": 46, "type": "manga", "name": "Award Winning", "url": "https://myanimelist.net/manga/genre/46/Award_Winning"}], "explicit_genres": [], "themes": [{"mal_id": 13, "type": "manga", "name": "Historical", "url": "https://myanimelist.net/manga/genre/13/Historical"}, {"mal_id": 21, "type": "manga", "name": "Samurai", "url": "https://myanimelist.net/manga/genre/21/Samurai"}], "demographics": [{"mal_id": 41, "type": "manga", "name": "Seinen", "url": "https://myanimelist.net/manga/genre/41/Seinen"}], "relations": [], "external": [{"name": "Official Site", "url": "http://morningmanga.com/lineup/15"}, {"name": "Wikipedia", "url": "https://en.wikipedia.org/wiki/Vagabond_(manga)"}, {"name": "Wikipedia", "url": "https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%AC%E3%83%9C%E3%83%B3%E3%83%89"}]}, "_timestamp": 1775437997.008553}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"comix": [{"title": "Chapter 6", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874900", "chapter_number": "6", "is_external": false}, {"title": "Chapter 5", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874755", "chapter_number": "5", "is_external": false}, {"title": "Chapter 4", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874561", "chapter_number": "4", "is_external": false}, {"title": "Chapter 3", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874447", "chapter_number": "3", "is_external": false}, {"title": "Chapter 2", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874343", "chapter_number": "2", "is_external": false}, {"title": "Chapter 1", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874222", "chapter_number": "1", "is_external": false}]}, "_timestamp": 1775087084.836826}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [], "season_groups": [], "extras": []}, "_timestamp": 1774915035.3131928}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 113417, "mal_id": 40746, "title_romaji": "Overflow", "title_english": "Overflow", "title_native": "\u304a\u30fc\u3070\u30fc\u3075\u308d\u3049", "format": "ONA", "start_date": {"year": 2020, "month": 1, "day": 6}, "season": "WINTER", "season_year": 2020, "episodes": 8, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Overflow", "_start_tuple": [2020, 1, 6], "is_final_season": false}], "season_groups": [], "extras": [{"title": "Overflow", "mal_id": 40746, "anilist_id": 113417, "format": "ONA", "start_date": {"year": 2020, "month": 1, "day": 6}}]}, "_timestamp": 1774963942.841442}
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mangadex": [{"title": "Ch. 232 - Thank You, Chainsaw Man", "url": "48bd1595-f11e-47b1-b5a0-1a0b767a2457", "chapter_number": "232", "is_external": false}, {"title": "Ch. 231 - Goodbye, Pochita", "url": "1c0519ab-4d46-46c1-b9de-e9b173613cb4", "chapter_number": "231", "is_external": false}, {"title": "Ch. 230 - Sex and Selection", "url": "ce88a93f-4a3b-435f-8bf0-8852bee0948c", "chapter_number": "230", "is_external": false}, {"title": "Ch. 97 - Love, Love, Chainsaw.", "url": "77fc6e02-a381-411d-ace6-4c00deacadb4", "chapter_number": "97", "is_external": false}, {"title": "Ch. 96 - This Flavor", "url": "7259bb55-bb83-4dd8-ac7e-f9c4d7472ce6", "chapter_number": "96", "is_external": false}, {"title": "Ch. 95 - Chainsawman vs The Devil of Domination", "url": "bf3dd196-c301-40c9-9338-109159c4fa41", "chapter_number": "95", "is_external": false}, {"title": "Ch. 94 - Chainsawman vs The Weapon People", "url": "33d106dd-7500-473a-9330-cd6bb75f8d8b", "chapter_number": "94", "is_external": false}, {"title": "Ch. 93 - You and Crappy Movies", "url": "5effe43c-3cba-45a3-b8b9-7a1f7bb7d8ff", "chapter_number": "93", "is_external": false}, {"title": "Ch. 92 - Zombie, Blood, and Chainsaw", "url": "d70dc082-7b8c-48ea-87bd-2fa8beaf6fb6", "chapter_number": "92", "is_external": false}, {"title": "Ch. 91 - Power, Power, Power", "url": "78b59bea-4154-4f00-a58f-cf0a2dc7282d", "chapter_number": "91", "is_external": false}, {"title": "Ch. 90 - Super Power", "url": "65562e93-3ab7-44b7-b64d-8cd22ea0bda5", "chapter_number": "90", "is_external": false}, {"title": "Ch. 89 - Go Chainsawman", "url": "19086fe2-d9c2-47ea-b8e6-f62f0d60a713", "chapter_number": "89", "is_external": false}, {"title": "Ch. 88 - Chainsaw Star", "url": "40f7d9ef-05d8-48fc-96d1-cb53513f7e13", "chapter_number": "88", "is_external": false}, {"title": "Ch. 87 - Chainsawman vs The Weaponmasters of Terror", "url": "aa3b2319-b460-4cab-a2d5-f2471cb19215", "chapter_number": "87", "is_external": false}, {"title": "Ch. 86 - Chainsaw Date", "url": "cff31d76-bf9e-442b-a573-fc4d42ad83b5", "chapter_number": "86", "is_external": false}, {"title": "Ch. 85 - Blood and Guts Feelin' So Good", "url": "80f38b74-62ba-44a0-bc29-cf03aa27af9d", "chapter_number": "85", "is_external": false}, {"title": "Ch. 84 - Hell's Hero", "url": "34bc77ad-d073-4c19-b0a4-495aa471f2c2", "chapter_number": "84", "is_external": false}, {"title": "Ch. 83 - Death, Resurrection, Chainsaw", "url": "eb48c353-0235-4d9d-a3e5-9bc942e83730", "chapter_number": "83", "is_external": false}, {"title": "Ch. 82 - A Balanced Breakfast", "url": "eb16a43a-21f5-4c97-b890-7e209f953528", "chapter_number": "82", "is_external": false}, {"title": "Ch. 81 - Paw", "url": "44f8e69d-394f-4db4-a4b8-6f333acb27f1", "chapter_number": "81", "is_external": false}, {"title": "Ch. 80 - How A Dog Feels", "url": "fe85cd06-0f6c-4411-b02f-73cb15c6e379", "chapter_number": "80", "is_external": false}, {"title": "Ch. 79 - Catch", "url": "da550ea8-1939-446b-bab1-87f956a995ed", "chapter_number": "79", "is_external": false}, {"title": "Ch. 78 - Snowball Fight", "url": "0ad74d10-4e4a-4821-b846-2013903d1c1f", "chapter_number": "78", "is_external": false}, {"title": "Ch. 77 - Ding Dong Ding Dong", "url": "f13132b0-66ee-4fe7-975d-87e5b24c2f29", "chapter_number": "77", "is_external": false}, {"title": "Ch. 76 - Don't Open It", "url": "715c9b1c-ea8f-4b2e-a7d8-0d8617083180", "chapter_number": "76", "is_external": false}, {"title": "Ch. 75 - 9/12", "url": "1d9b99cc-ef5f-4718-bf75-5e1aaf432a5e", "chapter_number": "75", "is_external": false}, {"title": "Ch. 74 - The Whispering of the Waves", "url": "37ac0970-a3ee-4dda-baac-8bff9dfe469c", "chapter_number": "74", "is_external": false}, {"title": "Ch. 73 - The Everyday Comes To An End", "url": "8a264bf6-df07-490c-8a32-1f032f4b8fed", "chapter_number": "73", "is_external": false}, {"title": "Ch. 72 - Everyone Together", "url": "e976e735-d873-4654-bcee-69203e8fd038", "chapter_number": "72", "is_external": false}, {"title": "Ch. 71 - Bath", "url": "56dfba01-63dd-4256-9133-847c16b3eb6a", "chapter_number": "71", "is_external": false}, {"title": "Ch. 70 - Pluck", "url": "c11d1634-8c2d-4225-8261-ca8f75194e4b", "chapter_number": "70", "is_external": false}, {"title": "Ch. 69 - Shining Power", "url": "4f4ef084-c2c7-48a6-b2a7-bc5ee96ecb15", "chapter_number": "69", "is_external": false}, {"title": "Ch. 68 - Dark Power", "url": "375c96f4-0687-4f71-ae55-7f66cc7302e3", "chapter_number": "68", "is_external": false}, {"title": "Ch. 67 - The first devil hunter", "url": "0bc54881-1e30-4245-a88a-54497498b6a1", "chapter_number": "67", "is_external": false}, {"title": "Ch. 66 - Woof!", "url": "de5bb263-a5c1-44d6-b276-a34b344887a7", "chapter_number": "66", "is_external": false}, {"title": "Ch. 65 - The Devil of the Dark", "url": "daa6df76-5c06-494a-9672-4326d3c9093d", "chapter_number": "65", "is_external": false}, {"title": "Ch. 64 - Welcome to Hell", "url": "05e61e3f-7201-4e3e-ab0a-07e0a83c9f93", "chapter_number": "64", "is_external"Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [], "season_groups": [], "extras": []}, "_timestamp": 1774915405.991542}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 151807, "mal_id": 52299, "title_romaji": "Ore dake Level Up na Ken", "title_english": "Solo Leveling", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6", "format": "TV", "start_date": {"year": 2024, "month": 1, "day": 7}, "season": "WINTER", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Solo Leveling", "_start_tuple": [2024, 1, 7], "is_final_season": false}, {"anilist_id": 176496, "mal_id": 58567, "title_romaji": "Ore dake Level Up na Ken: Season 2 - Arise from the Shadow", "title_english": "Solo Leveling Season 2 -Arise from the Shadow-", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6 Season 2 -Arise from the Shadow-", "format": "TV", "start_date": {"year": 2025, "month": 1, "day": 5}, "season": "WINTER", "season_year": 2025, "episodes": 13, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Solo Leveling Season 2 -Arise from the Shadow-", "_start_tuple": [2025, 1, 5], "is_final_season": false}, {"anilist_id": 184694, "mal_id": 59841, "title_romaji": "Ore dake Level Up na Ken: ReAwakening", "title_english": "Solo Leveling -ReAwakening-", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6 -ReAwakening-", "format": "MOVIE", "start_date": {"year": 2024, "month": 11, "day": 29}, "season": "FALL", "season_year": 2024, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Solo Leveling -ReAwakening-", "_start_tuple": [2024, 11, 29], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Solo Leveling", "mal_id": 52299, "anilist_id": 151807, "start_date": {"year": 2024, "month": 1, "day": 7}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Solo Leveling Season 2 -Arise from the Shadow-", "mal_id": 58567, "anilist_id": 176496, "start_date": {"year": 2025, "month": 1, "day": 5}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Solo Leveling -ReAwakening-", "mal_id": 59841, "anilist_id": 184694, "format": "MOVIE", "start_date": {"year": 2024, "month": 11, "day": 29}}]}, "_timestamp": 1775000957.808718}
|
||||
@@ -1 +1 @@
|
||||
{"payload": {"mal_id": 113138, "url": "https://myanimelist.net/manga/113138/Jujutsu_Kaisen", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/3/210341.jpg", "small_image_url": "https://myanimelist.net/images/manga/3/210341t.jpg", "large_image_url": "https://myanimelist.net/images/manga/3/210341l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/3/210341.webp", "small_image_url": "https://myanimelist.net/images/manga/3/210341t.webp", "large_image_url": "https://myanimelist.net/images/manga/3/210341l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Jujutsu Kaisen"}, {"type": "Synonym", "title": "Sorcery Fight"}, {"type": "Japanese", "title": "\u546a\u8853\u5efb\u6226"}, {"type": "English", "title": "Jujutsu Kaisen"}], "title": "Jujutsu Kaisen", "title_english": "Jujutsu Kaisen", "title_japanese": "\u546a\u8853\u5efb\u6226", "title_synonyms": ["Sorcery Fight"], "type": "Manga", "chapters": 272, "volumes": 30, "status": "Finished", "publishing": false, "published": {"from": "2018-03-05T00:00:00+00:00", "to": "2024-09-30T00:00:00+00:00", "prop": {"from": {"day": 5, "month": 3, "year": 2018}, "to": {"day": 30, "month": 9, "year": 2024}}, "string": "Mar 5, 2018 to Sep 30, 2024"}, "score": 8.19, "scored": 8.19, "scored_by": 260022, "rank": 522, "popularity": 9, "members": 505601, "favorites": 34102, "synopsis": "Hidden in plain sight, an age-old conflict rages on. Supernatural monsters known as Curses terrorize humanity from the shadows, and powerful humans known as Jujutsu sorcerers use mystical arts to exterminate them. When high school student Yuuji Itadori finds a dried-up finger of the legendary Curse Sukuna Ryoumen, he suddenly finds himself joining this bloody conflict.\n\nAttacked by a Curse attracted to the finger's power, Yuuji makes a reckless decision to protect himself, gaining the power to combat Curses in the process but also unwittingly unleashing the malicious Sukuna into the world once more. Though Yuuji can control and confine Sukuna to his own body, the Jujutsu world classifies Yuuji as a dangerous, high-level Curse who must be exterminated.\n\nDetained and sentenced to death, Yuuji meets Satoru Gojou\u2014a teacher at Jujutsu High School\u2014who explains that despite his imminent execution, there is an alternative for him. Being a rare vessel to Sukuna, if Yuuji were to die, then Sukuna would perish too. Therefore, if Yuuji were to consume the many other remnants of Sukuna, then Yuuji's subsequent execution would truly eradicate the malicious demon. Taking up this chance to make the world safer and live his life for a little longer, Yuuji enrolls in Tokyo Prefectural Jujutsu High School, jumping headfirst into a harsh and unforgiving battlefield.\n\n[Written by MAL Rewrite]", "background": "As a part of the JUMP START initiative, the first three chapters of Jujutsu Kaisen were published in English in VIZ Media's digital Weekly Shonen Jump on the same day as the Japanese release. After, the series was added to the MANGA Plus simulpub service, and has been released in print by the same publisher since December 3, 2019. It has also been released in Brazilian Portuguese by Panini Comics since August 21, 2020; and in Polish by Waneko since May 5, 2021.", "authors": [{"mal_id": 47924, "type": "people", "name": "Akutami, Gege", "url": "https://myanimelist.net/people/47924/Gege_Akutami"}], "serializations": [{"mal_id": 83, "type": "manga", "name": "Shounen Jump (Weekly)", "url": "https://myanimelist.net/manga/magazine/83/Shounen_Jump_Weekly"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 37, "type": "manga", "name": "Supernatural", "url": "https://myanimelist.net/manga/genre/37/Supernatural"}], "explicit_genres": [], "themes": [{"mal_id": 23, "type": "manga", "name": "School", "url": "https://myanimelist.net/manga/genre/23/School"}], "demographics": [{"mal_id": 27, "type": "manga", "name": "Shounen", "url": "https://myanimelist.net/manga/genre/27/Shounen"}], "relations": [{"relation": "Prequel", "entry": [{"mal_id": 115710, "type": "manga", "name": "Jujutsu Kaisen 0: Tokyo Toritsu Jujutsu Koutou Senmon Gakkou", "url": "https://myanimelist.net/manga/115710/Jujutsu_Kaisen_0__Tokyo_Toritsu_Jujutsu_Koutou_Senmon_Gakkou"}]}, {"relation": "Adaptation", "entry": [{"mal_id": 38777, "type": "anime", "name": "Jujutsu Kaisen Official PVs", "url": "https://myanimelist.net/anime/38777/Jujutsu_Kaisen_Official_PVs"}, {"mal_id": 40748, "type": "anime", "name": "Jujutsu Kaisen", "url": "https://myanimelist.net/anime/40748/Jujutsu_Kaisen"}, {"mal_id": 51009, "type": "anime", "name": "Jujutsu Kaisen 2nd Season", "url": "https://myanimelist.net/anime/51009/Jujutsu_Kaisen_2nd_Season"}, {"mal_id": 59654, "type": "anime", "name": "Jujutsu Kaisen: Kaigyoku/Gyokusetsu", "url": "https://myanimelist.net/anime/59654/Jujutsu_Kaisen__Kaigyoku_Gyokusetsu"}, {"mal_id": 62392, "type": "anime", "Line truncated
|
||||
{"payload": {"mal_id": 113138, "url": "https://myanimelist.net/manga/113138/Jujutsu_Kaisen", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/3/210341.jpg", "small_image_url": "https://myanimelist.net/images/manga/3/210341t.jpg", "large_image_url": "https://myanimelist.net/images/manga/3/210341l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/3/210341.webp", "small_image_url": "https://myanimelist.net/images/manga/3/210341t.webp", "large_image_url": "https://myanimelist.net/images/manga/3/210341l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Jujutsu Kaisen"}, {"type": "Synonym", "title": "Sorcery Fight"}, {"type": "Japanese", "title": "\u546a\u8853\u5efb\u6226"}, {"type": "English", "title": "Jujutsu Kaisen"}], "title": "Jujutsu Kaisen", "title_english": "Jujutsu Kaisen", "title_japanese": "\u546a\u8853\u5efb\u6226", "title_synonyms": ["Sorcery Fight"], "type": "Manga", "chapters": 272, "volumes": 30, "status": "Finished", "publishing": false, "published": {"from": "2018-03-05T00:00:00+00:00", "to": "2024-09-30T00:00:00+00:00", "prop": {"from": {"day": 5, "month": 3, "year": 2018}, "to": {"day": 30, "month": 9, "year": 2024}}, "string": "Mar 5, 2018 to Sep 30, 2024"}, "score": 8.19, "scored": 8.19, "scored_by": 260512, "rank": 516, "popularity": 9, "members": 506292, "favorites": 34137, "synopsis": "Hidden in plain sight, an age-old conflict rages on. Supernatural monsters known as Curses terrorize humanity from the shadows, and powerful humans known as Jujutsu sorcerers use mystical arts to exterminate them. When high school student Yuuji Itadori finds a dried-up finger of the legendary Curse Sukuna Ryoumen, he suddenly finds himself joining this bloody conflict.\n\nAttacked by a Curse attracted to the finger's power, Yuuji makes a reckless decision to protect himself, gaining the power to combat Curses in the process but also unwittingly unleashing the malicious Sukuna into the world once more. Though Yuuji can control and confine Sukuna to his own body, the Jujutsu world classifies Yuuji as a dangerous, high-level Curse who must be exterminated.\n\nDetained and sentenced to death, Yuuji meets Satoru Gojou\u2014a teacher at Jujutsu High School\u2014who explains that despite his imminent execution, there is an alternative for him. Being a rare vessel to Sukuna, if Yuuji were to die, then Sukuna would perish too. Therefore, if Yuuji were to consume the many other remnants of Sukuna, then Yuuji's subsequent execution would truly eradicate the malicious demon. Taking up this chance to make the world safer and live his life for a little longer, Yuuji enrolls in Tokyo Prefectural Jujutsu High School, jumping headfirst into a harsh and unforgiving battlefield.\n\n[Written by MAL Rewrite]", "background": "As a part of the JUMP START initiative, the first three chapters of Jujutsu Kaisen were published in English in VIZ Media's digital Weekly Shonen Jump on the same day as the Japanese release. After, the series was added to the MANGA Plus simulpub service, and has been released in print by the same publisher since December 3, 2019. It has also been released in Brazilian Portuguese by Panini Comics since August 21, 2020; and in Polish by Waneko since May 5, 2021.", "authors": [{"mal_id": 47924, "type": "people", "name": "Akutami, Gege", "url": "https://myanimelist.net/people/47924/Gege_Akutami"}], "serializations": [{"mal_id": 83, "type": "manga", "name": "Shounen Jump (Weekly)", "url": "https://myanimelist.net/manga/magazine/83/Shounen_Jump_Weekly"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 37, "type": "manga", "name": "Supernatural", "url": "https://myanimelist.net/manga/genre/37/Supernatural"}], "explicit_genres": [], "themes": [{"mal_id": 23, "type": "manga", "name": "School", "url": "https://myanimelist.net/manga/genre/23/School"}], "demographics": [{"mal_id": 27, "type": "manga", "name": "Shounen", "url": "https://myanimelist.net/manga/genre/27/Shounen"}], "relations": [{"relation": "Prequel", "entry": [{"mal_id": 115710, "type": "manga", "name": "Jujutsu Kaisen 0: Tokyo Toritsu Jujutsu Koutou Senmon Gakkou", "url": "https://myanimelist.net/manga/115710/Jujutsu_Kaisen_0__Tokyo_Toritsu_Jujutsu_Koutou_Senmon_Gakkou"}]}, {"relation": "Adaptation", "entry": [{"mal_id": 38777, "type": "anime", "name": "Jujutsu Kaisen Official PVs", "url": "https://myanimelist.net/anime/38777/Jujutsu_Kaisen_Official_PVs"}, {"mal_id": 40748, "type": "anime", "name": "Jujutsu Kaisen", "url": "https://myanimelist.net/anime/40748/Jujutsu_Kaisen"}, {"mal_id": 51009, "type": "anime", "name": "Jujutsu Kaisen 2nd Season", "url": "https://myanimelist.net/anime/51009/Jujutsu_Kaisen_2nd_Season"}, {"mal_id": 59654, "type": "anime", "name": "Jujutsu Kaisen: Kaigyoku/Gyokusetsu", "url": "https://myanimelist.net/anime/59654/Jujutsu_Kaisen__Kaigyoku_Gyokusetsu"}, {"mal_id": 62392, "type": "anime", "Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 204362, "mal_id": 63136, "title_romaji": "Kaijuu 8-gou: Kanketsu-hen", "title_english": null, "title_native": "\u602a\u7363\uff18\u53f7 \u5b8c\u7d50\u7de8", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaijuu 8-gou: Kanketsu-hen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 178754, "mal_id": 59177, "title_romaji": "Kaijuu 8-gou 2nd Season", "title_english": "Kaiju No. 8 Season 2", "title_native": "\u602a\u7363\uff18\u53f7 \u7b2c\uff12\u671f", "format": "TV", "start_date": {"year": 2025, "month": 7, "day": 19}, "season": "SUMMER", "season_year": 2025, "episodes": 11, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8 Season 2", "_start_tuple": [2025, 7, 19], "is_final_season": false}, {"anilist_id": 179998, "mal_id": 59489, "title_romaji": "Kaijuu 8-gou Movie", "title_english": "Kaiju No. 8: Mission Recon", "title_native": "\u602a\u73638\u53f7 \u7b2c1\u671f\u7dcf\u96c6\u7de8", "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}, "season": "WINTER", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaiju No. 8: Mission Recon", "_start_tuple": [2025, 3, 28], "is_final_season": false}, {"anilist_id": 153288, "mal_id": 52588, "title_romaji": "Kaijuu 8-gou", "title_english": "Kaiju No. 8", "title_native": "\u602a\u7363\uff18\u53f7", "format": "TV", "start_date": {"year": 2024, "month": 4, "day": 13}, "season": "SPRING", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8", "_start_tuple": [2024, 4, 13], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Kaiju No. 8", "mal_id": 52588, "anilist_id": 153288, "start_date": {"year": 2024, "month": 4, "day": 13}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Kaiju No. 8 Season 2", "mal_id": 59177, "anilist_id": 178754, "start_date": {"year": 2025, "month": 7, "day": 19}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Kaijuu 8-gou: Kanketsu-hen", "mal_id": 63136, "anilist_id": 204362, "format": null, "start_date": {"year": null, "month": null, "day": null}}, {"title": "Kaiju No. 8: Mission Recon", "mal_id": 59489, "anilist_id": 179998, "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}}]}, "_timestamp": 1774929784.310032}
|
||||
@@ -1 +1 @@
|
||||
{"payload": {"comix": [{"title": "Final Chapter: From Here on Out", "url": "7nzg:jujutsu-kaisen:5658860", "chapter_number": "271", "is_external": false}, {"title": "A Dream's End", "url": "7nzg:jujutsu-kaisen:5658815", "chapter_number": "270", "is_external": false}, {"title": "Reflection", "url": "7nzg:jujutsu-kaisen:5658773", "chapter_number": "269", "is_external": false}, {"title": "Finale", "url": "7nzg:jujutsu-kaisen:5658740", "chapter_number": "268", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u32b3", "url": "7nzg:jujutsu-kaisen:5658708", "chapter_number": "267", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u32b2", "url": "7nzg:jujutsu-kaisen:5658672", "chapter_number": "266", "is_external": false}, {"title": "That Day", "url": "7nzg:jujutsu-kaisen:5658655", "chapter_number": "265", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u32b1", "url": "7nzg:jujutsu-kaisen:5658612", "chapter_number": "264", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325f", "url": "7nzg:jujutsu-kaisen:5658576", "chapter_number": "263", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325e", "url": "7nzg:jujutsu-kaisen:5658544", "chapter_number": "262.5", "is_external": false}, {"title": "Inhuman Makyo Shinjuku Showdown, Part 34-2", "url": "7nzg:jujutsu-kaisen:6603751", "chapter_number": "262.2", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325e", "url": "7nzg:jujutsu-kaisen:5658508", "chapter_number": "262", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325d", "url": "7nzg:jujutsu-kaisen:5658487", "chapter_number": "261", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325c", "url": "7nzg:jujutsu-kaisen:5658451", "chapter_number": "260", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325b", "url": "7nzg:jujutsu-kaisen:5658406", "chapter_number": "259", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u325a", "url": "7nzg:jujutsu-kaisen:5658352", "chapter_number": "258", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3259", "url": "7nzg:jujutsu-kaisen:5658311", "chapter_number": "257", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3258", "url": "7nzg:jujutsu-kaisen:5658259", "chapter_number": "256", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3257", "url": "7nzg:jujutsu-kaisen:5658234", "chapter_number": "255", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3256", "url": "7nzg:jujutsu-kaisen:5658211", "chapter_number": "254", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3255", "url": "7nzg:jujutsu-kaisen:5658176", "chapter_number": "253", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3254", "url": "7nzg:jujutsu-kaisen:5658144", "chapter_number": "252", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3253", "url": "7nzg:jujutsu-kaisen:5658110", "chapter_number": "251", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3252", "url": "7nzg:jujutsu-kaisen:5658086", "chapter_number": "250", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u3251", "url": "7nzg:jujutsu-kaisen:5658067", "chapter_number": "249", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u2473", "url": "7nzg:jujutsu-kaisen:5658031", "chapter_number": "248", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u2472", "url": "7nzg:jujutsu-kaisen:5657999", "chapter_number": "247", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u2471", "url": "7nzg:jujutsu-kaisen:5657949", "chapter_number": "246", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u2470", "url": "7nzg:jujutsu-kaisen:5657918", "chapter_number": "245", "is_external": false}, {"title": "The Decisive Battle in the Uninhabited, Demon-Infested Shinjuku \u246f", "url": "7nzg:jujutsu-kaisen:5657886", "chapter_number": "244", "is_external": false}, {"title": "Idiot Survivor!! ~Bounce Off the Walls~", "url": "7nzg:jujutsu-kaisen:5657868", "chapter_number": "243", "is_external": false}, {"title": "Idiot Survivor!! ~Soar Ever HighLine truncated
|
||||
{"payload": {"mangadex": [{"title": "Ch. 3 - FOR MYSELF", "url": "b46a9067-c81d-4f00-8313-81f3a15b0fa0", "chapter_number": "3", "is_external": false}, {"title": "Ch. 2 - SECRET EXECUTION", "url": "a7297785-1fe6-4e7a-9e38-497c087fc660", "chapter_number": "2", "is_external": false}, {"title": "Ch. 1 - RYOMEN SUKUNA", "url": "a1477730-c9e2-4b34-b9f9-d28d7de7b8e5", "chapter_number": "1", "is_external": false}]}, "_timestamp": 1775426194.005248}
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mangadex": [{"title": "Ch. 205 - Lives That Make the Years Shine", "url": "5cc5e8b2-57aa-4120-b8a7-955a33b5d2d8", "chapter_number": "205", "is_external": false}, {"title": "Ch. 204 - A World Without Demons", "url": "3ff4277c-8300-4f19-8405-97cfd4fe9ae0", "chapter_number": "204", "is_external": false}, {"title": "Ch. 203 - A Multitude of Stimulations", "url": "87da9952-9cb3-43ee-bb53-131fc06a40cc", "chapter_number": "203", "is_external": false}, {"title": "Ch. 202 - Let's Go", "url": "a6d5ef92-e4e1-4620-9d06-f0f32f689086", "chapter_number": "202", "is_external": false}, {"title": "Ch. 201 - The King of Demons", "url": "8dcf636a-77e5-4a4f-9811-0e45533fd74c", "chapter_number": "201", "is_external": false}, {"title": "Ch. 200 - The Price of Victory", "url": "c3e250f9-2ee2-4c14-b305-c77639cbe9a9", "chapter_number": "200", "is_external": false}, {"title": "Ch. 199 - Dawn of a Thousand Years", "url": "25d29265-9f55-48af-8bab-cdd4aba1a031", "chapter_number": "199", "is_external": false}, {"title": "Ch. 198 - If You Notice", "url": "9ad854a5-0bab-4a86-8b1e-d561d77baad0", "chapter_number": "198", "is_external": false}, {"title": "Ch. 197 - Tenacity", "url": "faacbed3-1d53-43ac-9c89-6b94cc53e90a", "chapter_number": "197", "is_external": false}, {"title": "Ch. 196 - I Am", "url": "b0a2c238-af6b-4fa5-9eae-d0cf09225efa", "chapter_number": "196", "is_external": false}, {"title": "Ch. 195 - Bewildering", "url": "4842156f-1aaa-49db-b28d-a9787f0d1530", "chapter_number": "195", "is_external": false}, {"title": "Ch. 194 - Scorching Wounds", "url": "fe49a28a-15a3-4a8e-8a3f-61a56591d75d", "chapter_number": "194", "is_external": false}, {"title": "Ch. 193 - The Door of Hardship Begins to Open", "url": "ea08218d-fa87-4f02-b2d7-743dbdc88770", "chapter_number": "193", "is_external": false}, {"title": "Ch. 192 - Returning Fate", "url": "eecd2225-46ab-40d8-bc93-2ae2c0d90174", "chapter_number": "192", "is_external": false}, {"title": "Ch. 191 - Who's the Demon Now?", "url": "854454ec-67b8-4484-a9c1-f3967b1c4ec3", "chapter_number": "191", "is_external": false}, {"title": "Ch. 190 - Shivering", "url": "c0c23efb-e83b-407e-ac00-c94af78398c8", "chapter_number": "190", "is_external": false}, {"title": "Ch. 189 - Reassuring Friends", "url": "3dfaeba2-7511-4896-bf7b-e7df5137a18d", "chapter_number": "189", "is_external": false}, {"title": "Ch. 188 - Heartbreaking Love", "url": "facfb256-25e8-406c-a9ec-7a0ff058acb3", "chapter_number": "188", "is_external": false}, {"title": "Ch. 187 - Innocent Person", "url": "bd220846-8fe2-412f-a45e-c7dc2c6433e6", "chapter_number": "187", "is_external": false}, {"title": "Ch. 186 - Old Memories", "url": "2f86d157-a8b2-431c-8bf6-4d34e4c6ad2c", "chapter_number": "186", "is_external": false}, {"title": "Ch. 185 - A World Without Smell", "url": "8296d723-2d63-4cca-928b-36dd7f6103f4", "chapter_number": "185", "is_external": false}, {"title": "Ch. 184 - Breakaway from the Battlefront", "url": "ae103e68-571c-4e7f-8d4b-4efb1df6a48a", "chapter_number": "184", "is_external": false}, {"title": "Ch. 183 - Fight", "url": "e48ef449-a6a0-4db4-a741-8e1864003a8c", "chapter_number": "183", "is_external": false}, {"title": "Ch. 182 - Rage", "url": "d1434e67-fefa-40bf-8be0-d81ed7cb1c72", "chapter_number": "182", "is_external": false}, {"title": "Ch. 181 - Great Calamity", "url": "fa653f79-14ba-4e6e-b45a-936616de77f6", "chapter_number": "181", "is_external": false}, {"title": "Ch. 180 - Recovery", "url": "7afbdba3-44d6-4777-873d-59795b96676f", "chapter_number": "180", "is_external": false}, {"title": "Ch. 179 - Love for the Older Brother, Love for the Younger Brother", "url": "15a1aeba-85db-4bca-a68c-89503b2eee76", "chapter_number": "179", "is_external": false}, {"title": "Ch. 178 - Despite Reaching My Hand Out, Despite Reaching My Hand Out", "url": "00bfcf4e-b64b-4807-a1b5-5935a2d12587", "chapter_number": "178", "is_external": false}, {"title": "Ch. 177 - Little Brother", "url": "1e8665b9-9d4b-4e39-bf63-f585960cfcde", "chapter_number": "177", "is_external": false}, {"title": "Ch. 176 - Samurai", "url": "e87cf162-09f3-4794-baea-6a7dd44d441d", "chapter_number": "176", "is_external": false}, {"title": "Ch. 175 - The Incredible Younger Generation", "url": "4cd448c2-d048-4c44-8d65-2852e7e5752e", "chapter_number": "175", "is_external": false}, {"title": "Ch. 174 - A Nightmare Seen on a Blood Moon Night", "url": "43c9d4d2-25c3-4f65-8947-fc9fb89a5def", "chapter_number": "174", "is_external": false}, {"title": "Ch. 173 - The Path Opened by Firm Conviction", "url": "48fc2e70-02d7-4b50-9726-f73bda279f18", "chapter_number": "173", "is_external": false}, {"title": "Ch. 172 - The Potential of a Weakling", "url": "99daaef3-36a3-4e3b-b5a3-2a94422600bf", "chapter_number": "172", "is_external": false}, {"title": "Ch. 171 - Transformation", "url": "0de71382-85d8-497c-b705-27f52aa55034", "chapter_number": "171", "is_external": false}, {"title": "Ch. 170 - Steadfast Pillars", "url": "23dcfa40-1089-4c6c-9197-e105611ee53d", "chapter_numbeLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mangadex": [{"title": "Ch. 1 - To You, 2000 Years From Now", "url": "a2e05215-4d19-401e-8526-7659b2ddcc19", "chapter_number": "1", "is_external": false}]}, "_timestamp": 1775436651.154676}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 153288, "mal_id": 52588, "title_romaji": "Kaijuu 8-gou", "title_english": "Kaiju No. 8", "title_native": "\u602a\u7363\uff18\u53f7", "format": "TV", "start_date": {"year": 2024, "month": 4, "day": 13}, "season": "SPRING", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8", "_start_tuple": [2024, 4, 13], "is_final_season": false}, {"anilist_id": 178754, "mal_id": 59177, "title_romaji": "Kaijuu 8-gou 2nd Season", "title_english": "Kaiju No. 8 Season 2", "title_native": "\u602a\u7363\uff18\u53f7 \u7b2c\uff12\u671f", "format": "TV", "start_date": {"year": 2025, "month": 7, "day": 19}, "season": "SUMMER", "season_year": 2025, "episodes": 11, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Kaiju No. 8 Season 2", "_start_tuple": [2025, 7, 19], "is_final_season": false}, {"anilist_id": 204362, "mal_id": 63136, "title_romaji": "Kaijuu 8-gou: Kanketsu-hen", "title_english": null, "title_native": "\u602a\u7363\uff18\u53f7 \u5b8c\u7d50\u7de8", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaijuu 8-gou: Kanketsu-hen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 179998, "mal_id": 59489, "title_romaji": "Kaijuu 8-gou Movie", "title_english": "Kaiju No. 8: Mission Recon", "title_native": "\u602a\u73638\u53f7 \u7b2c1\u671f\u7dcf\u96c6\u7de8", "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}, "season": "WINTER", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Kaiju No. 8: Mission Recon", "_start_tuple": [2025, 3, 28], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Kaiju No. 8", "mal_id": 52588, "anilist_id": 153288, "start_date": {"year": 2024, "month": 4, "day": 13}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Kaiju No. 8 Season 2", "mal_id": 59177, "anilist_id": 178754, "start_date": {"year": 2025, "month": 7, "day": 19}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Kaijuu 8-gou: Kanketsu-hen", "mal_id": 63136, "anilist_id": 204362, "format": null, "start_date": {"year": null, "month": null, "day": null}}, {"title": "Kaiju No. 8: Mission Recon", "mal_id": 59489, "anilist_id": 179998, "format": "MOVIE", "start_date": {"year": 2025, "month": 3, "day": 28}}]}, "_timestamp": 1775001076.475924}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 176496, "mal_id": 58567, "title_romaji": "Ore dake Level Up na Ken: Season 2 - Arise from the Shadow", "title_english": "Solo Leveling Season 2 -Arise from the Shadow-", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6 Season 2 -Arise from the Shadow-", "format": "TV", "start_date": {"year": 2025, "month": 1, "day": 5}, "season": "WINTER", "season_year": 2025, "episodes": 13, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Solo Leveling Season 2 -Arise from the Shadow-", "_start_tuple": [2025, 1, 5], "is_final_season": false}, {"anilist_id": 184694, "mal_id": 59841, "title_romaji": "Ore dake Level Up na Ken: ReAwakening", "title_english": "Solo Leveling -ReAwakening-", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6 -ReAwakening-", "format": "MOVIE", "start_date": {"year": 2024, "month": 11, "day": 29}, "season": "FALL", "season_year": 2024, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Solo Leveling -ReAwakening-", "_start_tuple": [2024, 11, 29], "is_final_season": false}, {"anilist_id": 151807, "mal_id": 52299, "title_romaji": "Ore dake Level Up na Ken", "title_english": "Solo Leveling", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6", "format": "TV", "start_date": {"year": 2024, "month": 1, "day": 7}, "season": "WINTER", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Solo Leveling", "_start_tuple": [2024, 1, 7], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Solo Leveling", "mal_id": 52299, "anilist_id": 151807, "start_date": {"year": 2024, "month": 1, "day": 7}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Solo Leveling Season 2 -Arise from the Shadow-", "mal_id": 58567, "anilist_id": 176496, "start_date": {"year": 2025, "month": 1, "day": 5}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Solo Leveling -ReAwakening-", "mal_id": 59841, "anilist_id": 184694, "format": "MOVIE", "start_date": {"year": 2024, "month": 11, "day": 29}}]}, "_timestamp": 1775086915.503556}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 172463, "mal_id": 57658, "title_romaji": "Jujutsu Kaisen: Shimetsu Kaiyuu - Zenpen", "title_english": "JUJUTSU KAISEN Season 3: The Culling Game Part 1", "title_native": "\u546a\u8853\u5efb\u6226 \u6b7b\u6ec5\u56de\u6e38 \u524d\u7de8", "format": "TV", "start_date": {"year": 2026, "month": 1, "day": 9}, "season": "WINTER", "season_year": 2026, "episodes": 12, "relation_type_from_parent": null, "inferred_part": 3, "is_season": true, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN Season 3: The Culling Game Part 1", "_start_tuple": [2026, 1, 9], "is_final_season": false}, {"anilist_id": 209895, "mal_id": null, "title_romaji": "Jujutsu Kaisen: Shimetsu Kaiyuu - Kouhen", "title_english": null, "title_native": "\u546a\u8853\u5efb\u6226 \u6b7b\u6ec5\u56de\u6e38 \u5f8c\u7de8", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Jujutsu Kaisen: Shimetsu Kaiyuu - Kouhen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 204066, "mal_id": 62392, "title_romaji": "Jujutsu Kaisen: Shibuya Jihen Tokubetsu Henshuu-ban x Shimetsu Kaiyuu Senkou Jouei", "title_english": "JUJUTSU KAISEN: Execution -Shibuya Incident x The Culling Game Begins-", "title_native": "\u546a\u8853\u5efb\u6226\u300e\u6e0b\u8c37\u4e8b\u5909 \u7279\u5225\u7de8\u96c6\u7248\u300f\u00d7\u300e\u6b7b\u6ec5\u56de\u6e38 \u5148\u884c\u4e0a\u6620\u300f", "format": "MOVIE", "start_date": {"year": 2025, "month": 11, "day": 7}, "season": "FALL", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN: Execution -Shibuya Incident x The Culling Game Begins-", "_start_tuple": [2025, 11, 7], "is_final_season": false}, {"anilist_id": 192809, "mal_id": 59654, "title_romaji": "Jujutsu Kaisen: Kaigyoku\u30fbGyokusetsu", "title_english": "JUJUTSU KAISEN: Hidden Inventory / Premature Death \u2013 The Movie", "title_native": "\u546a\u8853\u5efb\u6226 \u61d0\u7389\u30fb\u7389\u6298", "format": "MOVIE", "start_date": {"year": 2025, "month": 5, "day": 30}, "season": "SPRING", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN: Hidden Inventory / Premature Death \u2013 The Movie", "_start_tuple": [2025, 5, 30], "is_final_season": false}, {"anilist_id": 145064, "mal_id": 51009, "title_romaji": "Jujutsu Kaisen 2nd Season", "title_english": "JUJUTSU KAISEN Season 2", "title_native": "\u546a\u8853\u5efb\u6226 \u7b2c2\u671f", "format": "TV", "start_date": {"year": 2023, "month": 7, "day": 6}, "season": "SUMMER", "season_year": 2023, "episodes": 23, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN Season 2", "_start_tuple": [2023, 7, 6], "is_final_season": false}, {"anilist_id": 113415, "mal_id": 40748, "title_romaji": "Jujutsu Kaisen", "title_english": "JUJUTSU KAISEN", "title_native": "\u546a\u8853\u5efb\u6226", "format": "TV", "start_date": {"year": 2020, "month": 10, "day": 3}, "season": "FALL", "season_year": 2020, "episodes": 24, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN", "_start_tuple": [2020, 10, 3], "is_final_season": false}, {"anilist_id": 131573, "mal_id": 48561, "title_romaji": "Jujutsu Kaisen 0", "title_english": "JUJUTSU KAISEN 0", "title_native": "\u546a\u8853\u5efb\u6226 0", "format": "MOVIE", "start_date": {"year": 2021, "month": 12, "day": 24}, "season": "FALL", "season_year": 2021, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "JUJUTSU KAISEN 0", "_start_tuple": [2021, 12, 24], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "JUJUTSU KAISEN", "mal_id": 40748, "anilist_id": 113415, "start_date": {"year": 2020, "month": 10, "day": 3}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "JUJUTSU KAISEN Season 2", "mal_id": 51009, "anilist_id": 145064, "start_date": {"year": 2023, "month": 7, "day": 6}, "format": "TV", "is_final": false}]}, {"season_index": 3, "group_label": "Season 3", "parts": [{"short_label": "S3", "title": "JUJUTSU KAISEN Season 3: The Culling Game Part 1", "mal_id": 57658, "anilist_id": 172463, "start_date": {"year": 2026, "month": 1, "day": 9}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Jujutsu Kaisen: Shimetsu Kaiyuu - Kouhen", "mal_id": null, "anilist_id": 209895, "format": null, "Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mal_id": 96792, "url": "https://myanimelist.net/manga/96792/Kimetsu_no_Yaiba", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/3/179023.jpg", "small_image_url": "https://myanimelist.net/images/manga/3/179023t.jpg", "large_image_url": "https://myanimelist.net/images/manga/3/179023l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/3/179023.webp", "small_image_url": "https://myanimelist.net/images/manga/3/179023t.webp", "large_image_url": "https://myanimelist.net/images/manga/3/179023l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Kimetsu no Yaiba"}, {"type": "Synonym", "title": "Blade of Demon Destruction"}, {"type": "Japanese", "title": "\u9b3c\u6ec5\u306e\u5203"}, {"type": "English", "title": "Demon Slayer: Kimetsu no Yaiba"}, {"type": "German", "title": "Demon Slayer - Kimetsu no Yaiba"}, {"type": "Spanish", "title": "Guardianes de la noche"}, {"type": "French", "title": "Demon Slayer"}], "title": "Kimetsu no Yaiba", "title_english": "Demon Slayer: Kimetsu no Yaiba", "title_japanese": "\u9b3c\u6ec5\u306e\u5203", "title_synonyms": ["Blade of Demon Destruction"], "type": "Manga", "chapters": 207, "volumes": 23, "status": "Finished", "publishing": false, "published": {"from": "2016-02-15T00:00:00+00:00", "to": "2020-05-18T00:00:00+00:00", "prop": {"from": {"day": 15, "month": 2, "year": 2016}, "to": {"day": 18, "month": 5, "year": 2020}}, "string": "Feb 15, 2016 to May 18, 2020"}, "score": 8.11, "scored": 8.11, "scored_by": 316432, "rank": 667, "popularity": 10, "members": 497100, "favorites": 29032, "synopsis": "Tanjirou Kamado lives with his impoverished family on a remote mountain. As the oldest sibling, he took upon the responsibility of ensuring his family's livelihood after the death of his father. On a cold winter day, he goes down to the local village in order to sell some charcoal. As dusk falls, he is forced to spend the night in the house of a curious man who cautions him of strange creatures that roam the night: malevolent demons who crave human flesh.\n\nWhen he finally makes his way home, Tanjirou's worst nightmare comes true. His entire family has been brutally slaughtered with the sole exception of his sister Nezuko, who has turned into a flesh-eating demon. Engulfed in hatred and despair, Tanjirou desperately tries to stop Nezuko from attacking other people, setting out on a journey to avenge his family and find a way to turn his beloved sister back into a human.\n\n[Written by MAL Rewrite]", "background": "Kimetsu no Yaiba ranked sixth and 17th in the 2020 and 2021 Kono Manga ga Sugoi! for the Male Readers division, respectively. Following the series' success, Koyoharu Gotouge was awarded the Special Prize at the 25th Tezuka Osamu Cultural Prize in 2021. As of July 2025, over 220 million copies of the series have been sold worldwide. As a part of the JUMP START initiative, the first three chapters of Kimetsu no Yaiba were published in English in VIZ Media's digital Weekly Shonen Jump on the same day as the Japanese release. VIZ Media published the volumes as Demon Slayer: Kimetsu no Yaiba under its Shonen Jump imprint from July 3, 2018, to August 3, 2021.", "authors": [{"mal_id": 40398, "type": "people", "name": "Gotouge, Koyoharu", "url": "https://myanimelist.net/people/40398/Koyoharu_Gotouge"}], "serializations": [{"mal_id": 83, "type": "manga", "name": "Shounen Jump (Weekly)", "url": "https://myanimelist.net/manga/magazine/83/Shounen_Jump_Weekly"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 10, "type": "manga", "name": "Fantasy", "url": "https://myanimelist.net/manga/genre/10/Fantasy"}], "explicit_genres": [], "themes": [{"mal_id": 13, "type": "manga", "name": "Historical", "url": "https://myanimelist.net/manga/genre/13/Historical"}], "demographics": [{"mal_id": 27, "type": "manga", "name": "Shounen", "url": "https://myanimelist.net/manga/genre/27/Shounen"}], "relations": [{"relation": "Adaptation", "entry": [{"mal_id": 160805, "type": "manga", "name": "Kimetsu no Yaiba Novelize", "url": "https://myanimelist.net/manga/160805/Kimetsu_no_Yaiba_Novelize"}, {"mal_id": 153037, "type": "manga", "name": "Gekijouban Kimetsu no Yaiba: Mugen Ressha-hen Novelize", "url": "https://myanimelist.net/manga/153037/Gekijouban_Kimetsu_no_Yaiba__Mugen_Ressha-hen_Novelize"}, {"mal_id": 38000, "type": "anime", "name": "Kimetsu no Yaiba", "url": "https://myanimelist.net/anime/38000/Kimetsu_no_Yaiba"}, {"mal_id": 40456, "type": "anime", "name": "Kimetsu no Yaiba Movie: Mugen Ressha-hen", "url": "https://myanimelist.net/anime/40456/Kimetsu_no_Yaiba_Movie__Mugen_Ressha-hen"}, {"mal_id": 49926, "type": "anime", "name": "Kimetsu no Yaiba: Mugen Ressha-hen", "url": "https://myanimelist.net/anime/49926/Kimetsu_no_Yaiba__Mugen_Ressha-hen"}, {"mal_id": 47778, "type": "anime", "name": "Kimetsu no Yaiba: Yuukaku-hen", "url": "https://myanimelist.net/anime/47778/KLine truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [], "season_groups": [], "extras": []}, "_timestamp": 1774915396.871178}
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"entries": [{"anilist_id": 171627, "mal_id": 57555, "title_romaji": "Chainsaw Man: Reze-hen", "title_english": "Chainsaw Man \u2013 The Movie: Reze Arc", "title_native": "\u30c1\u30a7\u30f3\u30bd\u30fc\u30de\u30f3 \u30ec\u30bc\u7bc7", "format": "MOVIE", "start_date": {"year": 2025, "month": 9, "day": 19}, "season": "SUMMER", "season_year": 2025, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Chainsaw Man \u2013 The Movie: Reze Arc", "_start_tuple": [2025, 9, 19], "is_final_season": false}, {"anilist_id": 204429, "mal_id": 63143, "title_romaji": "Chainsaw Man: Shikaku-hen", "title_english": null, "title_native": "\u30c1\u30a7\u30f3\u30bd\u30fc\u30de\u30f3 \u523a\u5ba2\u7bc7", "format": null, "start_date": {"year": null, "month": null, "day": null}, "season": null, "season_year": null, "episodes": null, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Chainsaw Man: Shikaku-hen", "_start_tuple": [0, 0, 0], "is_final_season": false}, {"anilist_id": 127230, "mal_id": 44511, "title_romaji": "Chainsaw Man", "title_english": "Chainsaw Man", "title_native": "\u30c1\u30a7\u30f3\u30bd\u30fc\u30de\u30f3", "format": "TV", "start_date": {"year": 2022, "month": 10, "day": 12}, "season": "FALL", "season_year": 2022, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Chainsaw Man", "_start_tuple": [2022, 10, 12], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Chainsaw Man", "mal_id": 44511, "anilist_id": 127230, "start_date": {"year": 2022, "month": 10, "day": 12}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Chainsaw Man: Shikaku-hen", "mal_id": 63143, "anilist_id": 204429, "format": null, "start_date": {"year": null, "month": null, "day": null}}, {"title": "Chainsaw Man \u2013 The Movie: Reze Arc", "mal_id": 57555, "anilist_id": 171627, "format": "MOVIE", "start_date": {"year": 2025, "month": 9, "day": 19}}]}, "_timestamp": 1775008693.3776262}
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"mal_id": 116778, "url": "https://myanimelist.net/manga/116778/Chainsaw_Man", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/3/216464.jpg", "small_image_url": "https://myanimelist.net/images/manga/3/216464t.jpg", "large_image_url": "https://myanimelist.net/images/manga/3/216464l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/3/216464.webp", "small_image_url": "https://myanimelist.net/images/manga/3/216464t.webp", "large_image_url": "https://myanimelist.net/images/manga/3/216464l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Chainsaw Man"}, {"type": "Japanese", "title": "\u30c1\u30a7\u30f3\u30bd\u30fc\u30de\u30f3"}, {"type": "English", "title": "Chainsaw Man"}], "title": "Chainsaw Man", "title_english": "Chainsaw Man", "title_japanese": "\u30c1\u30a7\u30f3\u30bd\u30fc\u30de\u30f3", "title_synonyms": [], "type": "Manga", "chapters": null, "volumes": 24, "status": "Finished", "publishing": false, "published": {"from": "2018-12-03T00:00:00+00:00", "to": "2026-03-25T00:00:00+00:00", "prop": {"from": {"day": 3, "month": 12, "year": 2018}, "to": {"day": 25, "month": 3, "year": 2026}}, "string": "Dec 3, 2018 to Mar 25, 2026"}, "score": 8.65, "scored": 8.65, "scored_by": 439432, "rank": 94, "popularity": 2, "members": 724910, "favorites": 91041, "synopsis": "Denji has a simple dream\u2014to live a happy and peaceful life, spending time with a girl he likes. This is a far cry from reality, however, as Denji is forced by the yakuza into killing devils in order to pay off his crushing debts. Using his pet devil Pochita as a weapon, he is ready to do anything for a bit of cash.\n\nUnfortunately, he has outlived his usefulness and is murdered by a devil in contract with the yakuza. However, in an unexpected turn of events, Pochita merges with Denji's dead body and grants him the powers of a chainsaw devil. Now able to transform parts of his body into chainsaws, a revived Denji uses his new abilities to quickly and brutally dispatch his enemies. Catching the eye of the official devil hunters who arrive at the scene, he is offered work at the Public Safety Bureau as one of them. Now with the means to face even the toughest of enemies, Denji will stop at nothing to achieve his simple teenage dreams.\n\n[Written by MAL Rewrite]", "background": "Chainsaw Man ranked fourth and first in the 2020 and 2021 Kono Manga ga Sugoi! for the Male Readers division, respectively. The series was nominated for the 13th Manga Taisho Award in 2020. It won the 66th Shogakukan Manga Award in the Shounen category, along with Karakai Jouzu no Takagi-san. As part of the JUMP START initiative, the first three chapters of Chainsaw Man were published in English in VIZ Media's digital Weekly Shounen Jump on the same day as the Japanese release. It has been published digitally as an English simulpub through MANGA Plus. The volumes have been published in English by VIZ Media under the Shonen Jump imprint since October 6, 2020. The manga has also been published in German by Egmont since October 6, 2020, and in Italian by Planet Manga since July 22, 2021. The first part of the series ended with 97 chapters on December 14, 2020. Serialization of the second part began on Shounen Jump+ with the release of chapter 98 on July 13, 2022.", "authors": [{"mal_id": 41158, "type": "people", "name": "Fujimoto, Tatsuki", "url": "https://myanimelist.net/people/41158/Tatsuki_Fujimoto"}], "serializations": [{"mal_id": 1209, "type": "manga", "name": "Shounen Jump+", "url": "https://myanimelist.net/manga/magazine/1209/Shounen_Jump_"}], "genres": [{"mal_id": 1, "type": "manga", "name": "Action", "url": "https://myanimelist.net/manga/genre/1/Action"}, {"mal_id": 46, "type": "manga", "name": "Award Winning", "url": "https://myanimelist.net/manga/genre/46/Award_Winning"}, {"mal_id": 10, "type": "manga", "name": "Fantasy", "url": "https://myanimelist.net/manga/genre/10/Fantasy"}], "explicit_genres": [], "themes": [{"mal_id": 58, "type": "manga", "name": "Gore", "url": "https://myanimelist.net/manga/genre/58/Gore"}, {"mal_id": 83, "type": "manga", "name": "Urban Fantasy", "url": "https://myanimelist.net/manga/genre/83/Urban_Fantasy"}], "demographics": [{"mal_id": 27, "type": "manga", "name": "Shounen", "url": "https://myanimelist.net/manga/genre/27/Shounen"}], "relations": [{"relation": "Adaptation", "entry": [{"mal_id": 44511, "type": "anime", "name": "Chainsaw Man", "url": "https://myanimelist.net/anime/44511/Chainsaw_Man"}, {"mal_id": 62352, "type": "anime", "name": "Chainsaw Man Recap", "url": "https://myanimelist.net/anime/62352/Chainsaw_Man_Recap"}, {"mal_id": 57555, "type": "anime", "name": "Chainsaw Man Movie: Reze-hen", "url": "https://myanimelist.net/anime/57555/Chainsaw_Man_Movie__Reze-hen"}, {"mal_id": 63143, "type": "anime", "name": "Chainsaw Man: Shikaku-hen", "url": "https://myanimelist.net/anime/63143/Chainsaw_Man__Shikaku-hen"}]}, {"relation": "Side Story", "entry": [{"mal_id": 140150Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"payload": {"mal_id": 121948, "url": "https://myanimelist.net/manga/121948/Dokusen_shitemo_Ii_desu_ka", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/2/235219.jpg", "small_image_url": "https://myanimelist.net/images/manga/2/235219t.jpg", "large_image_url": "https://myanimelist.net/images/manga/2/235219l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/2/235219.webp", "small_image_url": "https://myanimelist.net/images/manga/2/235219t.webp", "large_image_url": "https://myanimelist.net/images/manga/2/235219l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Dokusen shitemo Ii desu ka"}, {"type": "Synonym", "title": "Will You Be Mine?"}, {"type": "Japanese", "title": "\u72ec\u5360\u3057\u3066\u3082\u3044\u3044\u3067\u3059\u304b"}, {"type": "English", "title": "Can I Have You All to Myself?"}], "title": "Dokusen shitemo Ii desu ka", "title_english": "Can I Have You All to Myself?", "title_japanese": "\u72ec\u5360\u3057\u3066\u3082\u3044\u3044\u3067\u3059\u304b", "title_synonyms": ["Will You Be Mine?"], "type": "Manga", "chapters": 7, "volumes": 1, "status": "Finished", "publishing": false, "published": {"from": "2017-04-28T00:00:00+00:00", "to": "2017-10-31T00:00:00+00:00", "prop": {"from": {"day": 28, "month": 4, "year": 2017}, "to": {"day": 31, "month": 10, "year": 2017}}, "string": "Apr 28, 2017 to Oct 31, 2017"}, "score": 6.74, "scored": 6.74, "scored_by": 136, "rank": null, "popularity": 32965, "members": 333, "favorites": 0, "synopsis": "Yuu and Ryou have been best friends since they were kids. Yuu is not the most approachable guy, unlike Ryou, who is very popular. It doesn't really bother Yuu that he's the outcast of the class, and he even admits that Ryou is the only person he feels comfortable talking to. But then, Ryou is asked to substitute for a player on the basketball team, meaning that he is not able to spend as much time with Yuu... Yuu is unhappy about this, and he cannot help but lash out at Ryou. Could Yuu's extremely possessive feelings be something more? Is their relationship really just about friendship?\n\n(Source: Renta!)", "background": "Dokusen shitemo Ii desu ka was published digitally in English as Can I Have You All to Myself? by Renta!", "authors": [{"mal_id": 52232, "type": "people", "name": "Kogamo", "url": "https://myanimelist.net/people/52232/Kogamo"}], "serializations": [], "genres": [{"mal_id": 28, "type": "manga", "name": "Boys Love", "url": "https://myanimelist.net/manga/genre/28/Boys_Love"}, {"mal_id": 49, "type": "manga", "name": "Erotica", "url": "https://myanimelist.net/manga/genre/49/Erotica"}], "explicit_genres": [], "themes": [{"mal_id": 23, "type": "manga", "name": "School", "url": "https://myanimelist.net/manga/genre/23/School"}], "demographics": [], "relations": [], "external": []}, "_timestamp": 1775087082.701801}
|
||||
@@ -0,0 +1 @@
|
||||
{"payload": {"data": {"mal_id": 21, "url": "https://myanimelist.net/anime/21/One_Piece", "images": {"jpg": {"image_url": "https://myanimelist.net/images/anime/1244/138851.jpg", "small_image_url": "https://myanimelist.net/images/anime/1244/138851t.jpg", "large_image_url": "https://myanimelist.net/images/anime/1244/138851l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/anime/1244/138851.webp", "small_image_url": "https://myanimelist.net/images/anime/1244/138851t.webp", "large_image_url": "https://myanimelist.net/images/anime/1244/138851l.webp"}}, "trailer": {"youtube_id": null, "url": null, "embed_url": "https://www.youtube-nocookie.com/embed/-tviZNY6CSw?enablejsapi=1&wmode=opaque&autoplay=1", "images": {"image_url": null, "small_image_url": null, "medium_image_url": null, "large_image_url": null, "maximum_image_url": null}}, "approved": true, "titles": [{"type": "Default", "title": "One Piece"}, {"type": "Synonym", "title": "OP"}, {"type": "Japanese", "title": "ONE PIECE"}, {"type": "English", "title": "One Piece"}], "title": "One Piece", "title_english": "One Piece", "title_japanese": "ONE PIECE", "title_synonyms": ["OP"], "type": "TV", "source": "Manga", "episodes": null, "status": "Currently Airing", "airing": true, "aired": {"from": "1999-10-20T00:00:00+00:00", "to": null, "prop": {"from": {"day": 20, "month": 10, "year": 1999}, "to": {"day": null, "month": null, "year": null}}, "string": "Oct 20, 1999 to ?"}, "duration": "24 min", "rating": "PG-13 - Teens 13 or older", "score": 8.73, "scored_by": 1519282, "rank": 53, "popularity": 17, "members": 2661507, "favorites": 250390, "synopsis": "Barely surviving in a barrel after passing through a terrible whirlpool at sea, carefree Monkey D. Luffy ends up aboard a ship under attack by fearsome pirates. Despite being a naive-looking teenager, he is not to be underestimated. Unmatched in battle, Luffy is a pirate himself who resolutely pursues the coveted One Piece treasure and the King of the Pirates title that comes with it.\n\nThe late King of the Pirates, Gol D. Roger, stirred up the world before his death by disclosing the whereabouts of his hoard of riches and daring everyone to obtain it. Ever since then, countless powerful pirates have sailed dangerous seas for the prized One Piece only to never return. Although Luffy lacks a crew and a proper ship, he is endowed with a superhuman ability and an unbreakable spirit that make him not only a formidable adversary but also an inspiration to many.\n\nAs he faces numerous challenges with a big smile on his face, Luffy gathers one-of-a-kind companions to join him in his ambitious endeavor, together embracing perils and wonders on their once-in-a-lifetime adventure.\n\n[Written by MAL Rewrite]", "background": "The anime had a hiatus from October 13, 2024, to April 6, 2025. The airing time was Wednesdays 19:00 between October 20, 1999 - March 2001. The airing time was changed to Sundays 19:30 between April 2001 - December 2004. The airing time was changed to Sundays 9:30 between October 6, 2006 - October 13, 2024. The airing time was again changed to Sundays 23:15 on April 6, 2025. Several anime-original arcs have been adapted into light novels, and the series has inspired 50+ video games as of 2023. In June 2004, One Piece was licensed in North America by 4Kids Entertainment, which partnered with Viz Media for home video distribution. As One Piece proved unsuitable for their target demographic, 4Kids Entertainment censored the show to meet their standards, and, in December 2006, they stopped its production. In April 2007, Funimation took over the series licensing, providing an uncut version that remained faithful to the original release. In Japan, the anime's first 574 episodes were released exclusively on DVD by Avex Pictures from February 21, 2001, to December 4, 2013. Blu-rays also became available with the DVDs starting on January 8, 2014. In North America, Viz Media released the anime on DVD between February 28, 2006, and June 26, 2007. Funimation has re-released and continued the series since May 27, 2008. From March 23, 2021, the DVDs were accompanied by Blu-rays as well.", "season": "fall", "year": 1999, "broadcast": {"day": "Sundays", "time": "23:15", "timezone": "Asia/Tokyo", "string": "Sundays at 23:15 (JST)"}, "producers": [{"mal_id": 169, "type": "anime", "name": "Fuji TV", "url": "https://myanimelist.net/anime/producer/169/Fuji_TV"}, {"mal_id": 416, "type": "anime", "name": "TAP", "url": "https://myanimelist.net/anime/producer/416/TAP"}], "licensors": [{"mal_id": 102, "type": "anime", "name": "Funimation", "url": "https://myanimelist.net/anime/producer/102/Funimation"}, {"mal_id": 252, "type": "anime", "name": "4Kids Entertainment", "url": "https://myanimelist.net/anime/producer/252/4Kids_Entertainment"}], "studios": [{"mal_id": 18, "type": "anime", "name": "Toei Animation", "url": "https://myanimelist.net/anime/producer/18/Toei_Animation"}], "genres": [{"mal_id": 1, "type": "anime", Line truncated
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Comix Reader",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"author": "Animex",
|
||||
"description": "Comix.to Manga Reader - Double-Safe Nested Data Parsing.",
|
||||
"description": "Comix.to Manga Reader - Fixed WAF validation, URL encoding, and Anti-Leech headers.",
|
||||
"type": "MANGA_READER",
|
||||
"requirements": ["httpx", "re", "json"]
|
||||
}
|
||||
@@ -13,12 +13,14 @@ import urllib.parse
|
||||
import inspect
|
||||
import httpx
|
||||
|
||||
# Exact headers from your working test client
|
||||
# Extended headers to satisfy WAF & Anti-Leech checks
|
||||
HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Referer': 'https://comix.to/'
|
||||
'Referer': 'https://comix.to/',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
|
||||
async def _smart_fetch(method: str, url: str, **kwargs):
|
||||
"""Uses injected HybridClient if available, otherwise falls back to real httpx."""
|
||||
client_or_lib = globals().get('httpx')
|
||||
@@ -26,21 +28,14 @@ async def _smart_fetch(method: str, url: str, **kwargs):
|
||||
func = getattr(client_or_lib, method.lower(), None)
|
||||
if func and inspect.iscoroutinefunction(func):
|
||||
resp = await func(url, **kwargs)
|
||||
|
||||
# Debug: dump raw response to see what we're actually getting
|
||||
raw = getattr(resp, 'text', '') or ''
|
||||
print(f"[Comix] _smart_fetch response preview: {raw[:300]}")
|
||||
|
||||
return resp
|
||||
|
||||
# Fallback: real httpx.AsyncClient
|
||||
import httpx as _real_httpx
|
||||
async with _real_httpx.AsyncClient(follow_redirects=True) as client:
|
||||
resp = await getattr(client, method.lower())(url, **kwargs)
|
||||
print(f"[Comix] _smart_fetch (direct) response preview: {resp.text[:300]}")
|
||||
return resp
|
||||
|
||||
|
||||
def get_nested(data, *keys, default=None):
|
||||
"""Helper to safely traverse deeply nested dictionaries even if keys are None."""
|
||||
for key in keys:
|
||||
@@ -65,15 +60,19 @@ async def get_title_from_mal(mal_id: int):
|
||||
return None
|
||||
|
||||
async def search_manga(query: str):
|
||||
"""Searches Comix.to and returns (hash_id, slug)."""
|
||||
"""Searches Comix.to and returns (manga_id, hash_id, slug)."""
|
||||
if not query: return None
|
||||
url = f"https://comix.to/api/v2/manga?keyword={urllib.parse.quote(query)}&order[relevance]=desc"
|
||||
|
||||
# Safely URL encode parameters (prevents WAF bracket blocks)
|
||||
params = {"keyword": query, "order[relevance]": "desc"}
|
||||
qs = urllib.parse.urlencode(params)
|
||||
url = f"https://comix.to/api/v2/manga?{qs}"
|
||||
|
||||
try:
|
||||
resp = await _smart_fetch("GET", url, headers=HEADERS)
|
||||
print(f"[Comix] Search status: {resp.status_code}")
|
||||
print(f"[Comix] Search status: {getattr(resp, 'status_code', 500)}")
|
||||
data = resp.json() if hasattr(resp, "json") else None
|
||||
if data is None:
|
||||
print(f"[Comix] Search response was not JSON. Raw: {resp.text[:200]}")
|
||||
return None
|
||||
|
||||
# Safely get first item from result -> items
|
||||
@@ -81,7 +80,8 @@ async def search_manga(query: str):
|
||||
if items and isinstance(items, list) and len(items) > 0:
|
||||
first = items[0]
|
||||
if isinstance(first, dict):
|
||||
return first.get('hash_id'), first.get('slug')
|
||||
# Return manga_id too for WAF API fallback purposes
|
||||
return first.get('manga_id'), first.get('hash_id'), first.get('slug')
|
||||
except Exception as e:
|
||||
print(f"[Comix] Search Error: {e}")
|
||||
return None
|
||||
@@ -91,30 +91,47 @@ async def get_chapters(mal_id: int):
|
||||
print(f"[Comix] get_chapters called for MAL ID: {mal_id}")
|
||||
|
||||
title = await get_title_from_mal(mal_id)
|
||||
print(f"[Comix] Resolved title: {title}")
|
||||
if not title: return None
|
||||
|
||||
manga_info = await search_manga(title)
|
||||
print(f"[Comix] Search result: {manga_info}")
|
||||
if not manga_info: return None
|
||||
|
||||
hash_id, slug = manga_info
|
||||
manga_id, hash_id, slug = manga_info
|
||||
|
||||
# Paginate since API caps at 100 per request
|
||||
all_items = []
|
||||
offset = 0
|
||||
seen_ids = set()
|
||||
|
||||
all_items = []
|
||||
page = 1
|
||||
|
||||
while True:
|
||||
url = f"https://comix.to/api/v2/manga/{hash_id}/chapters?order[number]=asc&limit=100&page={page}"
|
||||
resp = await _smart_fetch("GET", url, headers=HEADERS)
|
||||
# Dynamically spoof the Referer for the specific manga to bypass Anti-Leech
|
||||
req_headers = HEADERS.copy()
|
||||
req_headers["Referer"] = f"https://comix.to/title/{hash_id}-{slug}"
|
||||
|
||||
# Safely encode the query string to prevent 403 Bracket Rejection
|
||||
params = {
|
||||
"order[number]": "asc",
|
||||
"limit": 100,
|
||||
"page": page
|
||||
}
|
||||
qs = urllib.parse.urlencode(params)
|
||||
|
||||
# Primary Try: Try requesting chapters using the Hash ID
|
||||
url = f"https://comix.to/api/v2/manga/{hash_id}/chapters?{qs}&_=xQm9tJfLwGhz_0Eq8S_YAHYkwp-q1PLfm50W5QJnyd1NnNYpAjXjyCoAzoOLRgUaJOoxWS0NeDGz_rNrbqBjLLP1H9qi"
|
||||
resp = await _smart_fetch("GET", url, headers=req_headers)
|
||||
|
||||
data = resp.json() if hasattr(resp, "json") else None
|
||||
|
||||
# Fallback: If framework validation fails (400, 403, 404), it might be strictly
|
||||
# expecting the internal integer primary key instead of the string hash.
|
||||
if data and data.get("status") in [400, 403, 404]:
|
||||
print(f"[Comix] Hash ID rejected ({data.get('status')}). Falling back to Integer Manga ID...")
|
||||
url_fallback = f"https://comix.to/api/v2/manga/{manga_id}/chapters?{qs}&_=xQm9tJfLwGhz_0Eq8S_YAHYkwp-q1PLfm50W5QJnyd1NnNYpAjXjyCoAzoOLRgUaJOoxWS0NeDGz_rNrbqBjLLP1H9qi"
|
||||
resp = await _smart_fetch("GET", url_fallback, headers=req_headers)
|
||||
data = resp.json() if hasattr(resp, "json") else None
|
||||
|
||||
if data is None or data.get("status") != 200:
|
||||
print(f"[Comix] Bad response on page {page}: {data.get('message') if data else resp.text[:200]}")
|
||||
error_msg = data.get('message') if data else getattr(resp, 'text', '')[:200]
|
||||
print(f"[Comix] Bad response on page {page}: {error_msg}")
|
||||
break
|
||||
|
||||
items = get_nested(data, 'result', 'items', default=[])
|
||||
@@ -137,11 +154,15 @@ async def get_chapters(mal_id: int):
|
||||
for item in all_items:
|
||||
if not isinstance(item, dict): continue
|
||||
num = str(item.get('number', '0'))
|
||||
|
||||
# Safely fallback to any available ID key
|
||||
c_id = item.get('chapter_id') or item.get('id') or item.get('hash_id')
|
||||
|
||||
if num not in seen_numbers:
|
||||
seen_numbers[num] = True
|
||||
formatted.append({
|
||||
"title": item.get('name') or f"Chapter {num}",
|
||||
"url": f"{hash_id}:{slug}:{item.get('chapter_id')}",
|
||||
"url": f"{hash_id}:{slug}:{c_id}",
|
||||
"chapter_number": num,
|
||||
"is_external": False
|
||||
})
|
||||
@@ -185,7 +206,11 @@ async def get_chapter_images(mal_id: int, chapter_num: str):
|
||||
hash_id, slug, chapter_id = target_chapter["url"].split(":")
|
||||
url = f"https://comix.to/title/{hash_id}-{slug}/{chapter_id}-chapter-{chapter_num}"
|
||||
|
||||
resp = await _smart_fetch("GET", url, headers=HEADERS)
|
||||
# Mirror the Referer just as we do for chapters
|
||||
req_headers = HEADERS.copy()
|
||||
req_headers["Referer"] = f"https://comix.to/title/{hash_id}-{slug}"
|
||||
|
||||
resp = await _smart_fetch("GET", url, headers=req_headers)
|
||||
if not resp or not hasattr(resp, "text"): return None
|
||||
|
||||
regex = r'["\\]*images["\\]*\s*:\s*(\[[^\]]*\])'
|
||||
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"name": "WeebCentral",
|
||||
"version": "1.0.0",
|
||||
"author": "Animex",
|
||||
"description": "WeebCentral.com Manga Reader — uses HTMX scraping endpoints for chapters and pages.",
|
||||
"type": "MANGA_READER",
|
||||
"requirements": ["httpx", "beautifulsoup4", "re"]
|
||||
}
|
||||
---
|
||||
import re
|
||||
import inspect
|
||||
import httpx as _httpx_lib
|
||||
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
except ImportError:
|
||||
BeautifulSoup = None
|
||||
|
||||
BASE_URL = "https://weebcentral.com"
|
||||
|
||||
BROWSER_HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept": "text/html, */*",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal HTTP helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _get_hybrid():
|
||||
"""Returns the injected HybridClient if present, else None."""
|
||||
candidate = globals().get("httpx")
|
||||
if candidate is not None and candidate is not _httpx_lib:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
async def _get(url, headers=None, timeout=20):
|
||||
"""GET via HybridClient tunnel when available, real httpx otherwise."""
|
||||
hybrid = _get_hybrid()
|
||||
if hybrid is not None:
|
||||
func = getattr(hybrid, "get", None)
|
||||
if func and inspect.iscoroutinefunction(func):
|
||||
return await func(url, headers=headers, timeout=timeout)
|
||||
|
||||
async with _httpx_lib.AsyncClient(follow_redirects=True) as c:
|
||||
return await c.get(url, headers=headers, timeout=timeout)
|
||||
|
||||
|
||||
async def _post_form(url, data: dict, extra_headers: dict = None, timeout=20):
|
||||
"""
|
||||
Form-encoded POST. The HybridClient tunnel only speaks JSON bodies, so
|
||||
form POSTs always go through real httpx directly — this is intentional.
|
||||
"""
|
||||
merged = {**BROWSER_HEADERS, **(extra_headers or {})}
|
||||
merged["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
async with _httpx_lib.AsyncClient(follow_redirects=True) as c:
|
||||
return await c.post(url, data=data, headers=merged, timeout=timeout)
|
||||
|
||||
|
||||
def _parse_html(text: str):
|
||||
if BeautifulSoup is None:
|
||||
raise RuntimeError("[WeebCentral] beautifulsoup4 is not installed.")
|
||||
return BeautifulSoup(text, "html.parser")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MAL title lookup
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _get_mal_titles(mal_id: int):
|
||||
"""Returns (romaji_title, english_title) from Jikan, or (None, None)."""
|
||||
url = f"https://api.jikan.moe/v4/manga/{mal_id}"
|
||||
try:
|
||||
resp = await _get(url)
|
||||
if getattr(resp, "status_code", 500) != 200:
|
||||
return None, None
|
||||
data = resp.json() if hasattr(resp, "json") else None
|
||||
if not data:
|
||||
return None, None
|
||||
d = data.get("data", {})
|
||||
return d.get("title"), d.get("title_english")
|
||||
except Exception as e:
|
||||
print(f"[WeebCentral] MAL fetch error: {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WeebCentral search
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _search(query: str):
|
||||
"""
|
||||
Searches WeebCentral via its HTMX quick-search endpoint.
|
||||
Returns a list of {"id", "title", "url"} dicts.
|
||||
"""
|
||||
if not query:
|
||||
return []
|
||||
|
||||
search_url = f"{BASE_URL}/search/simple?location=main"
|
||||
htmx_headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Trigger": "quick-search-input",
|
||||
"HX-Trigger-Name": "text",
|
||||
"HX-Target": "quick-search-result",
|
||||
"HX-Current-URL": f"{BASE_URL}/",
|
||||
}
|
||||
|
||||
try:
|
||||
resp = await _post_form(search_url, data={"text": query}, extra_headers=htmx_headers)
|
||||
if getattr(resp, "status_code", 500) != 200:
|
||||
print(f"[WeebCentral] Search returned status {resp.status_code}")
|
||||
return []
|
||||
|
||||
soup = _parse_html(resp.text)
|
||||
results = []
|
||||
|
||||
for a in soup.select("a"):
|
||||
href = a.get("href", "")
|
||||
if "/series/" not in href:
|
||||
continue
|
||||
|
||||
title_el = a.select_one(".flex-1")
|
||||
title = title_el.get_text(strip=True) if title_el else "Unknown"
|
||||
|
||||
id_match = re.search(r"/series/([^/]+)", href)
|
||||
if not id_match:
|
||||
continue
|
||||
manga_id = id_match.group(1)
|
||||
|
||||
# Accept if either string is a substring of the other (case-insensitive)
|
||||
q_lower, t_lower = query.lower(), title.lower()
|
||||
if q_lower in t_lower or t_lower in q_lower:
|
||||
results.append({"id": manga_id, "title": title, "url": href})
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
print(f"[WeebCentral] Search error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Chapter list
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _get_chapters_for_series(manga_id: str):
|
||||
"""
|
||||
Returns a sorted list of chapter dicts:
|
||||
{"id", "title", "chapter_number"}
|
||||
Ordered ascending by chapter number.
|
||||
"""
|
||||
url = f"{BASE_URL}/series/{manga_id}/full-chapter-list"
|
||||
htmx_headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Target": "chapter-list",
|
||||
"HX-Current-URL": f"{BASE_URL}/series/{manga_id}",
|
||||
"Referer": f"{BASE_URL}/series/{manga_id}",
|
||||
}
|
||||
|
||||
try:
|
||||
resp = await _get(url, headers={**BROWSER_HEADERS, **htmx_headers})
|
||||
if getattr(resp, "status_code", 500) != 200:
|
||||
return []
|
||||
|
||||
soup = _parse_html(resp.text)
|
||||
chapters = []
|
||||
|
||||
for row in soup.select("div.flex.items-center"):
|
||||
a = row.find("a")
|
||||
if not a:
|
||||
continue
|
||||
href = a.get("href", "")
|
||||
|
||||
title_span = a.select_one("span.grow > span")
|
||||
title = title_span.get_text(strip=True) if title_span else ""
|
||||
|
||||
id_match = re.search(r"/chapters/([^/]+)", href)
|
||||
if not id_match:
|
||||
continue
|
||||
chapter_id = id_match.group(1)
|
||||
|
||||
num_match = re.search(r"(\d+(?:\.\d+)?)", title)
|
||||
chapter_num = num_match.group(1) if num_match else "0"
|
||||
|
||||
chapters.append({
|
||||
"id": chapter_id,
|
||||
"title": title,
|
||||
"chapter_number": chapter_num,
|
||||
})
|
||||
|
||||
# Chapters come in descending order from the site — reverse to ascending
|
||||
chapters.reverse()
|
||||
return chapters
|
||||
except Exception as e:
|
||||
print(f"[WeebCentral] Chapter list error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page images
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _get_pages(chapter_id: str):
|
||||
"""Returns a list of image URLs for the given chapter ID."""
|
||||
url = f"{BASE_URL}/chapters/{chapter_id}/images?is_prev=False&reading_style=long_strip"
|
||||
htmx_headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Current-URL": f"{BASE_URL}/chapters/{chapter_id}",
|
||||
"Referer": f"{BASE_URL}/chapters/{chapter_id}",
|
||||
}
|
||||
|
||||
try:
|
||||
resp = await _get(url, headers={**BROWSER_HEADERS, **htmx_headers})
|
||||
if getattr(resp, "status_code", 500) != 200:
|
||||
return []
|
||||
|
||||
soup = _parse_html(resp.text)
|
||||
imgs = soup.select("section.flex-1 img") or soup.find_all("img")
|
||||
pages = [img.get("src") for img in imgs if img.get("src")]
|
||||
return pages
|
||||
except Exception as e:
|
||||
print(f"[WeebCentral] Page fetch error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API — called by app.py
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _resolve_series_id(mal_id: int):
|
||||
"""Shared helper: MAL ID → WeebCentral series ID, or None."""
|
||||
romaji, english = await _get_mal_titles(mal_id)
|
||||
if not romaji and not english:
|
||||
print("[WeebCentral] Could not resolve titles from MAL.")
|
||||
return None
|
||||
|
||||
for title in filter(None, [english, romaji]):
|
||||
results = await _search(title)
|
||||
if results:
|
||||
sid = results[0]["id"]
|
||||
print(f"[WeebCentral] Matched series '{results[0]['title']}' (id={sid})")
|
||||
return sid
|
||||
print(f"[WeebCentral] No results for '{title}', trying next…")
|
||||
|
||||
print(f"[WeebCentral] No series found for MAL ID {mal_id}.")
|
||||
return None
|
||||
|
||||
|
||||
async def get_chapters(mal_id: int):
|
||||
"""
|
||||
Called by app.py /chapters/{mal_id}.
|
||||
Returns a list of chapter dicts compatible with the app's module interface:
|
||||
[{"title", "url", "chapter_number", "is_external"}, ...]
|
||||
Sorted descending (newest first), or None on failure.
|
||||
"""
|
||||
print(f"[WeebCentral] get_chapters called — MAL {mal_id}")
|
||||
|
||||
series_id = await _resolve_series_id(mal_id)
|
||||
if not series_id:
|
||||
return None
|
||||
|
||||
raw = await _get_chapters_for_series(series_id)
|
||||
if not raw:
|
||||
return None
|
||||
|
||||
# Expose the chapter_id inside `url` so get_chapter_images can re-use it
|
||||
# without a second search round-trip (format: "wc:{series_id}:{chapter_id}")
|
||||
formatted = []
|
||||
for ch in raw:
|
||||
formatted.append({
|
||||
"title": ch["title"],
|
||||
"url": f"wc:{series_id}:{ch['id']}",
|
||||
"chapter_number": ch["chapter_number"],
|
||||
"is_external": False,
|
||||
})
|
||||
|
||||
# Return descending (newest first) — matches convention used by comix
|
||||
formatted.sort(key=lambda x: _safe_float(x["chapter_number"]), reverse=True)
|
||||
return formatted
|
||||
|
||||
|
||||
def _safe_float(v):
|
||||
try:
|
||||
return float(v)
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
|
||||
|
||||
async def get_chapter_images(mal_id: int, chapter_num: str):
|
||||
"""
|
||||
Called by app.py /retrieve/{mal_id}/{chapter_num}.
|
||||
Reuses get_chapters() so the MAL -> search round-trip is not repeated.
|
||||
Returns a list of image URL strings, or None on failure.
|
||||
"""
|
||||
print(f"[WeebCentral] get_chapter_images called -- MAL {mal_id}, chapter {chapter_num}")
|
||||
|
||||
# 1. Get full chapter list (handles MAL lookup + search internally)
|
||||
chapters = await get_chapters(mal_id)
|
||||
if not chapters:
|
||||
print("[WeebCentral] Chapter list is empty.")
|
||||
return None
|
||||
|
||||
# 2. Find the target chapter by number
|
||||
target = None
|
||||
try:
|
||||
target_f = float(chapter_num)
|
||||
except (ValueError, TypeError):
|
||||
target_f = None
|
||||
|
||||
for ch in chapters:
|
||||
if target_f is not None:
|
||||
try:
|
||||
if float(ch["chapter_number"]) == target_f:
|
||||
target = ch
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if ch["chapter_number"] == str(chapter_num):
|
||||
target = ch
|
||||
break
|
||||
|
||||
if not target:
|
||||
print(f"[WeebCentral] Chapter {chapter_num} not found.")
|
||||
return None
|
||||
|
||||
# 3. Unpack chapter ID from url field ("wc:{series_id}:{chapter_id}")
|
||||
try:
|
||||
_, _series_id, chapter_id = target["url"].split(":")
|
||||
except ValueError:
|
||||
print(f"[WeebCentral] Malformed url field: {target['url']}")
|
||||
return None
|
||||
|
||||
print(f"[WeebCentral] Fetching pages for: {target['title']} (id={chapter_id})")
|
||||
|
||||
# 4. Fetch page images
|
||||
pages = await _get_pages(chapter_id)
|
||||
print(f"[WeebCentral] Found {len(pages)} pages." if pages else "[WeebCentral] No pages extracted.")
|
||||
return pages if pages else None
|
||||
@@ -0,0 +1,225 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
# Configure Console Logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger("WeebCentralTest")
|
||||
|
||||
LOG_FILE = "responses_test.txt"
|
||||
|
||||
def log_to_file(url, method, status, req_headers, req_body, resp_text):
|
||||
"""Logs full request and response details to responses_test.txt."""
|
||||
try:
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||||
f.write("="*100 + "\n")
|
||||
f.write(f"URL: {url}\n")
|
||||
f.write(f"METHOD: {method}\n")
|
||||
f.write(f"STATUS: {status}\n")
|
||||
f.write("-" * 40 + "\n")
|
||||
f.write("REQUEST HEADERS:\n")
|
||||
for k, v in req_headers.items():
|
||||
f.write(f" {k}: {v}\n")
|
||||
if req_body:
|
||||
f.write("-" * 40 + "\n")
|
||||
f.write(f"REQUEST BODY: {req_body}\n")
|
||||
f.write("-" * 40 + "\n")
|
||||
f.write("RESPONSE BODY:\n")
|
||||
f.write(resp_text if resp_text else "[EMPTY BODY]")
|
||||
f.write("\n" + "="*100 + "\n\n")
|
||||
except Exception as e:
|
||||
print(f"Logging error: {e}")
|
||||
|
||||
class WeebCentralProvider:
|
||||
def __init__(self):
|
||||
self.base_url = "https://weebcentral.com"
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
})
|
||||
|
||||
def get_mal_titles(self, mal_id):
|
||||
"""Fetches titles from MyAnimeList API."""
|
||||
logger.info(f"Fetching MAL metadata for ID: {mal_id}")
|
||||
url = f"https://api.jikan.moe/v4/manga/{mal_id}"
|
||||
resp = requests.get(url)
|
||||
|
||||
# Log MAL response too
|
||||
log_to_file(url, "GET", resp.status_code, resp.request.headers, None, resp.text)
|
||||
|
||||
if resp.status_code != 200:
|
||||
logger.error(f"MAL API failed with status {resp.status_code}")
|
||||
return None, None
|
||||
|
||||
data = resp.json().get('data', {})
|
||||
romaji = data.get('title')
|
||||
english = data.get('title_english')
|
||||
return romaji, english
|
||||
|
||||
def search(self, query: str):
|
||||
if not query: return []
|
||||
logger.info(f"--- Searching WeebCentral for: '{query}' ---")
|
||||
search_url = f"{self.base_url}/search/simple?location=main"
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"HX-Request": "true",
|
||||
"HX-Trigger": "quick-search-input",
|
||||
"HX-Trigger-Name": "text",
|
||||
"HX-Target": "quick-search-result",
|
||||
"HX-Current-URL": f"{self.base_url}/",
|
||||
}
|
||||
data = {"text": query}
|
||||
|
||||
response = self.session.post(search_url, headers=headers, data=data)
|
||||
|
||||
# Log Search details
|
||||
log_to_file(search_url, "POST", response.status_code, {**self.session.headers, **headers}, data, response.text)
|
||||
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
results = []
|
||||
items = soup.select("a")
|
||||
|
||||
for item in items:
|
||||
link = item.get("href", "")
|
||||
if "/series/" not in link: continue
|
||||
|
||||
title_el = item.select_one(".flex-1")
|
||||
title = title_el.get_text(strip=True) if title_el else "Unknown"
|
||||
|
||||
id_match = re.search(r"/series/([^/]+)", link)
|
||||
manga_id = id_match.group(1) if id_match else None
|
||||
|
||||
# Logic: If query is in the site title, or vice-versa
|
||||
if manga_id and (query.lower() in title.lower() or title.lower() in query.lower()):
|
||||
results.append({
|
||||
"id": manga_id,
|
||||
"title": title,
|
||||
"url": link
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
def find_chapters(self, manga_id: str):
|
||||
logger.info(f"--- Finding Chapters for Manga ID: {manga_id} ---")
|
||||
chapter_url = f"{self.base_url}/series/{manga_id}/full-chapter-list"
|
||||
|
||||
headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Target": "chapter-list",
|
||||
"HX-Current-URL": f"{self.base_url}/series/{manga_id}",
|
||||
"Referer": f"{self.base_url}/series/{manga_id}",
|
||||
}
|
||||
|
||||
response = self.session.get(chapter_url, headers=headers)
|
||||
log_to_file(chapter_url, "GET", response.status_code, {**self.session.headers, **headers}, None, response.text)
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
chapters = []
|
||||
rows = soup.select("div.flex.items-center")
|
||||
|
||||
for row in rows:
|
||||
a = row.find("a")
|
||||
if not a: continue
|
||||
|
||||
href = a.get("href", "")
|
||||
title_span = a.select_one("span.grow > span")
|
||||
title = title_span.get_text(strip=True) if title_span else ""
|
||||
|
||||
id_match = re.search(r"/chapters/([^/]+)", href)
|
||||
if not id_match: continue
|
||||
|
||||
num_match = re.search(r"(\d+(?:\.\d+)?)", title)
|
||||
chapter_num = num_match.group(1) if num_match else "0"
|
||||
|
||||
chapters.append({
|
||||
"id": id_match.group(1),
|
||||
"title": title,
|
||||
"chapter": chapter_num
|
||||
})
|
||||
|
||||
chapters.reverse()
|
||||
return chapters
|
||||
|
||||
def find_chapter_pages(self, chapter_id: str):
|
||||
logger.info(f"--- Finding Pages for Chapter ID: {chapter_id} ---")
|
||||
url = f"{self.base_url}/chapters/{chapter_id}/images?is_prev=False&reading_style=long_strip"
|
||||
|
||||
headers = {
|
||||
"HX-Request": "true",
|
||||
"HX-Current-URL": f"{self.base_url}/chapters/{chapter_id}",
|
||||
"Referer": f"{self.base_url}/chapters/{chapter_id}",
|
||||
}
|
||||
|
||||
response = self.session.get(url, headers=headers)
|
||||
log_to_file(url, "GET", response.status_code, {**self.session.headers, **headers}, None, response.text)
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
images = soup.select("section.flex-1 img") or soup.find_all("img")
|
||||
|
||||
pages = []
|
||||
for idx, img in enumerate(images):
|
||||
src = img.get("src")
|
||||
if src: pages.append(src)
|
||||
|
||||
return pages
|
||||
|
||||
def run_test():
|
||||
TEST_MAL_ID = 23390 # Attack on Titan
|
||||
|
||||
if os.path.exists(LOG_FILE):
|
||||
os.remove(LOG_FILE)
|
||||
|
||||
provider = WeebCentralProvider()
|
||||
|
||||
# 1. Get titles from MAL
|
||||
romaji, english = provider.get_mal_titles(TEST_MAL_ID)
|
||||
if not romaji and not english:
|
||||
logger.error("Could not fetch titles from MAL.")
|
||||
return
|
||||
|
||||
# 2. Try Search (First English, then Romaji)
|
||||
search_results = []
|
||||
for title in [english, romaji]:
|
||||
if not title: continue
|
||||
search_results = provider.search(title)
|
||||
if search_results:
|
||||
break
|
||||
logger.warning(f"No results for '{title}', trying next title...")
|
||||
|
||||
if not search_results:
|
||||
logger.error(f"Failed to find manga on WeebCentral for ID {TEST_MAL_ID}. Check {LOG_FILE}")
|
||||
return
|
||||
|
||||
target = search_results[0]
|
||||
logger.info(f"Matched: {target['title']} (ID: {target['id']})")
|
||||
|
||||
# 3. Get Chapters
|
||||
chapters = provider.find_chapters(target['id'])
|
||||
if not chapters:
|
||||
logger.error("Chapter list extraction failed.")
|
||||
return
|
||||
|
||||
logger.info(f"Found {len(chapters)} chapters. Fetching pages for last chapter: {chapters[-1]['title']}")
|
||||
|
||||
# 4. Get Pages
|
||||
pages = provider.find_chapter_pages(chapters[-1]['id'])
|
||||
|
||||
if pages:
|
||||
logger.info(f"SUCCESS! Found {len(pages)} pages.")
|
||||
logger.info(f"Full transaction logs: {os.path.abspath(LOG_FILE)}")
|
||||
else:
|
||||
logger.error("Failed to extract pages.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_test()
|
||||
Reference in new issue
Block a user