fix(cache): validate cached ohli24 html before reuse
- add basic html validation before reading or writing browse cache - purge invalid cached responses instead of reusing them for cache ttl - bump anime_downloader plugin version to 0.7.19 Co-Authored-By: First Fluke <our.first.fluke@gmail.com>
This commit is contained in:
@@ -84,6 +84,11 @@
|
|||||||
|
|
||||||
## 📝 변경 이력 (Changelog)
|
## 📝 변경 이력 (Changelog)
|
||||||
|
|
||||||
|
### v0.7.19 (2026-03-23)
|
||||||
|
- **Ohli24 캐시 검증 1차 적용**:
|
||||||
|
- 브라우징 캐시 사용 전 HTML 유효성 검사를 추가하여 차단 페이지나 비정상 응답이 10분간 재사용되지 않도록 했습니다.
|
||||||
|
- 목록/검색/상세 URL 유형별로 기본 시그니처를 검사하고, `Just a moment`, `Cloudflare`, `captcha` 등 차단 마커가 있으면 캐시를 즉시 폐기합니다.
|
||||||
|
|
||||||
### v0.7.18 (2026-03-23)
|
### v0.7.18 (2026-03-23)
|
||||||
- **Zendriver 업데이트**:
|
- **Zendriver 업데이트**:
|
||||||
- 자동 설치 대상 버전을 `zendriver==0.15.3`으로 명시하여 환경별 최신 패치 차이로 인한 동작 편차를 줄였습니다.
|
- 자동 설치 대상 버전을 `zendriver==0.15.3`으로 명시하여 환경별 최신 패치 차이로 인한 동작 편차를 줄였습니다.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
title: "애니 다운로더"
|
title: "애니 다운로더"
|
||||||
version: 0.7.18
|
version: 0.7.19
|
||||||
package_name: "anime_downloader"
|
package_name: "anime_downloader"
|
||||||
developer: "projectdx"
|
developer: "projectdx"
|
||||||
description: "anime downloader"
|
description: "anime downloader"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import urllib
|
import urllib
|
||||||
import unicodedata
|
import unicodedata
|
||||||
@@ -2345,6 +2346,66 @@ class LogicOhli24(AnimeModuleBase):
|
|||||||
|
|
||||||
return response_data
|
return response_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_cache_page_type(url: str) -> str:
|
||||||
|
parsed = parse.urlparse(url)
|
||||||
|
|
||||||
|
if "/bbs/search.php" in parsed.path:
|
||||||
|
return "search"
|
||||||
|
if "/bbs/board.php" in parsed.path:
|
||||||
|
if "wr_id=" in parsed.query:
|
||||||
|
return "detail"
|
||||||
|
return "list"
|
||||||
|
if "/e/" in parsed.path or "/c/" in parsed.path:
|
||||||
|
return "detail"
|
||||||
|
return "generic"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _is_valid_cached_html(cls, url: str, html_text: str) -> Tuple[bool, str]:
|
||||||
|
if not html_text:
|
||||||
|
return False, "empty"
|
||||||
|
|
||||||
|
html_text = html_text.strip()
|
||||||
|
if len(html_text) < 200:
|
||||||
|
return False, "too_short"
|
||||||
|
|
||||||
|
lowered = html_text.lower()
|
||||||
|
blocked_markers = [
|
||||||
|
"just a moment",
|
||||||
|
"access denied",
|
||||||
|
"captcha",
|
||||||
|
"attention required",
|
||||||
|
"enable javascript",
|
||||||
|
"cf-browser-verification",
|
||||||
|
"cloudflare",
|
||||||
|
"blocked",
|
||||||
|
"403 forbidden",
|
||||||
|
"error 403",
|
||||||
|
"error 404",
|
||||||
|
"error 500",
|
||||||
|
"too many requests",
|
||||||
|
]
|
||||||
|
for marker in blocked_markers:
|
||||||
|
if marker in lowered:
|
||||||
|
return False, f"blocked:{marker}"
|
||||||
|
|
||||||
|
if "<html" not in lowered or "</html>" not in lowered:
|
||||||
|
return False, "not_html_document"
|
||||||
|
|
||||||
|
page_type = cls._get_cache_page_type(url)
|
||||||
|
markers_by_type = {
|
||||||
|
"list": ["list-row", "post-title", "img-item", "board-list", "list-wrap"],
|
||||||
|
"search": ["list-row", "post-title", "img-item", "search.php", "board-list"],
|
||||||
|
"detail": ["item-subject", 'itemprop="headline"', 'itemprop="image"', "view-wrap", "serial-movie-wrap"],
|
||||||
|
"generic": ["<body", "og:title", "viewport"],
|
||||||
|
}
|
||||||
|
|
||||||
|
matched = [marker for marker in markers_by_type.get(page_type, []) if marker in lowered]
|
||||||
|
min_markers = 1 if page_type in ("list", "search", "generic") else 2
|
||||||
|
if len(matched) < min_markers:
|
||||||
|
return False, f"missing_markers:{page_type}"
|
||||||
|
|
||||||
|
return True, f"valid:{page_type}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_html_cached(url: str, **kwargs) -> str:
|
def get_html_cached(url: str, **kwargs) -> str:
|
||||||
@@ -2353,8 +2414,6 @@ class LogicOhli24(AnimeModuleBase):
|
|||||||
캐시 시간은 ohli24_cache_minutes 설정에 따름 (0=캐시 없음)
|
캐시 시간은 ohli24_cache_minutes 설정에 따름 (0=캐시 없음)
|
||||||
다운로드 루틴은 이 함수를 사용하지 않음 (세션/헤더 필요)
|
다운로드 루틴은 이 함수를 사용하지 않음 (세션/헤더 필요)
|
||||||
"""
|
"""
|
||||||
import hashlib
|
|
||||||
|
|
||||||
cache_minutes = int(P.ModelSetting.get("ohli24_cache_minutes") or 0)
|
cache_minutes = int(P.ModelSetting.get("ohli24_cache_minutes") or 0)
|
||||||
|
|
||||||
# 캐시 비활성화 시 바로 fetch
|
# 캐시 비활성화 시 바로 fetch
|
||||||
@@ -2377,11 +2436,17 @@ class LogicOhli24(AnimeModuleBase):
|
|||||||
try:
|
try:
|
||||||
with open(cache_file, 'r', encoding='utf-8') as f:
|
with open(cache_file, 'r', encoding='utf-8') as f:
|
||||||
cached_html = f.read()
|
cached_html = f.read()
|
||||||
if cached_html and len(cached_html) > 100:
|
is_valid, reason = LogicOhli24._is_valid_cached_html(url, cached_html)
|
||||||
logger.debug(f"[Cache HIT] {url[:60]}... (age: {cache_age:.0f}s)")
|
if is_valid:
|
||||||
|
logger.debug(f"[Cache HIT] {url[:60]}... (age: {cache_age:.0f}s, reason: {reason})")
|
||||||
return cached_html
|
return cached_html
|
||||||
else:
|
|
||||||
logger.debug(f"[Cache MISS] Cached content is empty or too short for {url[:60]}...")
|
logger.warning(f"[Cache INVALID] {url[:60]}... (reason: {reason})")
|
||||||
|
try:
|
||||||
|
os.remove(cache_file)
|
||||||
|
logger.debug(f"[Cache PURGE] Removed invalid cache for {url[:60]}...")
|
||||||
|
except OSError as purge_error:
|
||||||
|
logger.warning(f"[Cache PURGE ERROR] {purge_error}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[Cache READ ERROR] {e}")
|
logger.warning(f"[Cache READ ERROR] {e}")
|
||||||
else:
|
else:
|
||||||
@@ -2393,13 +2458,16 @@ class LogicOhli24(AnimeModuleBase):
|
|||||||
html = LogicOhli24.get_html(url, **kwargs)
|
html = LogicOhli24.get_html(url, **kwargs)
|
||||||
|
|
||||||
# 캐시에 저장 (유효한 HTML만)
|
# 캐시에 저장 (유효한 HTML만)
|
||||||
if html and len(html) > 100:
|
is_valid, reason = LogicOhli24._is_valid_cached_html(url, html)
|
||||||
|
if is_valid:
|
||||||
try:
|
try:
|
||||||
with open(cache_file, 'w', encoding='utf-8') as f:
|
with open(cache_file, 'w', encoding='utf-8') as f:
|
||||||
f.write(html)
|
f.write(html)
|
||||||
logger.debug(f"[Cache SAVE] {url[:60]}...")
|
logger.debug(f"[Cache SAVE] {url[:60]}... (reason: {reason})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[Cache WRITE ERROR] {e}")
|
logger.warning(f"[Cache WRITE ERROR] {e}")
|
||||||
|
elif html:
|
||||||
|
logger.warning(f"[Cache SKIP SAVE] {url[:60]}... (reason: {reason})")
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user