diff --git a/.agent/workflows/coding-rules.md b/.agent/workflows/coding-rules.md index 152f69b..0c64baa 100644 --- a/.agent/workflows/coding-rules.md +++ b/.agent/workflows/coding-rules.md @@ -14,6 +14,10 @@ description: anime_downloader 플러그인 코딩 규칙 - 로거 메시지는 영어/한국어 혼용 가능 - 에러 메시지는 간결하게 +## 커맨드 실행 규칙 +- `rm`, `mv`, `cp` 등 파일을 변조하거나 삭제할 수 있는 파괴적인 명령은 반드시 사용자 승인 필요 (`SafeToAutoRun: false`) +- `cat`, `grep`, `sed`, `ps`, `lsof`, `curl` (단순 조회용) 등 부수 효과가 없는 조회성 명령은 자동 실행 허용 (`SafeToAutoRun: true`) + ## FlaskFarm 관련 - flaskfarm 코어 소스 수정 최소화 (외부 프로젝트) - 플러그인 내에서 해결 가능한 것은 플러그인에서 처리 diff --git a/README.md b/README.md index 327f329..1993c64 100644 --- a/README.md +++ b/README.md @@ -72,20 +72,21 @@ ## 📝 변경 이력 (Changelog) ### v0.5.0 (2026-01-03) -- **Zendriver Daemon 최적화 (성능 대폭 향상)**: - - **브라우저 상시 대기 (Daemon)**: 매 요청마다 브라우저를 새로 띄우지 않고 백그라운드 데몬 프로세스 활용 - - **우회 속도 개선**: 클라우드플레어 우회 속도 최적화 (기존 4~6초 → **2~3초**) - - **안정성**: 브라우저 프리징 시 자동 재시작 및 HTTP API 기반 통신 -- **Python 3.14 정식 지원**: - - Flask 3.1.2, SQLAlchemy 2.0.45, gevent 25.9.1 등 최신 라이브러리 호환성 확보 - - gevent fork 시 발생하는 `AssertionError` 경고 완전 제거 (stderr 리다이렉션 기법 적용) -- **UI/UX 편의성 강화**: - - **Enter 키 검색**: Ohli24, Anilife, Linkkf 분석 페이지에서 검색창 Enter 키 입력 지원 - - **모바일 큐 개선**: 모바일 화면에서 진행바 위에 텍스트로 진행률 표시 (가독성 향상) -- **버그 수정 및 안정성**: - - **대소문자 구분 없는 파일 체크**: 파일 존재 확인 시 대소문자 차이로 인한 중복 다운로드 해결 - - **타입 힌트 리팩토링**: `mod_ohli24.py` 전체 모듈 타입 힌트 적용으로 안정성 증대 - - **Zendriver 자동 설치**: 환경에 Zendriver가 없을 경우 첫 실행 시 자동 설치 로직 추가 +- **Ohli24 비디오 플레이어 UI 전면 개편**: + - **프리미엄 글래스모피즘 디자인**: 플레이어 모달 및 플레이리스트 컨트롤에 투명 유리 테마 적용 + - **Video.js 8.10.0 업그레이드**: 최신 엔진으로 안정성 및 재생 성능 최적화 + - **"Scale to Fill" (줌) 기능**: 모바일 전체화면 시 검은 여백을 없애고 화면을 가득 채우는 기능 추가 + - **중앙 재생 버튼 개선**: 모바일에 최적화된 대형 중앙 재생 버튼 및 아이콘 정렬 수정 +- **Anilife / Ohli24 검색 엔진 고도화**: + - **Zendriver Daemon 최적화**: 매 요청마다 브라우저를 띄우지 않고 백그라운드 프로세스 활용 (응답 속도 2~3초로 단축) + - **완결 카테고리 & 년도별 필터링**: Ohli24 검색에 '완결' 버튼 추가 및 년도별(2020~2025) 상세 필터링 지원 + - **모던 로딩 UI**: 시각적으로 세련된 멀티 링 프리로더 및 글래스모피즘 AJAX 스피너 도입 +- **Python 3.14 및 최신 스택 지원**: + - Flask 3.1.2, SQLAlchemy 2.0.45 등 최신 라이브러리 호환성 확보 및 `AssertionError` 경고 제거 +- **안정성 및 UX 강화**: + - **Enter 키 검색**: 모든 분석/검색 페이지에서 Enter 키 지원 + - **Zendriver 자동 설치**: 환경에 패키지가 없을 경우 실행 시 자동 설치 + - **타입 힌트 리팩토링**: `mod_ohli24.py`, `mod_anilife.py` 전반에 엄격한 타입 힌트 적용 ### v0.4.18 (2026-01-03) - **Ohli24 4단계 폴백 체인 구현**: `curl_cffi` → `cloudscraper` → `Zendriver` → `Camoufox` diff --git a/info.yaml b/info.yaml index 18d52cf..9a8c274 100644 --- a/info.yaml +++ b/info.yaml @@ -1,5 +1,5 @@ title: "애니 다운로더" -version: "0.5.0" +version: "0.5.1" package_name: "anime_downloader" developer: "projectdx" description: "anime downloader" diff --git a/lib/crawler.py b/lib/crawler.py index 61b3fdc..65f398e 100644 --- a/lib/crawler.py +++ b/lib/crawler.py @@ -72,7 +72,7 @@ class Crawler: referer: str = None, engine: str = "chrome", stealth: bool = False, - ): + ) -> str: try: from playwright.async_api import async_playwright # from playwright.sync_api import sync_playwright diff --git a/lib/zendriver_daemon.py b/lib/zendriver_daemon.py index 5c5d829..d804ba7 100644 --- a/lib/zendriver_daemon.py +++ b/lib/zendriver_daemon.py @@ -15,7 +15,21 @@ import os import traceback from http.server import HTTPServer, BaseHTTPRequestHandler from threading import Thread, Lock -from typing import Any, Optional +from typing import Any, Optional, Dict, List, Type, cast + +# 터미널 및 파일로 로그 출력 설정 +LOG_FILE: str = "/tmp/zendriver_daemon.log" + +def log_debug(msg: str) -> None: + """타임스탬프와 함께 로그 출력 및 파일 저장""" + timestamp: str = time.strftime("%Y-%m-%d %H:%M:%S") + formatted_msg: str = f"[{timestamp}] {msg}" + print(formatted_msg, file=sys.stderr) + try: + with open(LOG_FILE, "a", encoding="utf-8") as f: + f.write(formatted_msg + "\n") + except Exception: + pass DAEMON_PORT: int = 19876 browser: Optional[Any] = None @@ -27,20 +41,26 @@ class ZendriverHandler(BaseHTTPRequestHandler): """HTTP 요청 핸들러""" def log_message(self, format: str, *args: Any) -> None: - # 로그 출력 억제 + """로그 출력 억제""" pass def do_POST(self) -> None: + """POST 요청 처리 (/fetch, /health, /shutdown)""" global browser, loop if self.path == "/fetch": try: - content_length = int(self.headers['Content-Length']) - body = self.rfile.read(content_length).decode('utf-8') - data: dict = json.loads(body) + content_length: int = int(self.headers.get('Content-Length', 0)) + if content_length == 0: + self._send_json(400, {"success": False, "error": "Empty body"}) + return + + body_bytes: bytes = self.rfile.read(content_length) + body: str = body_bytes.decode('utf-8') + data: Dict[str, Any] = json.loads(body) url: Optional[str] = data.get("url") - timeout: int = data.get("timeout", 30) + timeout: int = cast(int, data.get("timeout", 30)) if not url: self._send_json(400, {"success": False, "error": "Missing 'url' parameter"}) @@ -48,15 +68,21 @@ class ZendriverHandler(BaseHTTPRequestHandler): # 비동기 fetch 실행 if loop: - result = asyncio.run_coroutine_threadsafe( + future = asyncio.run_coroutine_threadsafe( fetch_with_browser(url, timeout), loop - ).result(timeout=timeout + 10) + ) + result: Dict[str, Any] = future.result(timeout=timeout + 15) self._send_json(200, result) else: self._send_json(500, {"success": False, "error": "Event loop not ready"}) except Exception as e: - self._send_json(500, {"success": False, "error": str(e), "traceback": traceback.format_exc()}) + log_debug(f"[Handler] Error: {e}\n{traceback.format_exc()}") + self._send_json(500, { + "success": False, + "error": str(e) or e.__class__.__name__, + "traceback": traceback.format_exc() + }) elif self.path == "/health": self._send_json(200, {"status": "ok", "browser_ready": browser is not None}) @@ -69,16 +95,21 @@ class ZendriverHandler(BaseHTTPRequestHandler): self._send_json(404, {"error": "Not found"}) def do_GET(self) -> None: + """GET 요청 처리 (/health)""" if self.path == "/health": self._send_json(200, {"status": "ok", "browser_ready": browser is not None}) else: self._send_json(404, {"error": "Not found"}) - def _send_json(self, status_code: int, data: dict) -> None: - self.send_response(status_code) - self.send_header('Content-Type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8')) + def _send_json(self, status_code: int, data: Dict[str, Any]) -> None: + """JSON 응답 전송""" + try: + self.send_response(status_code) + self.send_header('Content-Type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8')) + except Exception as e: + log_debug(f"[Handler] Failed to send response: {e}") async def ensure_browser() -> Any: @@ -89,21 +120,23 @@ async def ensure_browser() -> Any: if browser is None: try: import zendriver as zd + log_debug("[ZendriverDaemon] Starting new browser instance...") + # zendriver.start()는 브라우저를 시작하고 첫 번째 페이지를 반환할 수 있음 browser = await zd.start(headless=True) - print(f"[ZendriverDaemon] Browser started", file=sys.stderr) + log_debug("[ZendriverDaemon] Browser started successfully") except Exception as e: - print(f"[ZendriverDaemon] Failed to start browser: {e}", file=sys.stderr) + log_debug(f"[ZendriverDaemon] Failed to start browser: {e}") browser = None raise return browser -async def fetch_with_browser(url: str, timeout: int = 30) -> dict: - """상시 대기 브라우저로 HTML 페칭""" +async def fetch_with_browser(url: str, timeout: int = 30) -> Dict[str, Any]: + """상시 대기 브라우저로 HTML 페칭 (탭 유지 방식)""" global browser - result: dict = {"success": False, "html": "", "elapsed": 0} + result: Dict[str, Any] = {"success": False, "html": "", "elapsed": 0.0} start_time: float = time.time() try: @@ -113,38 +146,54 @@ async def fetch_with_browser(url: str, timeout: int = 30) -> dict: result["error"] = "Browser not available" return result - # 새 탭에서 페이지 로드 - page = await browser.get(url) + # zendriver의 browser.get(url)은 이미 열린 탭이 있으면 거기서 열려고 시도함. + # 하지만 모든 탭이 닫히면 StopIteration이 발생할 수 있음. + log_debug(f"[ZendriverDaemon] Fetching URL: {url}") - # 페이지 로드 대기 - await asyncio.sleep(1.5) - - # HTML 추출 - html: str = await page.get_content() - elapsed: float = time.time() - start_time - - if html and len(html) > 100: - result.update({ - "success": True, - "html": html, - "elapsed": round(elapsed, 2) - }) - else: - result["error"] = f"Short response: {len(html) if html else 0} bytes" - result["elapsed"] = round(elapsed, 2) - - # 탭 닫기 (브라우저는 유지) + # StopIteration 방지를 위해 페이지 이동 시도 try: - await page.close() - except: - pass + # browser.get(url)은 새 탭을 열거나 기존 탭을 사용함 + page: Any = await browser.get(url) - except Exception as e: - result["error"] = str(e) + # 페이지 로드 대기 (충분히 대기) + await asyncio.sleep(2.0) + + # HTML 추출 + html_content: str = await page.get_content() + elapsed: float = time.time() - start_time + + if html_content and len(html_content) > 100: + result.update({ + "success": True, + "html": html_content, + "elapsed": round(elapsed, 2) + }) + log_debug(f"[ZendriverDaemon] Fetch success in {elapsed:.2f}s (Length: {len(html_content)})") + else: + result["error"] = f"Short response: {len(html_content) if html_content else 0} bytes" + result["elapsed"] = round(elapsed, 2) + log_debug(f"[ZendriverDaemon] Fetch failure: Short response ({len(html_content) if html_content else 0} bytes)") + + # 여기서 page.close()를 하지 않음! (탭을 하나라도 남겨두어야 StopIteration 방지 가능) + # 대신 나중에 탭이 너무 많아지면 정리하는 로직 필요할 수 있음 + + except StopIteration: + log_debug("[ZendriverDaemon] StopIteration caught during browser.get, resetting browser") + browser = None + raise + + except BaseException as e: + # StopIteration 등 모든 예외 캐치 + err_msg: str = str(e) or e.__class__.__name__ + result["error"] = err_msg result["elapsed"] = round(time.time() - start_time, 2) + log_debug(f"[ZendriverDaemon] Exception during fetch: {err_msg}") + if not isinstance(e, asyncio.CancelledError): + log_debug(traceback.format_exc()) # 브라우저 오류 시 재시작 플래그 - if "browser" in str(e).lower() or "closed" in str(e).lower(): + if "browser" in err_msg.lower() or "closed" in err_msg.lower() or "stopiteration" in err_msg.lower(): + log_debug("[ZendriverDaemon] Resetting browser due to critical error") browser = None return result @@ -155,11 +204,13 @@ async def run_async_loop() -> None: global loop loop = asyncio.get_event_loop() + log_debug("[ZendriverDaemon] Async loop started") + # 브라우저 미리 시작 try: await ensure_browser() - except: - pass + except Exception as e: + log_debug(f"[ZendriverDaemon] Initial browser start failed: {e}") # 루프 유지 while True: @@ -168,31 +219,37 @@ async def run_async_loop() -> None: def run_server() -> None: """HTTP 서버 실행""" - server = HTTPServer(('127.0.0.1', DAEMON_PORT), ZendriverHandler) - print(f"[ZendriverDaemon] Starting on port {DAEMON_PORT}", file=sys.stderr) - server.serve_forever() + try: + server: HTTPServer = HTTPServer(('127.0.0.1', DAEMON_PORT), ZendriverHandler) + log_debug(f"[ZendriverDaemon] HTTP server starting on port {DAEMON_PORT}") + server.serve_forever() + except Exception as e: + log_debug(f"[ZendriverDaemon] HTTP server error: {e}") def signal_handler(sig: int, frame: Any) -> None: """종료 시그널 처리""" global browser - print("\n[ZendriverDaemon] Shutting down...", file=sys.stderr) + log_debug("\n[ZendriverDaemon] Shutdown signal received") if browser: try: - asyncio.run(browser.stop()) - except: - pass + if loop and loop.is_running(): + future = asyncio.run_coroutine_threadsafe(browser.stop(), loop) + future.result(timeout=5) + except Exception as e: + log_debug(f"[ZendriverDaemon] Error during browser stop: {e}") sys.exit(0) if __name__ == "__main__": + # 시그널 핸들러 등록 signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # 비동기 루프를 별도 스레드에서 실행 - async_thread = Thread(target=lambda: asyncio.run(run_async_loop()), daemon=True) + async_thread: Thread = Thread(target=lambda: asyncio.run(run_async_loop()), daemon=True) async_thread.start() # HTTP 서버 실행 (메인 스레드) - time.sleep(1) # 브라우저 시작 대기 + time.sleep(2) # 초기화 대기 run_server() diff --git a/mod_anilife.py b/mod_anilife.py index 3b77b37..5907443 100644 --- a/mod_anilife.py +++ b/mod_anilife.py @@ -210,25 +210,34 @@ class LogicAniLife(AnimeModuleBase): is_stealth: bool = False, timeout: int = 5, headless: bool = False, - ): + ) -> str: + import time + start_time = time.time() data = "" try: - print("cloudflare protection bypass ==================") - # print(self) - # return LogicAniLife.get_html_cloudflare(url) - # return self.get_html_selenium(url=url, referer=referer, is_stealth=is_stealth) - # url: str, - # headless: bool = False, - # referer: str = None, - # engine: str = "chrome", - # stealth: bool = False, - # return asyncio.run(LogicAniLife.get_html_playwright(url, engine="chrome", headless=True)) - return asyncio.run( - LogicAniLife.get_html_playwright( + # --- Zendriver Daemon 최적 최적화 (v0.5.0) --- + from .mod_ohli24 import LogicOhli24 + if LogicOhli24.is_zendriver_daemon_running(): + logger.info(f"[Anilife] Trying Zendriver Daemon: {url}") + daemon_res = LogicOhli24.fetch_via_daemon(url, timeout=30) + elapsed = time.time() - start_time + if daemon_res.get("success") and daemon_res.get("html"): + logger.info(f"[Anilife] Daemon success in {elapsed:.2f}s, HTML len: {len(daemon_res['html'])}") + return daemon_res["html"] + else: + logger.warning(f"[Anilife] Daemon failed in {elapsed:.2f}s: {daemon_res.get('error', 'Unknown')}") + + # --- Fallback: Playwright --- + logger.info("[Anilife] Falling back to Playwright...") + from .lib.crawler import Crawler + res = asyncio.run( + Crawler().get_html_playwright( url, engine="chromium", headless=headless ) ) - # return LogicAniLife.get_html_playwright_sync(url, engine="chrome", headless=True) + elapsed = time.time() - start_time + logger.info(f"[Anilife] Playwright finished in {elapsed:.2f}s") + return res except Exception as e: logger.error("Exception:%s", e) @@ -543,7 +552,7 @@ class LogicAniLife(AnimeModuleBase): page = request.form["page"] try: data = self.get_anime_info(cate, page) - logger.debug(data) + # logger.debug(data) if data is not None: return jsonify( {"ret": "success", "cate": cate, "page": page, "data": data} @@ -829,7 +838,7 @@ class LogicAniLife(AnimeModuleBase): return True # 시리즈 정보를 가져오는 함수 (cloudscraper 버전) - def get_series_info(self, code): + def get_series_info(self, code: str) -> Dict[str, Any]: try: if code.isdigit(): url = P.ModelSetting.get("anilife_url") + "/detail/id/" + code @@ -838,26 +847,14 @@ class LogicAniLife(AnimeModuleBase): logger.debug("get_series_info()::url > %s", url) - # cloudscraper를 사용하여 Cloudflare 우회 - scraper = cloudscraper.create_scraper( - browser={ - "browser": "chrome", - "platform": "windows", - "desktop": True - } - ) + # self.get_html을 사용하여 Zendriver Daemon 우선 시도 + html_content = self.get_html(url) - # 리다이렉트 자동 처리 (숫자 ID → UUID 페이지로 리다이렉트됨) - response = scraper.get(url, timeout=15, allow_redirects=True) - - if response.status_code != 200: - logger.error(f"Failed to fetch series info: HTTP {response.status_code}") - return {"ret": "error", "log": f"HTTP {response.status_code}"} - - # 최종 URL 로깅 (리다이렉트된 경우) - logger.debug(f"Final URL after redirect: {response.url}") + if not html_content: + logger.error(f"Failed to fetch series info: Empty content") + return {"ret": "error", "log": "Empty content"} - tree = html.fromstring(response.text) + tree = html.fromstring(html_content) # tree = html.fromstring(response_data) # logger.debug(response_data) @@ -992,7 +989,7 @@ class LogicAniLife(AnimeModuleBase): return {"ret": "exception", "log": str(e)} @staticmethod - def get_real_link(url): + def get_real_link(url: str) -> str: response = requests.get(url) if response.history: print("Request was redirected") @@ -1004,8 +1001,7 @@ class LogicAniLife(AnimeModuleBase): else: print("Request was not redirected") - @staticmethod - def get_anime_info(cate, page): + def get_anime_info(self, cate: str, page: str) -> Dict[str, Any]: logger.debug(f"get_anime_info() routine") logger.debug(f"cate:: {cate}") wrapper_xpath = '//div[@class="bsx"]' @@ -1028,35 +1024,26 @@ class LogicAniLife(AnimeModuleBase): + "/vodtype/categorize/Movie/" + page ) - # cate == "complete": logger.info("url:::> %s", url) - data = {} + data: Dict[str, Any] = {} - # cloudscraper를 사용하여 Cloudflare 우회 - scraper = cloudscraper.create_scraper( - browser={ - "browser": "chrome", - "platform": "windows", - "desktop": True - } - ) + url = url.split("?")[0] + html_content: str = self.get_html(url) - response = scraper.get(url, timeout=15) - - if response.status_code != 200: - logger.error(f"Failed to fetch anime info: HTTP {response.status_code}") - return {"ret": "error", "log": f"HTTP {response.status_code}"} + if not html_content: + logger.error("Failed to fetch anime info: Empty content") + return {"ret": "error", "log": "Empty content"} - LogicAniLife.episode_url = response.url - logger.info(response.url) - logger.debug(LogicAniLife.episode_url) + LogicAniLife.episode_url = url + # logger.info(response.url) + # logger.debug(LogicAniLife.episode_url) - soup_text = BeautifulSoup(response.text, "lxml") + soup_text = BeautifulSoup(html_content, "lxml") - tree = html.fromstring(response.text) + tree = html.fromstring(html_content) tmp_items = tree.xpath(wrapper_xpath) - logger.debug(tmp_items) + # logger.debug(tmp_items) data["anime_count"] = len(tmp_items) data["anime_list"] = [] @@ -1115,7 +1102,7 @@ class LogicAniLife(AnimeModuleBase): # cloudscraper 버전 직접 사용 (외부 playwright API 서버 불필요) return self.get_search_result_v2(query, page, cate) - def get_search_result_v2(self, query, page, cate): + def get_search_result_v2(self, query: str, page: int, cate: str) -> Dict[str, Any]: """ anilife.live 검색 결과를 가져오는 함수 (cloudscraper 버전) 외부 playwright API 서버 없이 직접 cloudscraper를 사용 @@ -1135,22 +1122,14 @@ class LogicAniLife(AnimeModuleBase): logger.info("get_search_result_v2()::url> %s", url) data = {} - # cloudscraper를 사용하여 Cloudflare 우회 - scraper = cloudscraper.create_scraper( - browser={ - "browser": "chrome", - "platform": "windows", - "desktop": True - } - ) + # self.get_html을 사용하여 Zendriver Daemon 우선 시도 + html_content = self.get_html(url) - response = scraper.get(url, timeout=15) - - if response.status_code != 200: - logger.error(f"Failed to fetch search results: HTTP {response.status_code}") - return {"ret": "error", "log": f"HTTP {response.status_code}"} + if not html_content: + logger.error(f"Failed to fetch search results: Empty content") + return {"ret": "error", "log": "Empty content"} - tree = html.fromstring(response.text) + tree = html.fromstring(html_content) # 검색 결과 항목들 (div.bsx) tmp_items = tree.xpath('//div[@class="bsx"]') diff --git a/mod_ohli24.py b/mod_ohli24.py index 9c7854a..853e2f1 100644 --- a/mod_ohli24.py +++ b/mod_ohli24.py @@ -297,17 +297,19 @@ class LogicOhli24(AnimeModuleBase): self.current_data = data return jsonify({"ret": "success", "data": data, "code": code}) elif sub == "anime_list": - data = self.get_anime_info(cate, page) + sca = request.form.get("sca", None) + data = self.get_anime_info(cate, page, sca=sca) if isinstance(data, dict) and data.get("ret") == "error": return jsonify(data) - return jsonify({"ret": "success", "cate": cate, "page": page, "data": data}) + return jsonify({"ret": "success", "cate": cate, "page": page, "data": data, "sca": sca}) elif sub == "complete_list": logger.debug("cate:: %s", cate) page = request.form["page"] - data = self.get_anime_info(cate, page) + sca = request.form.get("sca", None) + data = self.get_anime_info(cate, page, sca=sca) if isinstance(data, dict) and data.get("ret") == "error": return jsonify(data) - return jsonify({"ret": "success", "cate": cate, "page": page, "data": data}) + return jsonify({"ret": "success", "cate": cate, "page": page, "data": data, "sca": sca}) elif sub == "search": query = request.form["query"] page = request.form["page"] @@ -1073,17 +1075,13 @@ class LogicOhli24(AnimeModuleBase): P.logger.error(traceback.format_exc()) return {"ret": "error", "log": str(e)} - def get_anime_info(self, cate: str, page: str) -> Dict[str, Any]: + def get_anime_info(self, cate: str, page: str, sca: Optional[str] = None) -> Dict[str, Any]: """카테고리별 애니메이션 목록 조회.""" - logger.debug(f"get_anime_info: cate={cate}, page={page}") + logger.debug(f"get_anime_info: cate={cate}, page={page}, sca={sca}") try: - if cate == "ing": - url = P.ModelSetting.get("ohli24_url") + "/bbs/board.php?bo_table=" + cate + "&page=" + page - elif cate == "movie": - url = P.ModelSetting.get("ohli24_url") + "/bbs/board.php?bo_table=" + cate + "&page=" + page - else: - url = P.ModelSetting.get("ohli24_url") + "/bbs/board.php?bo_table=" + cate + "&page=" + page - # cate == "complete": + url = P.ModelSetting.get("ohli24_url") + "/bbs/board.php?bo_table=" + cate + "&page=" + page + if sca: + url += "&sca=" + sca logger.info("url:::> %s", url) data = {} response_data = LogicOhli24.get_html(url, timeout=10) diff --git a/templates/anime_downloader_ohli24_list.html b/templates/anime_downloader_ohli24_list.html index 09e4445..95713b1 100644 --- a/templates/anime_downloader_ohli24_list.html +++ b/templates/anime_downloader_ohli24_list.html @@ -70,31 +70,39 @@