diff --git a/.agent/workflows/coding-rules.md b/.agent/workflows/coding-rules.md
deleted file mode 100644
index 0c64baa..0000000
--- a/.agent/workflows/coding-rules.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-description: anime_downloader 플러그인 코딩 규칙
----
-
-# 코딩 규칙
-
-## 타입 힌트
-- 모든 새 코드에 타입 힌트 필수 적용
-- 함수 파라미터와 반환값에 타입 지정
-- 클래스 변수에도 타입 지정
-
-## 코드 스타일
-- 한국어 주석 사용
-- 로거 메시지는 영어/한국어 혼용 가능
-- 에러 메시지는 간결하게
-
-## 커맨드 실행 규칙
-- `rm`, `mv`, `cp` 등 파일을 변조하거나 삭제할 수 있는 파괴적인 명령은 반드시 사용자 승인 필요 (`SafeToAutoRun: false`)
-- `cat`, `grep`, `sed`, `ps`, `lsof`, `curl` (단순 조회용) 등 부수 효과가 없는 조회성 명령은 자동 실행 허용 (`SafeToAutoRun: true`)
-
-## FlaskFarm 관련
-- flaskfarm 코어 소스 수정 최소화 (외부 프로젝트)
-- 플러그인 내에서 해결 가능한 것은 플러그인에서 처리
diff --git a/.gitignore b/.gitignore
index 7a1a4b9..7721381 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,6 +134,8 @@ dmypy.json
config.yaml
lib2/
.vscode/
+.agents/
+.agent/
memo.txt
*.zip
flaskfarm.sh
@@ -151,6 +153,8 @@ lib/support/site/wavve.py
lib/support/site/seezn.py
lib/support/site/kakaotv.py
*.bat
+docs/
+error_logs/
output/
*.mkv
@@ -164,3 +168,4 @@ bin/
debug_*.py
check_top_api.py
d_ohli24.py
+dev_scratch/
diff --git a/2026-03-23.history.md b/2026-03-23.history.md
deleted file mode 100644
index ecc4c7f..0000000
--- a/2026-03-23.history.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# 2026-03-23 Work History
-
-## Summary
-- Strengthened Ohli24 browse-cache validation so list/search cache only survives when at least one row contains both a link and a title.
-- Hardened Ohli24 list, auto-list, and search parsing against nested `post-title` markup and missing image attributes.
-- Prevented malformed rows from crashing the entire list response by skipping incomplete entries with warning logs.
-
-## Implementation Notes
-- Added `_extract_text()` and `_extract_first()` helpers in [`mod_ohli24.py`](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py) to avoid repeated unsafe XPath `[0]` access.
-- Extended `_is_valid_cached_html()` to parse list/search rows and reject cache payloads that only contain placeholder rows.
-- Updated changelog and plugin version to `0.7.20`.
-
-## Verification
-- `python3 -m py_compile /Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py`
-- Smoke-checked validator behavior for valid list/detail HTML and blocked HTML patterns during implementation.
diff --git a/README.md b/README.md
index 6f52dd0..c92fe16 100644
--- a/README.md
+++ b/README.md
@@ -84,6 +84,12 @@
## 📝 변경 이력 (Changelog)
+### v0.7.23 (2026-04-01)
+- **루트 정리 및 개발 보관 폴더 분리**:
+ - 루트에 흩어져 있던 `test_*`, 디버그 스크립트, 임시 HTML/노트북/구버전 백업 파일을 `dev_scratch/` 폴더로 이동해 작업용 산출물을 한곳에 모았습니다.
+ - `dev_scratch/` 는 `.gitignore` 에 추가하여 로컬 검증 파일이 저장소 루트를 다시 어지르지 않도록 정리했습니다.
+ - 루트에 있던 `2026-03-23.history.md` 도 `docs/history/` 로 이동해 작업 이력 위치를 일관되게 맞췄습니다.
+
### v0.7.20 (2026-03-23)
- **Ohli24 캐시/목록 파싱 보강**:
- 목록/검색 캐시 저장 전에 실제로 파싱 가능한 `href + title` 행이 있는지 검사하여 skeleton/불완전 HTML이 캐시에 남지 않도록 했습니다.
diff --git a/docs/history/2026-03-26.history.md b/docs/history/2026-03-26.history.md
deleted file mode 100644
index fb6478b..0000000
--- a/docs/history/2026-03-26.history.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# 2026-03-26 작업 이력
-
-## ohli24 큐 탭 초기화 버튼 오류 수정
-
-- `ohli24` 큐 탭의 `초기화` 버튼 클릭 시 확인 모달 뒤에 `{ret: 'error'}`가 떨어지고 실제 초기화가 진행되지 않는 문제를 점검했다.
-- 원인은 [mod_ohli24.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py) 의 `queue_command` 처리에서 `req.form["entity_id"]`를 무조건 읽는 방식이었다.
-- 큐 템플릿은 `reset`, `delete_completed` 요청 시 `entity_id` 없이 `globalSendCommand('reset', null, ...)` 형태로 보내므로, 서버가 `reset` 분기까지 내려가기 전에 실패할 수 있었다.
-- `queue_command`를 [mod_anilife.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_anilife.py) 와 같은 안전 패턴으로 정리해 `command`, `entity_id`를 `req.form.get(...)`로 읽도록 수정했다.
-- `reset` 처리 시 GDM 다운로드 취소만 하지 않고, [ffmpeg_queue_v1.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/lib/ffmpeg_queue_v1.py) 의 `reset` 경로를 통해 `self.queue` 런타임 큐와 `entity_list`도 함께 비우도록 보강했다.
-- 일반 큐 명령 fallback도 `entity_id`가 비어 있을 수 있는 경우를 고려해 `int(entity_id) if digit else 0` 패턴으로 안전화했다.
-
-## 2026-03-26 검증
-
-- `python -m py_compile /Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py`: success
-- 수정 후 [mod_ohli24.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py) 의 `queue_command` 분기에서 `reset` 요청이 `entity_id` 없이도 처리 가능한지 코드 경로를 재확인했다.
-
-## ohli24 큐 진행률 실시간 반영 보강
-
-- [anime_downloader_ohli24_queue.html](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/templates/anime_downloader_ohli24_queue.html) 에서 큐 페이지 Socket.IO 네임스페이스가 `ohli24`가 아닌 `linkkf`로 하드코딩되어 있어, 모듈 전용 큐 이벤트를 정상 수신하지 못하는 문제를 확인했다.
-- 같은 템플릿이 GDM 이벤트를 `status`로 수신하고 있었는데, 실제 [gommi_downloader_manager/mod_queue.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/gommi_downloader_manager/mod_queue.py) 는 `/gommi_downloader_manager` 네임스페이스에 `download_status` 이벤트를 emit하고 있었다.
-- 추가로 `autoRefreshList()`가 항목 개수 변화가 있을 때만 `renderList()`를 호출하고 있어, 진행률/속도만 변하는 구간에서는 폴링 fallback이 있어도 화면이 갱신되지 않았다.
-- 템플릿을 `PACKAGE_NAME`, `MODULE_NAME` 기반 동적 네임스페이스로 바꾸고, framework 이벤트명도 `MODULE_NAME + '_status'`를 쓰도록 정리했다.
-- GDM 소켓은 `download_status`를 수신하도록 맞췄고, 이미 렌더된 항목은 전체 새로고침 대신 progress/status/button 영역만 즉시 갱신하게 보강했다.
-- 소켓 이벤트를 놓치더라도 현재 DOM과 서버 목록을 비교해 진행률 차이가 있으면 조용히 다시 렌더하도록 fallback을 추가했다.
diff --git a/info.yaml b/info.yaml
index 374c595..ebf661d 100644
--- a/info.yaml
+++ b/info.yaml
@@ -1,5 +1,5 @@
title: "애니 다운로더"
-version: 0.7.22
+version: 0.7.23
package_name: "anime_downloader"
developer: "projectdx"
description: "anime downloader"
diff --git a/inspect_zendriver_test.py b/inspect_zendriver_test.py
deleted file mode 100644
index bde8145..0000000
--- a/inspect_zendriver_test.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import asyncio
-import zendriver as zd
-import json
-import os
-
-async def test():
- try:
- browser = await zd.start(headless=True)
- page = await browser.get("about:blank")
-
- # Test header setting
- headers = {"Referer": "https://v2.linkkf.app/"}
- try:
- await page.send(zd.cdp.network.enable())
- headers_obj = zd.cdp.network.Headers(headers)
- await page.send(zd.cdp.network.set_extra_http_headers(headers_obj))
- print("Successfully set headers")
- except Exception as e:
- print(f"Failed to set headers: {e}")
- import traceback
- traceback.print_exc()
-
- methods = [m for m in dir(page) if not m.startswith("_")]
- print(json.dumps({"methods": methods}))
- await browser.stop()
- except Exception as e:
- import traceback
- print(json.dumps({"error": str(e), "traceback": traceback.format_exc()}))
-
-if __name__ == "__main__":
- asyncio.run(test())
diff --git a/mod_anilife.py b/mod_anilife.py
index d83da41..45618c1 100644
--- a/mod_anilife.py
+++ b/mod_anilife.py
@@ -23,21 +23,6 @@ from lxml import html
from urllib import parse
import urllib
-packages = [
- "beautifulsoup4",
- "requests-cache",
- "cloudscraper",
- "selenium_stealth",
- "webdriver_manager",
-]
-for package in packages:
- try:
- import package
-
- except ImportError:
- # main(["install", package])
- os.system(f"pip install {package}")
-
from bs4 import BeautifulSoup
import cloudscraper
diff --git a/outline.md b/outline.md
deleted file mode 100644
index 890798d..0000000
--- a/outline.md
+++ /dev/null
@@ -1,124 +0,0 @@
-# anime_downloader 플러그인 구조 분석
-
-## 📌 개요
-
-FlaskFarm용 **애니메이션 다운로드 플러그인**으로, 여러 애니메이션 스트리밍 사이트에서 콘텐츠를 검색하고 다운로드할 수 있는 기능을 제공합니다.
-
----
-
-## 🏗️ 전체 구조
-
-```
-anime_downloader/
-├── __init__.py # 플러그인 초기화 (현재 비활성화됨)
-├── setup.py # 플러그인 설정 및 메뉴 구조, 모듈 로드
-├── info.yaml # 플러그인 메타데이터 (이름, 버전, 개발자)
-├── mod_ohli24.py # 애니24 사이트 모듈 (1,542줄)
-├── mod_anilife.py # 애니라이프 사이트 모듈 (1,322줄)
-├── mod_linkkf.py # 링크애니 사이트 모듈 (1,449줄)
-├── lib/ # 공용 라이브러리
-│ ├── crawler.py # 웹 크롤링 엔진 (Playwright, Selenium, Cloudscraper)
-│ ├── ffmpeg_queue_v1.py# FFmpeg 다운로드 큐 관리
-│ ├── util.py # 유틸리티 함수 (파일명 정리, 타이밍 등)
-│ └── misc.py # 비동기 실행 헬퍼 함수
-├── templates/ # HTML 템플릿 (18개 파일)
-├── static/ # CSS, JS, 이미지 리소스
-├── bin/ # 플랫폼별 바이너리 (Darwin, Linux)
-├── nest_api/ # 애니 API 관련 (서브디렉토리)
-└── yommi_api/ # 커스텀 API 관련
-```
-
----
-
-## 🔧 핵심 컴포넌트
-
-### 1. setup.py - 플러그인 엔트리포인트
-
-| 항목 | 설명 |
-|------|------|
-| `__menu` | 3개 사이트별 서브메뉴 (설정, 요청, 큐, 검색, 목록) |
-| `setting` | DB 사용, 기본 설정, 홈 모듈(`ohli24`) 지정 |
-| `P` | FlaskFarm 플러그인 인스턴스 생성 |
-| 모듈 로드 | `LogicOhli24`, `LogicAniLife`, `LogicLinkkf` |
-
-### 2. 사이트 모듈 (mod_*.py)
-
-각 모듈은 동일한 구조를 따릅니다:
-
-| 클래스 | 역할 |
-|--------|------|
-| `LogicXxx` | 사이트별 비즈니스 로직 (검색, 시리즈 정보, 다운로드 추가) |
-| `XxxQueueEntity` | 다운로드 큐 항목 (에피소드 정보, 상태 관리) |
-| `ModelXxxItem` | SQLAlchemy DB 모델 (다운로드 기록 저장) |
-
-**LogicXxx 주요 메서드:**
-
-- `process_menu()` / `process_ajax()` - 웹 요청 처리
-- `get_series_info()` - 시리즈/에피소드 정보 파싱
-- `get_anime_info()` / `get_search_result()` - 목록/검색
-- `add()` - 다운로드 큐에 추가
-- `scheduler_function()` - 자동 다운로드 스케줄러
-- `plugin_load()` / `plugin_unload()` - 생명주기 관리
-
-### 3. lib/crawler.py - 웹 크롤링 엔진
-
-| 메서드 | 기술 |
-|--------|------|
-| `get_html_requests()` | 기본 requests 요청 |
-| `get_html_playwright()` | Playwright 비동기 (헤드리스 브라우저) |
-| `get_html_playwright_sync()` | Playwright 동기 |
-| `get_html_selenium()` | Selenium WebDriver |
-| `get_html_cloudflare()` | Cloudscraper (CF 우회) |
-
-### 4. lib/ffmpeg_queue_v1.py - 다운로드 큐
-
-| 클래스 | 역할 |
-|--------|------|
-| `FfmpegQueueEntity` | 개별 다운로드 항목 (URL, 파일경로, 상태) |
-| `FfmpegQueue` | 큐 관리자 (스레드 기반 다운로드, 동시 다운로드 수 제어) |
-
----
-
-## 🖥️ 지원 사이트 (3개)
-
-| 모듈 | 사이트 | URI |
-|------|--------|-----|
-| `mod_ohli24.py` | 애니24 (ohli24) | `/ohli24` |
-| `mod_anilife.py` | 애니라이프 | `/anilife` |
-| `mod_linkkf.py` | 링크애니 | `/linkkf` |
-
----
-
-## 📄 템플릿 구조
-
-각 사이트별로 6개 템플릿 제공:
-
-- `*_setting.html` - 사이트 설정
-- `*_request.html` - 다운로드 요청 페이지
-- `*_queue.html` - 다운로드 큐 현황
-- `*_search.html` - 검색 인터페이스
-- `*_list.html` - 다운로드 목록
-- `*_category.html` - 카테고리 탐색
-
----
-
-## 🔄 동작 흐름
-
-```mermaid
-graph LR
- A[사용자] --> B[검색/탐색]
- B --> C[시리즈 선택]
- C --> D[에피소드 선택]
- D --> E[다운로드 큐 추가]
- E --> F[FfmpegQueue]
- F --> G[FFmpeg 다운로드]
- G --> H[DB 기록 저장]
-```
-
----
-
-## ⚠️ 주의사항
-
-1. **개발 모드**: `setup.py`에서 `DEFINE_DEV = True`로 설정되어 직접 모듈 import
-2. **`__init__.py` 비활성화**: 현재 주석 처리되어 `setup.py`가 실제 엔트리포인트 역할
-3. **크롤링 기술 혼용**: Cloudflare 우회를 위해 Playwright, Selenium, cloudscraper 등 다양한 기술 사용
diff --git a/plugin.py.old b/plugin.py.old
deleted file mode 100644
index d34c197..0000000
--- a/plugin.py.old
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-# @Time : 2022/02/08 2:57 PM
-# @Author : yommi
-# @Site :
-# @File : plugin
-# @Software: PyCharm
-
-import os, traceback
-
-# third-party
-from flask import Blueprint
-
-# sjva 공용
-from framework.logger import get_logger
-from framework import app, path_data
-from plugin import get_model_setting, Logic, default_route, PluginUtil
-
-
-#######################################################################
-
-
-class P(object):
- package_name = __name__.split(".")[0]
- logger = get_logger(package_name)
- blueprint = Blueprint(
- package_name,
- package_name,
- url_prefix="/%s" % package_name,
- template_folder=os.path.join(os.path.dirname(__file__), "templates"),
- static_folder="static",
- )
- menu = {
- "main": [package_name, "애니 다운로드"],
- "sub": [
- ["ohli24", "OHLI24"],
- ["linkkf", "LINKKF"],
- ["anilife", "애니라이프"],
- ["log", "로그"],
- ],
- "category": "vod",
- "sub2": {
- "ohli24": [
- ["setting", "설정"],
- ["request", "요청"],
- ["queue", "큐"],
- ["category", "검색"],
- ["list", "목록"],
- ],
- "linkkf": [
- ["setting", "설정"],
- ["request", "요청"],
- ["queue", "큐"],
- ["category", "검색"],
- ["list", "목록"],
- ],
- "anilife": [
- ["setting", "설정"],
- ["request", "요청"],
- ["category", "검색"],
- ["queue", "큐"],
- ["list", "목록"],
- ],
- },
- }
- plugin_info = {
- "version": "0.1.2.0",
- "name": "anime_downloader",
- "category_name": "vod",
- "icon": "",
- "developer": "soju6jan && projectdx",
- "description": "비디오 다운로드",
- "home": "http://yommi.duckdns.org:20080/projectdx/anime-downloader",
- "more": "",
- }
- ModelSetting = get_model_setting(package_name, logger)
- logic = None
- module_list = None
- home_module = "ohli24"
-
-
-# 초기화 함수
-def initialize():
- try:
- app.config["SQLALCHEMY_BINDS"][P.package_name] = "sqlite:///%s" % (
- os.path.join(
- path_data, "db", "{package_name}.db".format(package_name=P.package_name)
- )
- )
- PluginUtil.make_info_json(P.plugin_info, __file__)
- from .logic_ohli24 import LogicOhli24
- from .logic_anilife import LogicAniLife
-
- # P.module_list = [LogicOhli24(P), LogicLinkkf(P)]
- P.module_list = [LogicOhli24(P), LogicAniLife(P)]
- P.logic = Logic(P)
- default_route(P)
- except Exception as e:
- P.logger.error("Exception:%s", e)
- P.logger.error(traceback.format_exc())
-
-
-initialize()
diff --git a/search_result.html b/search_result.html
deleted file mode 100644
index ee20aa3..0000000
--- a/search_result.html
+++ /dev/null
@@ -1,810 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
원펀맨 애니 자막 Linkkf
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 9231591..00367fd 100644
--- a/setup.py
+++ b/setup.py
@@ -112,6 +112,9 @@ setting = {
from plugin import *
import os
+import importlib
+import subprocess
+import sys
import traceback
from flask import render_template
@@ -144,18 +147,44 @@ DEFINE_DEV = True
P = create_plugin_instance(setting)
-# curl_cffi 자동 설치 루틴
-try:
- import curl_cffi
-except ImportError:
+REQUIRED_PACKAGES = [
+ {"import_name": "bs4", "package_name": "beautifulsoup4"},
+ {"import_name": "requests_cache", "package_name": "requests-cache"},
+ {"import_name": "cloudscraper", "package_name": "cloudscraper"},
+ {"import_name": "PIL", "package_name": "Pillow"},
+ {"import_name": "lxml", "package_name": "lxml"},
+ {"import_name": "aiohttp", "package_name": "aiohttp"},
+ {"import_name": "jsbeautifier", "package_name": "jsbeautifier"},
+ {"import_name": "curl_cffi", "package_name": "curl-cffi"},
+ {"import_name": "loguru", "package_name": "loguru"},
+ {"import_name": "zendriver", "package_name": "zendriver==0.15.3"},
+]
+
+
+def ensure_dependencies():
+ missing_packages = []
+ for dependency in REQUIRED_PACKAGES:
+ try:
+ importlib.import_module(dependency["import_name"])
+ except ImportError:
+ missing_packages.append(dependency)
+
+ if not missing_packages:
+ return
+
+ package_names = [dependency["package_name"] for dependency in missing_packages]
+ P.logger.warning(f"Missing plugin dependencies detected: {package_names}")
+
try:
- import subprocess
- import sys
- P.logger.info("curl_cffi not found. Attempting to install...")
- subprocess.check_call([sys.executable, "-m", "pip", "install", "curl-cffi"])
- P.logger.info("curl_cffi installed successfully.")
+ subprocess.check_call([sys.executable, "-m", "pip", "install"] + package_names)
+ P.logger.info(f"Plugin dependencies installed successfully: {package_names}")
except Exception as e:
- P.logger.error(f"Failed to install curl_cffi: {e}")
+ P.logger.error(f"Failed to install plugin dependencies: {package_names}")
+ P.logger.error(f"Install error: {e}")
+ raise
+
+
+ensure_dependencies()
try:
if DEFINE_DEV:
@@ -180,4 +209,3 @@ try:
except Exception as e:
P.logger.error(f'Exception: {str(e)}')
P.logger.error(traceback.format_exc())
-
diff --git a/test.ipynb b/test.ipynb
deleted file mode 100644
index 2546f31..0000000
--- a/test.ipynb
+++ /dev/null
@@ -1,360 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {
- "collapsed": true
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "
\n"
- ]
- }
- ],
- "source": [
- "from datetime import date, datetime\n",
- "\n",
- "print(date)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "2022-06-07\n"
- ]
- }
- ],
- "source": [
- "print(date.today())"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "outputs": [
- {
- "ename": "RuntimeError",
- "evalue": "asyncio.run() cannot be called from a running event loop",
- "output_type": "error",
- "traceback": [
- "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
- "\u001B[0;31mRuntimeError\u001B[0m Traceback (most recent call last)",
- "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_40530/2714877972.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 52\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 53\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 54\u001B[0;31m \u001B[0masyncio\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/runners.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(main, debug)\u001B[0m\n\u001B[1;32m 31\u001B[0m \"\"\"\n\u001B[1;32m 32\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mevents\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_get_running_loop\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mis\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 33\u001B[0;31m raise RuntimeError(\n\u001B[0m\u001B[1;32m 34\u001B[0m \"asyncio.run() cannot be called from a running event loop\")\n\u001B[1;32m 35\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;31mRuntimeError\u001B[0m: asyncio.run() cannot be called from a running event loop"
- ]
- }
- ],
- "source": [
- "from playwright.sync_api import sync_playwright\n",
- "from playwright.async_api import async_playwright\n",
- "# from playwright_stealth import stealth_sync\n",
- "import asyncio\n",
- "\n",
- "async def run(pw):\n",
- "\n",
- " browser = await pw.chromium.launch(headless=False)\n",
- " # context = browser.new_context(\n",
- " # user_agent=ua,\n",
- " # )\n",
- "\n",
- "\n",
- " url = 'https://anilife.live/h/live?p=5aaf4907-da62-4b98-ba8f-59066a0dc0f4&a=none&player=jawcloud'\n",
- " #\n",
- " # if referer is not None:\n",
- " # LogicAniLife.headers[\"Referer\"] = referer\n",
- "\n",
- " # context = browser.new_context(extra_http_headers=LogicAniLife.headers)\n",
- " context = await browser.new_context()\n",
- " # LogicAniLife.headers[\"Cookie\"] = cookie_value\n",
- "\n",
- " # context.set_extra_http_headers(LogicAniLife.headers)\n",
- "\n",
- " page = await context.new_page()\n",
- "\n",
- "\n",
- "\n",
- " # page.on(\"request\", set_cookie)\n",
- " # stealth_sync(page)\n",
- " page.goto(\n",
- " url, wait_until=\"networkidle\"\n",
- " )\n",
- " page.wait_for_timeout(10000)\n",
- " # time.sleep(1)\n",
- " # page.reload()\n",
- "\n",
- " # time.sleep(10)\n",
- " cookies = context.cookies\n",
- " # print(cookies)\n",
- "\n",
- " # print(page.content())\n",
- " vod_url = page.evaluate(\n",
- " \"\"\"() => {\n",
- " return console.log(vodUrl_1080p) }\"\"\"\n",
- " )\n",
- " print(vod_url)\n",
- "\n",
- "async def main():\n",
- " async with async_playwright() as p:\n",
- " await main_run(p)\n",
- "\n",
- "\n",
- "asyncio.run(main())"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "outputs": [
- {
- "ename": "Error",
- "evalue": "Executable doesn't exist at /Users/yommi/Library/Caches/ms-playwright/chromium-939194/chrome-mac/Chromium.app/Contents/MacOS/Chromium\n╔═════════════════════════════════════════════════════════════════════════╗\n║ Looks like Playwright Test or Playwright was just installed or updated. ║\n║ Please run the following command to download new browsers: ║\n║ ║\n║ playwright install ║\n║ ║\n║ <3 Playwright Team ║\n╚═════════════════════════════════════════════════════════════════════════╝",
- "output_type": "error",
- "traceback": [
- "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
- "\u001B[0;31mError\u001B[0m Traceback (most recent call last)",
- "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_67283/2756312828.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 57\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 58\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 59\u001B[0;31m \u001B[0masyncio\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/nest_asyncio.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(future, debug)\u001B[0m\n\u001B[1;32m 30\u001B[0m \u001B[0mloop\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0masyncio\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_event_loop\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 31\u001B[0m \u001B[0mloop\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mset_debug\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdebug\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 32\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mloop\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun_until_complete\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mfuture\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 33\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 34\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0msys\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mversion_info\u001B[0m \u001B[0;34m>=\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0;36m3\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;36m6\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/nest_asyncio.py\u001B[0m in \u001B[0;36mrun_until_complete\u001B[0;34m(self, future)\u001B[0m\n\u001B[1;32m 68\u001B[0m raise RuntimeError(\n\u001B[1;32m 69\u001B[0m 'Event loop stopped before Future completed.')\n\u001B[0;32m---> 70\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mf\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mresult\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 71\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 72\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m_run_once\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/futures.py\u001B[0m in \u001B[0;36mresult\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 176\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__log_traceback\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 177\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m \u001B[0;32mis\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 178\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 179\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_result\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 180\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/tasks.py\u001B[0m in \u001B[0;36m__step\u001B[0;34m(***failed resolving arguments***)\u001B[0m\n\u001B[1;32m 278\u001B[0m \u001B[0;31m# We use the `send` method directly, because coroutines\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 279\u001B[0m \u001B[0;31m# don't have `__iter__` and `__next__` methods.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 280\u001B[0;31m \u001B[0mresult\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcoro\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msend\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 281\u001B[0m \u001B[0;32melse\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 282\u001B[0m \u001B[0mresult\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcoro\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mthrow\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mexc\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_67283/2756312828.py\u001B[0m in \u001B[0;36mmain\u001B[0;34m()\u001B[0m\n\u001B[1;32m 54\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mmain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 55\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mwith\u001B[0m \u001B[0masync_playwright\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mas\u001B[0m \u001B[0mp\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 56\u001B[0;31m \u001B[0;32mawait\u001B[0m \u001B[0mmain_run\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mp\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 57\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 58\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_67283/2756312828.py\u001B[0m in \u001B[0;36mmain_run\u001B[0;34m(playwright)\u001B[0m\n\u001B[1;32m 11\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mmain_run\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mplaywright\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 12\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 13\u001B[0;31m \u001B[0mbrowser\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mawait\u001B[0m \u001B[0mplaywright\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchromium\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mlaunch\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mheadless\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 14\u001B[0m \u001B[0;31m# context = browser.new_context(\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 15\u001B[0m \u001B[0;31m# user_agent=ua,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/async_api/_generated.py\u001B[0m in \u001B[0;36mlaunch\u001B[0;34m(self, executable_path, channel, args, ignore_default_args, handle_sigint, handle_sigterm, handle_sighup, timeout, env, headless, devtools, proxy, downloads_path, slow_mo, traces_dir, chromium_sandbox, firefox_user_prefs)\u001B[0m\n\u001B[1;32m 11600\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 11601\u001B[0m return mapping.from_impl(\n\u001B[0;32m> 11602\u001B[0;31m await self._async(\n\u001B[0m\u001B[1;32m 11603\u001B[0m \u001B[0;34m\"browser_type.launch\"\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 11604\u001B[0m self._impl_obj.launch(\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/_impl/_browser_type.py\u001B[0m in \u001B[0;36mlaunch\u001B[0;34m(self, executablePath, channel, args, ignoreDefaultArgs, handleSIGINT, handleSIGTERM, handleSIGHUP, timeout, env, headless, devtools, proxy, downloadsPath, slowMo, tracesDir, chromiumSandbox, firefoxUserPrefs)\u001B[0m\n\u001B[1;32m 86\u001B[0m \u001B[0mparams\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mlocals_to_params\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mlocals\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 87\u001B[0m \u001B[0mnormalize_launch_params\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mparams\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 88\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mfrom_channel\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;32mawait\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_channel\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msend\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"launch\"\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 89\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 90\u001B[0m async def launch_persistent_context(\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/_impl/_connection.py\u001B[0m in \u001B[0;36msend\u001B[0;34m(self, method, params)\u001B[0m\n\u001B[1;32m 37\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 38\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0msend\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmethod\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mstr\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mDict\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m->\u001B[0m \u001B[0mAny\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 39\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0;32mawait\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minner_send\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmethod\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 40\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 41\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0msend_return_as_dict\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmethod\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mstr\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mDict\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m->\u001B[0m \u001B[0mAny\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/_impl/_connection.py\u001B[0m in \u001B[0;36minner_send\u001B[0;34m(self, method, params, return_as_dict)\u001B[0m\n\u001B[1;32m 61\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0mcallback\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfuture\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdone\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 62\u001B[0m \u001B[0mcallback\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfuture\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mcancel\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 63\u001B[0;31m \u001B[0mresult\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mnext\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0miter\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mresult\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 64\u001B[0m \u001B[0;31m# Protocol now has named return values, assume result is one level deeper unless\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 65\u001B[0m \u001B[0;31m# there is explicit ambiguity.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/futures.py\u001B[0m in \u001B[0;36mresult\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 176\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__log_traceback\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 177\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m \u001B[0;32mis\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 178\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 179\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_result\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 180\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;31mError\u001B[0m: Executable doesn't exist at /Users/yommi/Library/Caches/ms-playwright/chromium-939194/chrome-mac/Chromium.app/Contents/MacOS/Chromium\n╔═════════════════════════════════════════════════════════════════════════╗\n║ Looks like Playwright Test or Playwright was just installed or updated. ║\n║ Please run the following command to download new browsers: ║\n║ ║\n║ playwright install ║\n║ ║\n║ <3 Playwright Team ║\n╚═════════════════════════════════════════════════════════════════════════╝"
- ]
- }
- ],
- "source": [
- "# !playwright install\n",
- "\n",
- "from playwright.sync_api import sync_playwright\n",
- "from playwright.async_api import async_playwright\n",
- "# from playwright_stealth import stealth_sync\n",
- "import asyncio\n",
- "import nest_asyncio\n",
- "nest_asyncio.apply()\n",
- "\n",
- "\n",
- "async def main_run(playwright):\n",
- "\n",
- " browser = await playwright.chromium.launch(headless=False)\n",
- " # context = browser.new_context(\n",
- " # user_agent=ua,\n",
- " # )\n",
- "\n",
- "\n",
- " url = 'https://sir.kr'\n",
- " #\n",
- " # if referer is not None:\n",
- " # LogicAniLife.headers[\"Referer\"] = referer\n",
- "\n",
- " # context = browser.new_context(extra_http_headers=LogicAniLife.headers)\n",
- " context = await browser.new_context()\n",
- " # LogicAniLife.headers[\"Cookie\"] = cookie_value\n",
- "\n",
- " # context.set_extra_http_headers(LogicAniLife.headers)\n",
- "\n",
- " page = await context.new_page()\n",
- "\n",
- "\n",
- "\n",
- " # page.on(\"request\", set_cookie)\n",
- " # stealth_sync(page)\n",
- " page.goto(\n",
- " url, wait_until=\"networkidle\"\n",
- " )\n",
- " page.wait_for_timeout(10000)\n",
- " # time.sleep(1)\n",
- " # page.reload()\n",
- "\n",
- " # time.sleep(10)\n",
- " cookies = context.cookies\n",
- " # print(cookies)\n",
- "\n",
- " # print(page.content())\n",
- " vod_url = page.evaluate(\n",
- " \"\"\"() => {\n",
- " return console.log(vodUrl_1080p) }\"\"\"\n",
- " )\n",
- " print(vod_url)\n",
- "\n",
- "async def main():\n",
- " async with async_playwright() as p:\n",
- " await main_run(p)\n",
- "\n",
- "\n",
- "asyncio.run(main())"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n"
- ]
- }
- ],
- "source": [
- "import select\n",
- "\n",
- "print(select)"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "['KQ_EV_ADD', 'KQ_EV_CLEAR', 'KQ_EV_DELETE', 'KQ_EV_DISABLE', 'KQ_EV_ENABLE', 'KQ_EV_EOF', 'KQ_EV_ERROR', 'KQ_EV_FLAG1', 'KQ_EV_ONESHOT', 'KQ_EV_SYSFLAGS', 'KQ_FILTER_AIO', 'KQ_FILTER_PROC', 'KQ_FILTER_READ', 'KQ_FILTER_SIGNAL', 'KQ_FILTER_TIMER', 'KQ_FILTER_VNODE', 'KQ_FILTER_WRITE', 'KQ_NOTE_ATTRIB', 'KQ_NOTE_CHILD', 'KQ_NOTE_DELETE', 'KQ_NOTE_EXEC', 'KQ_NOTE_EXIT', 'KQ_NOTE_EXTEND', 'KQ_NOTE_FORK', 'KQ_NOTE_LINK', 'KQ_NOTE_LOWAT', 'KQ_NOTE_PCTRLMASK', 'KQ_NOTE_PDATAMASK', 'KQ_NOTE_RENAME', 'KQ_NOTE_REVOKE', 'KQ_NOTE_TRACK', 'KQ_NOTE_TRACKERR', 'KQ_NOTE_WRITE', 'PIPE_BUF', 'POLLERR', 'POLLHUP', 'POLLIN', 'POLLNVAL', 'POLLOUT', 'POLLPRI', 'POLLRDBAND', 'POLLRDNORM', 'POLLWRBAND', 'POLLWRNORM', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'error', 'kevent', 'kqueue', 'poll', 'select']\n"
- ]
- }
- ],
- "source": [
- "print(dir(select))"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "_CountingAttr(counter=23, _default=Factory(factory=, takes_self=False), repr=True, eq=True, order=True, hash=None, init=True, on_setattr=None, metadata={})\n"
- ]
- }
- ],
- "source": [
- "import attr\n",
- "\n",
- "_kqueue = attr.ib(factory=select.kqueue)\n",
- "\n",
- "print(_kqueue)"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "outputs": [],
- "source": [],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "outputs": [],
- "source": [],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "용사 파티에서 추방된 비스트테이머, 최강종의 고양이귀 소녀와 만나다.S01E06.1080p-OHNI24.mp4\n"
- ]
- }
- ],
- "source": [
- "import os\n",
- "\n",
- "t_str = \"/data/anime_downloader/ohli24/용사 파티에서 추방된 비스트테이머, 최강종의 고양이귀 소녀와 만나다/Season 1/용사 파티에서 추방된 비스트테이머, 최강종의 고양이귀 소녀와 만나다.S01E06.1080p-OHNI24.mp4\"\n",
- "\n",
- "filename = os.path.basename(t_str)\n",
- "print(filename)"
- ],
- "metadata": {
- "collapsed": false
- }
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "thumbnails.vtt\n"
- ]
- }
- ],
- "source": [
- "srt_url = \"https://cdn1-nydoodle.xyz/cdn/down/e2dbe79957ca1ba094c80cfd4c7604cb/thumbnails.vtt\"\n",
- "\n",
- "print(srt_url.split('/')[-1])\n"
- ],
- "metadata": {
- "collapsed": false
- }
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 2
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython2",
- "version": "2.7.6"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/zd_debug.py b/zd_debug.py
deleted file mode 100644
index 693f6c7..0000000
--- a/zd_debug.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import requests
-import json
-import re
-import sys
-
-def test_fetch():
- url = "https://playv2.sub3.top/r2/play.php?&id=n20&url=405686s1"
- headers = {
- "Referer": "https://linkkf.live/",
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
- }
-
- daemon_url = "http://127.0.0.1:19876/fetch"
- payload = {
- "url": url,
- "headers": headers,
- "timeout": 30
- }
-
- print(f"Fetching {url} via daemon...")
- try:
- resp = requests.post(daemon_url, json=payload, timeout=40)
- if resp.status_code != 200:
- print(f"Error: HTTP {resp.status_code}")
- print(resp.text)
- return
-
- data = resp.json()
- if not data.get("success"):
- print(f"Fetch failed: {data.get('error')}")
- return
-
- html = data.get("html", "")
- print(f"Fetch success. Length: {len(html)}")
-
- # Save for inspection
- with open("linkkf_player_test.html", "w", encoding="utf-8") as f:
- f.write(html)
- print("Saved to linkkf_player_test.html")
-
- # Try regex patterns from mod_linkkf.py
- patterns = [
- r"url:\s*['\"]([^'\"]*\.m3u8[^'\"]*)['\"]",
- r"]+src=['\"]([^'\"]*\.m3u8[^'\"]*)['\"]",
- r"src\s*=\s*['\"]([^'\"]*\.m3u8[^'\"]*)['\"]",
- r"url\s*:\s*['\"]([^'\"]+)['\"]"
- ]
-
- found = False
- for p in patterns:
- match = re.search(p, html, re.IGNORECASE)
- if match:
- url_found = match.group(1)
- if ".m3u8" in url_found or "m3u8" in p:
- print(f"Pattern '{p}' found: {url_found}")
- found = True
-
- if not found:
- print("No m3u8 found with existing patterns.")
- # Search for any .m3u8
- any_m3u8 = re.findall(r"['\"]([^'\"]*\.m3u8[^'\"]*)['\"]", html)
- if any_m3u8:
- print(f"Generic search found {len(any_m3u8)} m3u8 links:")
- for m in any_m3u8[:5]:
- print(f" - {m}")
- else:
- print("No .m3u8 found in generic search either.")
- # Check for other video extensions or potential indicators
- if "Artplayer" in html:
- print("Artplayer detected.")
- if "video" in html:
- print("Video tag found.")
-
- # Check for 'cache/'
- if "cache/" in html:
- print("Found 'cache/' keyword.")
- cache_links = re.findall(r"['\"]([^'\"]*cache/[^'\"]*)['\"]", html)
- for c in cache_links:
- print(f" - Possible cache link: {c}")
-
- except Exception as e:
- print(f"Exception: {e}")
-
-if __name__ == "__main__":
- test_fetch()