diff --git a/linkkf/.gitignore b/linkkf/.gitignore deleted file mode 100644 index 10f2f00..0000000 --- a/linkkf/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -.DS_Store -*.ipynb -test.ipynb -.idea -.vscode -*.pyo -*.pyc -/linkkf_cache.sqlite -bin/Darwin/mp4decrypt -bin/Darwin/mp4dump -bin/Darwin/mp4dump_ -bin/Darwin/mp4info -bin/Linux/mp4decrypt -bin/Linux/mp4dump -bin/Linux/mp4info diff --git a/linkkf/README.md b/linkkf/README.md deleted file mode 100644 index 4d9abac..0000000 --- a/linkkf/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# linkkf - -FlaskFarm용 `linkkf.tv` 플러그인입니다. - -설치 전 의존 패키지: -`pip install -r requirements.txt` - -플러그인 최초 로딩 시 누락된 패키지는 자동 설치를 시도합니다. - -현재 기준 주요 기능: -- 카테고리 목록 조회 -- 작품 분석 및 회차 목록 조회 -- 재생 URL 추출 및 브라우저 플레이어 프록시 -- ffmpeg 다운로드 대기열 연동 -- 다운로드 이력 목록 표시 diff --git a/linkkf/__init__.py b/linkkf/__init__.py deleted file mode 100644 index a83baeb..0000000 --- a/linkkf/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .setup import P - diff --git a/linkkf/info.json b/linkkf/info.json deleted file mode 100644 index a879bf5..0000000 --- a/linkkf/info.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.3.2.0", - "name": "linkkf", - "category_name": "vod", - "icon": "", - "developer": "projectdx && persuade", - "description": "linkkf 사이트에서 애니 다운로드", - "home": "https://linkkf.tv", - "more": "", - "category": "vod" -} diff --git a/linkkf/info.yaml b/linkkf/info.yaml deleted file mode 100644 index 40ce8e0..0000000 --- a/linkkf/info.yaml +++ /dev/null @@ -1,8 +0,0 @@ -title: "linkkf" -version: "0.3.2.0" -package_name: "linkkf" -developer: "projectdx && persuade" -description: "linkkf 사이트에서 애니 다운로드" -home: "https://linkkf.tv" -more: "" - diff --git a/linkkf/lib/utils.py b/linkkf/lib/utils.py deleted file mode 100644 index 6d318fe..0000000 --- a/linkkf/lib/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -import time -import logging -from functools import wraps - -logger = logging.getLogger("linkkf") - -def linkkf_async_timeit(func): - @wraps(func) - async def wrapper(*args, **kwargs): - start_time = time.perf_counter() - try: - return await func(*args, **kwargs) - finally: - total_time = time.perf_counter() - start_time - logger.debug("%s%r %r took %.4fs", func.__name__, args, kwargs, total_time) - - return wrapper diff --git a/linkkf/logic.py b/linkkf/logic.py deleted file mode 100644 index cba6dd1..0000000 --- a/linkkf/logic.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import traceback - -from framework import F, Job, db, get_logger, path_data, scheduler -from support import SupportFile - -from .logic_linkkf import LogicLinkkf -from .logic_queue import LogicQueue -from .model import ModelLinkkf, ModelSetting - - -package_name = __name__.split(".")[0] -logger = get_logger(package_name) - - -class Logic(object): - db_default = { - "linkkf_url": "https://linkkf.tv", - "download_path": os.path.join(path_data, "linkkf"), - "linkkf_auto_make_folder": "True", - "linkkf_auto_make_season_folder": "True", - "linkkf_finished_insert": "[완결]", - "include_date": "False", - "date_option": "0", - "auto_make_folder": "True", - "max_ffmpeg_process_count": "4", - "auto_interval": "* 20 * * *", - "auto_start": "False", - "whitelist_program": "", - } - - @staticmethod - def db_init(): - try: - with F.app.app_context(): - logger.debug(Logic.db_default.items()) - for key, value in Logic.db_default.items(): - logger.debug(f"{key}: {value}") - if db.session.query(ModelSetting).filter_by(key=key).count() == 0: - db.session.add(ModelSetting(key, value)) - db.session.commit() - Logic.db_migration() - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - - @staticmethod - def plugin_load(): - try: - logger.debug("%s plugin_load", package_name) - Logic.db_init() - - if ModelSetting.get("auto_start") == "True": - Logic.scheduler_start() - - from .plugin import plugin_info - - SupportFile.write_json( - os.path.join(os.path.dirname(__file__), "info.json"), - plugin_info, - ) - LogicQueue.queue_start() - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - - @staticmethod - def plugin_unload(): - try: - logger.debug("%s plugin_unload", package_name) - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - - @staticmethod - def db_migration(): - logger.debug("db_migration::=======================") - try: - migrated = ModelLinkkf.migrate_existing_rows() - logger.debug("ModelLinkkf migrate_existing_rows: %s", migrated) - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - - @staticmethod - def scheduler_start(): - try: - interval = ModelSetting.get("auto_interval") - job = Job( - package_name, - package_name, - interval, - Logic.scheduler_function, - "linkkf 다운로드", - True, - ) - scheduler.add_job_instance(job) - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - - @staticmethod - def scheduler_stop(): - try: - scheduler.remove_job(package_name) - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - - @staticmethod - def setting_save(req): - try: - for key, value in req.form.items(): - logger.debug("Key:%s Value:%s", key, value) - entity = ( - db.session.query(ModelSetting) - .filter_by(key=key) - .with_for_update() - .first() - ) - entity.value = value - db.session.commit() - return True - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - logger.error("key:%s value:%s", key, value) - return False - - @staticmethod - def scheduler_function(): - try: - LogicLinkkf.scheduler_function() - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) diff --git a/linkkf/mod_basic.py b/linkkf/mod_basic.py deleted file mode 100644 index fb9b413..0000000 --- a/linkkf/mod_basic.py +++ /dev/null @@ -1,303 +0,0 @@ -import asyncio -import json -import re -import threading -import traceback -import urllib.parse - -import requests -from flask import Response, jsonify, render_template, stream_with_context -from plugin import PluginModuleBase, default_route_socketio_module -from framework import F - -from .logic import Logic -from .logic_linkkf import LogicLinkkf -from .logic_queue import LogicQueue, QueueEntity -from .model import ModelLinkkf -from .setup import P - - -class ModuleBasic(PluginModuleBase): - def __init__(self, P): - super(ModuleBasic, self).__init__(P, name="main") - default_route_socketio_module(self) - - @staticmethod - def _make_proxy_url(target, referer): - return ( - f"/{P.package_name}/normal/proxy" - f"?target={urllib.parse.quote(str(target), safe='')}" - f"&referer={urllib.parse.quote(str(referer or ''), safe='')}" - ) - - @staticmethod - def _get_proxy_headers(referer): - return { - "User-Agent": ( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/122.0.0.0 Safari/537.36" - ), - "Referer": referer or P.ModelSetting.get("linkkf_url"), - "Origin": urllib.parse.urlsplit(referer or P.ModelSetting.get("linkkf_url")).scheme - + "://" - + urllib.parse.urlsplit(referer or P.ModelSetting.get("linkkf_url")).netloc, - } - - @staticmethod - def _rewrite_m3u8(content, target_url, referer): - def replace_uri_attr(line): - def repl(match): - absolute = urllib.parse.urljoin(target_url, match.group(1)) - proxied = ModuleBasic._make_proxy_url(absolute, referer) - return f'URI="{proxied}"' - - return re.sub(r'URI="([^"]+)"', repl, line) - - lines = [] - for raw_line in content.splitlines(): - line = raw_line.strip() - if line == "": - lines.append(raw_line) - continue - if line.startswith("#"): - lines.append(replace_uri_attr(raw_line)) - continue - absolute = urllib.parse.urljoin(target_url, line) - lines.append(ModuleBasic._make_proxy_url(absolute, referer)) - return "\n".join(lines) - - def process_menu(self, sub, req): - if sub == "log": - return render_template("log.html", package=self.P.package_name) - - arg = self.P.ModelSetting.to_dict() if self.P.ModelSetting is not None else {} - arg["package_name"] = self.P.package_name - arg["sub"] = sub - arg["template_name"] = f"{self.P.package_name}_{sub}" - - if sub == "setting": - arg["scheduler"] = str(F.scheduler.is_include(self.P.package_name)) - arg["is_running"] = str(F.scheduler.is_running(self.P.package_name)) - elif sub in ["request", "queue", "list"]: - arg["current_code"] = ( - LogicLinkkf.current_data["code"] - if LogicLinkkf.current_data is not None - else "" - ) - - return render_template(f"{self.P.package_name}_{sub}.html", arg=arg) - - def process_ajax(self, sub, req): - try: - if sub == "scheduler_toggle": - go = req.form["scheduler"] - if go == "true": - Logic.scheduler_start() - else: - Logic.scheduler_stop() - return jsonify(go) - if sub == "execute_once": - threading.Thread(target=Logic.scheduler_function, daemon=True).start() - return jsonify({"ret": "success"}) - if sub == "analysis": - code = req.form["code"] - data = LogicLinkkf.get_title_info(code) - if data["ret"] == "error": - return jsonify(data) - return jsonify({"ret": "success", "data": data}) - if sub == "play": - episode_url = req.form["url"] - play_title = req.form.get("title", "LinkKF") - return jsonify( - { - "ret": "success", - "data": { - "play_url": ( - f"/{self.P.package_name}/normal/play" - f"?url={urllib.parse.quote(str(episode_url), safe='')}" - f"&title={urllib.parse.quote(str(play_title), safe='')}" - ) - }, - } - ) - if sub == "play_latest": - code = req.form["code"] - data = LogicLinkkf.get_title_info(code) - if data["ret"] == "error": - return jsonify(data) - if "episode" not in data or len(data["episode"]) == 0: - return jsonify({"ret": "error", "log": "최신 화 정보를 찾지 못했습니다."}) - latest_episode = data["episode"][0] - latest_title = f"{data['title']} - {latest_episode['title']}" - return jsonify( - { - "ret": "success", - "data": { - "play_url": ( - f"/{self.P.package_name}/normal/play" - f"?url={urllib.parse.quote(str(latest_episode['url']), safe='')}" - f"&title={urllib.parse.quote(str(latest_title), safe='')}" - ), - }, - } - ) - if sub == "search": - query = req.form["query"] - return jsonify(LogicLinkkf.get_search_result(str(query))) - if sub == "anime_list": - page = req.form["page"] - cate = req.form["type"] - return jsonify(LogicLinkkf.get_anime_list_info(cate, page)) - if sub == "airing_list": - return jsonify(LogicLinkkf.get_airing_info()) - if sub == "get_airing_code": - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - data = loop.run_until_complete(LogicLinkkf.get_airing_code()) - return jsonify({"ret": "success", "data": data}) - if sub == "screen_movie_list": - page = req.form["page"] - return jsonify(LogicLinkkf.get_screen_movie_info(page)) - if sub == "complete_anilist": - page = req.form["page"] - return jsonify(LogicLinkkf.get_complete_anilist_info(page)) - if sub == "apply_new_title": - return jsonify(LogicLinkkf.apply_new_title(req.form["new_title"])) - if sub == "apply_new_season": - return jsonify(LogicLinkkf.apply_new_season(req.form["new_season"])) - if sub == "add_whitelist": - payload = req.get_json() - ret = LogicLinkkf.add_whitelist(payload if payload is not None else None) - return jsonify(ret) - if sub == "add_queue": - code = req.form["code"] - info = LogicLinkkf.get_info_by_code(code) - if info is not None: - return jsonify({"ret": LogicQueue.add_queue(info)}) - return jsonify({"ret": "no_data"}) - if sub == "add_queue_checked_list": - code_list = req.form["code"].split(",") - count = 0 - for code in code_list: - info = LogicLinkkf.get_info_by_code(code) - if info is not None: - LogicQueue.add_queue(info) - count += 1 - return jsonify({"ret": "success", "log": str(count)}) - if sub == "down_subtitle_list": - code_list = req.form["code"].split(",") - count = 0 - for code in code_list: - info = LogicLinkkf.get_info_by_code(code) - if info is not None: - LogicLinkkf.download_subtitle(info) - count += 1 - return jsonify({"ret": "success", "log": str(count)}) - if sub == "program_auto_command": - return jsonify(LogicQueue.program_auto_command(req)) - if sub == "web_list": - LogicQueue.sync_entities_to_db() - return jsonify(ModelLinkkf.web_list(req)) - if sub == "db_remove": - return jsonify(ModelLinkkf.delete_by_id(req.form["id"])) - if sub == "reset_db": - res = LogicLinkkf.reset_db() - return jsonify({"ret": "success" if res else "error"}) - except Exception as e: - self.P.logger.error(f"Exception:{str(e)}") - self.P.logger.error(traceback.format_exc()) - return jsonify({"ret": "error", "log": str(e)}) - return jsonify({"ret": "error", "log": f"unsupported ajax: {sub}"}) - - def process_normal(self, sub, req): - try: - if sub == "play": - episode_url = req.args.get("url", "").strip() - play_title = req.args.get("title", "LinkKF").strip() - video_info = LogicLinkkf.get_video_url(episode_url) - if video_info is None or video_info[0] in [None, ""]: - return f"재생 URL을 가져오지 못했습니다: {episode_url}", 500 - - referer = video_info[1] or self.P.ModelSetting.get("linkkf_url") - data = { - "play_title": play_title or "LinkKF", - "play_source_src": self._make_proxy_url(video_info[0], referer), - "play_source_type": "application/x-mpegURL" if ".m3u8" in str(video_info[0]).lower() else "video/mp4", - "play_subtitle_src": "", - } - if len(video_info) > 2 and video_info[2] not in [None, ""]: - data["play_subtitle_src"] = self._make_proxy_url(video_info[2], referer) - return render_template("videojs.html", data=data) - - if sub == "proxy": - target = req.args.get("target", "").strip() - referer = req.args.get("referer", self.P.ModelSetting.get("linkkf_url")).strip() - if target == "": - return "missing target", 400 - - headers = self._get_proxy_headers(referer) - if req.headers.get("Range") is not None: - headers["Range"] = req.headers.get("Range") - upstream = requests.get(target, headers=headers, stream=True, timeout=30) - content_type = upstream.headers.get("content-type", "") - - if upstream.status_code >= 400: - return Response( - upstream.content, - status=upstream.status_code, - content_type=content_type or "text/plain", - ) - - if ".m3u8" in target.lower() or "mpegurl" in content_type.lower(): - text = upstream.text - rewritten = self._rewrite_m3u8(text, target, referer) - return Response( - rewritten, - content_type=content_type or "application/vnd.apple.mpegurl", - ) - - def generate(): - try: - for chunk in upstream.iter_content(chunk_size=64 * 1024): - if chunk: - yield chunk - finally: - upstream.close() - - response = Response( - stream_with_context(generate()), - status=upstream.status_code, - content_type=content_type or "application/octet-stream", - ) - if upstream.headers.get("Accept-Ranges") is not None: - response.headers["Accept-Ranges"] = upstream.headers.get("Accept-Ranges") - if upstream.headers.get("Content-Length") is not None: - response.headers["Content-Length"] = upstream.headers.get("Content-Length") - if upstream.headers.get("Content-Range") is not None: - response.headers["Content-Range"] = upstream.headers.get("Content-Range") - return response - except Exception as e: - self.P.logger.error(f"Exception:{str(e)}") - self.P.logger.error(traceback.format_exc()) - return f"playback proxy error: {e}", 500 - return "unsupported normal route", 404 - - def plugin_load(self): - Logic.plugin_load() - - def plugin_unload(self): - Logic.plugin_unload() - - def setting_save_after(self, change_list): - if "linkkf_url" in change_list: - LogicLinkkf.referer = None - LogicLinkkf.headers["Referer"] = self.P.ModelSetting.get("linkkf_url") - - def socketio_connect(self): - data = json.loads(json.dumps([item.__dict__ for item in QueueEntity.entity_list], default=str)) - self.socketio_callback("on_connect", data, encoding=False) - - def socketio_list_refresh(self): - data = json.loads(json.dumps([item.__dict__ for item in QueueEntity.entity_list], default=str)) - self.socketio_callback("list_refresh", data, encoding=False) diff --git a/linkkf/model.py b/linkkf/model.py deleted file mode 100644 index 4918b99..0000000 --- a/linkkf/model.py +++ /dev/null @@ -1,273 +0,0 @@ -# -*- coding: utf-8 -*- -import json -import os -from datetime import datetime - -from sqlalchemy import desc, or_ - -from .setup import * - - -ModelSetting = P.ModelSetting - - -class ModelLinkkfProgram(ModelBase): - P = P - __tablename__ = "linkkf_program" - __bind_key__ = P.package_name - - id = db.Column(db.Integer, primary_key=True) - contents_json = db.Column(db.JSON) - created_time = db.Column(db.DateTime) - programcode = db.Column(db.String) - save_folder = db.Column(db.String) - season = db.Column(db.Integer) - - def __init__(self, data): - self.created_time = datetime.now() - self.programcode = data["code"] - self.save_folder = data["title"] - self.season = data["season"] - - def set_info(self, data): - self.contents_json = data - self.programcode = data["code"] - self.save_folder = data["save_folder"] - self.season = data["season"] - - -class ModelLinkkf(ModelBase): - P = P - __tablename__ = "linkkf_auto_episode" - __bind_key__ = P.package_name - - id = db.Column(db.Integer, primary_key=True) - contents_json = db.Column(db.JSON) - created_time = db.Column(db.DateTime) - completed_time = db.Column(db.DateTime) - - programcode = db.Column(db.String) - episodecode = db.Column(db.String) - filename = db.Column(db.String) - duration = db.Column(db.Integer) - start_time = db.Column(db.DateTime) - end_time = db.Column(db.DateTime) - download_time = db.Column(db.Integer) - completed = db.Column(db.Boolean) - user_abort = db.Column(db.Boolean) - pf_abort = db.Column(db.Boolean) - etc_abort = db.Column(db.Integer) - ffmpeg_status = db.Column(db.Integer) - temp_path = db.Column(db.String) - save_path = db.Column(db.String) - pf = db.Column(db.Integer) - retry = db.Column(db.Integer) - filesize = db.Column(db.Integer) - filesize_str = db.Column(db.String) - download_speed = db.Column(db.String) - call = db.Column(db.String) - status = db.Column(db.String) - linkkf_info = db.Column(db.JSON) - - def __init__(self, call, info): - self.created_time = datetime.now() - self.completed = False - self.start_time = datetime.now() - self.user_abort = False - self.pf_abort = False - self.etc_abort = 0 - self.ffmpeg_status = -1 - self.pf = 0 - self.retry = 0 - self.call = call - self.set_info(info) - - def as_dict(self): - ret = super().as_dict() - if ret.get("status") in [None, ""]: - if self.completed is True: - ret["status"] = "completed" - elif self.user_abort is True: - ret["status"] = "canceled" - elif self.pf_abort is True or (self.etc_abort is not None and int(self.etc_abort) > 0): - ret["status"] = "error" - elif self.ffmpeg_status in [0, 5]: - ret["status"] = "downloading" - ret["created_time"] = self.created_time.strftime("%Y-%m-%d %H:%M:%S") - ret["completed_time"] = ( - self.completed_time.strftime("%Y-%m-%d %H:%M:%S") - if self.completed_time is not None - else None - ) - return ret - - def set_info(self, data): - self.contents_json = data - self.programcode = data["program_code"] - self.episodecode = data["code"] - self.filename = data.get("filename", self.filename) - self.linkkf_info = data - if self.status in [None, ""]: - self.status = "waiting" - - @staticmethod - def _normalize_json_data(data): - if isinstance(data, dict): - return data - if isinstance(data, str) and data.strip() != "": - try: - return json.loads(data) - except Exception: - return {} - return {} - - @classmethod - def sync_completed_from_filesystem(cls): - with F.app.app_context(): - changed = 0 - rows = F.db.session.query(cls).filter( - or_(cls.status != "completed", cls.status.is_(None), cls.completed.is_(False)) - ).all() - for row in rows: - info = cls._normalize_json_data(row.linkkf_info) or cls._normalize_json_data(row.contents_json) - save_path = row.save_path or info.get("save_path") - filename = row.filename or info.get("filename") - if not save_path or not filename: - continue - fullpath = os.path.join(save_path, filename) - if os.path.exists(fullpath) is False: - continue - row.completed = True - row.user_abort = False - row.pf_abort = False - row.etc_abort = 0 - row.ffmpeg_status = 7 if row.ffmpeg_status in [None, -1, 0, 5] else row.ffmpeg_status - row.status = "completed" - file_time = datetime.fromtimestamp(os.path.getmtime(fullpath)) - if row.end_time is None: - row.end_time = file_time - if row.completed_time is None: - row.completed_time = file_time - changed += 1 - if changed > 0: - F.db.session.commit() - return changed - - @classmethod - def migrate_existing_rows(cls): - with F.app.app_context(): - changed = 0 - rows = F.db.session.query(cls).all() - for row in rows: - info = cls._normalize_json_data(row.contents_json) or cls._normalize_json_data(row.linkkf_info) - updated = False - if row.programcode in [None, ""] and info.get("program_code"): - row.programcode = info.get("program_code") - updated = True - if row.episodecode in [None, ""] and info.get("code"): - row.episodecode = info.get("code") - updated = True - if row.filename in [None, ""] and info.get("filename"): - row.filename = info.get("filename") - updated = True - if row.linkkf_info in [None, {}] and info: - row.linkkf_info = info - updated = True - if row.status in [None, ""]: - if row.completed is True: - row.status = "completed" - elif row.user_abort is True: - row.status = "canceled" - elif row.pf_abort is True or (row.etc_abort is not None and int(row.etc_abort) > 0): - row.status = "error" - elif row.ffmpeg_status in [0, 5]: - row.status = "downloading" - else: - row.status = "waiting" - updated = True - if updated: - changed += 1 - if changed > 0: - F.db.session.commit() - changed += cls.sync_completed_from_filesystem() - return changed - - @classmethod - def web_list(cls, req): - with F.app.app_context(): - ret = {} - cls.sync_completed_from_filesystem() - page = int(req.form["page"]) if "page" in req.form else 1 - page_size = 30 - search = req.form["search_word"] if "search_word" in req.form else req.form.get("keyword", "") - option = req.form["option"] if "option" in req.form else req.form.get("option1", "finished") - order = req.form["order"] if "order" in req.form else "desc" - - query = cls.make_query(search=search, order=order, option=option) - count = query.count() - query = query.limit(page_size).offset((page - 1) * page_size) - lists = query.all() - ret["list"] = [item.as_dict() for item in lists] - ret["paging"] = cls.get_paging_info(count, page, page_size) - return ret - - @classmethod - def get_by_linkkf_id(cls, linkkf_id): - with F.app.app_context(): - return F.db.session.query(cls).filter_by(episodecode=linkkf_id).first() - - @classmethod - def make_query(cls, search="", order="desc", option="all"): - query = F.db.session.query(cls) - if search is not None and search != "": - if "|" in search: - conditions = [] - for token in [x.strip() for x in search.split("|") if x.strip()]: - conditions.append(cls.filename.like(f"%{token}%")) - conditions.append(cls.programcode.like(f"%{token}%")) - if conditions: - query = query.filter(or_(*conditions)) - elif "," in search: - for token in [x.strip() for x in search.split(",") if x.strip()]: - query = query.filter( - or_( - cls.filename.like(f"%{token}%"), - cls.programcode.like(f"%{token}%"), - ) - ) - else: - query = query.filter( - or_( - cls.filename.like(f"%{search}%"), - cls.programcode.like(f"%{search}%"), - ) - ) - if option == "completed": - query = query.filter(or_(cls.status == "completed", cls.completed.is_(True))) - elif option == "canceled": - query = query.filter(or_(cls.status == "canceled", cls.user_abort.is_(True))) - elif option == "error": - query = query.filter( - or_( - cls.status == "error", - cls.pf_abort.is_(True), - cls.etc_abort > 0, - ) - ) - elif option == "finished": - query = query.filter( - or_( - cls.status.in_(["completed", "error", "canceled"]), - cls.completed.is_(True), - cls.user_abort.is_(True), - cls.pf_abort.is_(True), - cls.etc_abort > 0, - ) - ) - elif option == "downloading": - query = query.filter(cls.status == "downloading") - if order == "desc": - query = query.order_by(desc(cls.id)) - else: - query = query.order_by(cls.id) - return query diff --git a/linkkf/plugin.py b/linkkf/plugin.py deleted file mode 100644 index 8483eb4..0000000 --- a/linkkf/plugin.py +++ /dev/null @@ -1,37 +0,0 @@ -from .setup import P - - -package_name = P.package_name -logger = P.logger -plugin_info = P.plugin_info - - -def _module(): - if P is None or P.module_list is None or len(P.module_list) == 0: - return None - return P.module_list[0] - - -def plugin_load(): - module = _module() - if module is not None: - module.plugin_load() - - -def plugin_unload(): - module = _module() - if module is not None: - module.plugin_unload() - - -def socketio_callback(cmd, data): - module = _module() - if module is not None and hasattr(module, "socketio_callback"): - module.socketio_callback(cmd, data) - - -def socketio_list_refresh(): - module = _module() - if module is not None and hasattr(module, "socketio_list_refresh"): - module.socketio_list_refresh() - diff --git a/linkkf/requirements.txt b/linkkf/requirements.txt deleted file mode 100644 index 964c35e..0000000 --- a/linkkf/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -cloudscraper -beautifulsoup4 -requests-cache -lxml diff --git a/linkkf/setup.py b/linkkf/setup.py deleted file mode 100644 index 3532da3..0000000 --- a/linkkf/setup.py +++ /dev/null @@ -1,90 +0,0 @@ -import importlib.util -import os -import subprocess -import sys -import yaml - -from framework import F -from plugin import * - - -REQUIRED_PACKAGES = [ - ("cloudscraper", "cloudscraper"), - ("beautifulsoup4", "bs4"), - ("requests-cache", "requests_cache"), - ("lxml", "lxml"), -] - - -def _get_declared_package_name(): - info_path = os.path.join(os.path.dirname(__file__), "info.yaml") - try: - with open(info_path, encoding="utf-8") as file: - info = yaml.safe_load(file) or {} - package_name = str(info.get("package_name", "")).strip() - if package_name != "": - return package_name - except Exception: - pass - return os.path.basename(os.path.dirname(__file__)) - - -def ensure_sqlalchemy_bind(package_name=None): - package_name = package_name or _get_declared_package_name() - try: - if getattr(F, "app", None) is None: - return package_name - binds = F.app.config.setdefault("SQLALCHEMY_BINDS", {}) - if package_name not in binds: - db_path = os.path.join(F.config["path_data"], "db", f"{package_name}.db") - binds[package_name] = f"sqlite:///{db_path}?check_same_thread=False" - except Exception: - pass - return package_name - - -def _ensure_requirements(): - missing = [package for package, module_name in REQUIRED_PACKAGES if importlib.util.find_spec(module_name) is None] - if not missing: - return - - if getattr(P, "logger", None) is not None: - P.logger.info("Installing missing packages: %s", ", ".join(missing)) - - subprocess.check_call([sys.executable, "-m", "pip", "install", *missing]) - - -setting = { - "filepath": __file__, - "use_db": True, - "use_default_setting": True, - "home_module": "category", - "menu": { - "uri": __package__, - "name": "linkkf", - "list": [ - {"uri": "setting", "name": "설정"}, - {"uri": "request", "name": "요청"}, - {"uri": "category", "name": "카테고리"}, - {"uri": "queue", "name": "대기열"}, - {"uri": "list", "name": "목록"}, - {"uri": "log", "name": "로그"}, - ], - }, - "setting_menu": None, - "default_route": "single", -} - - -ensure_sqlalchemy_bind() -P = create_plugin_instance(setting) -ensure_sqlalchemy_bind(P.package_name) -_ensure_requirements() - -try: - from .mod_basic import ModuleBasic - - P.set_module_list([ModuleBasic]) -except Exception as e: - P.logger.error(f"Exception:{str(e)}") - P.logger.error(traceback.format_exc()) diff --git a/linkkf/subtitle_util.py b/linkkf/subtitle_util.py deleted file mode 100644 index 1a80997..0000000 --- a/linkkf/subtitle_util.py +++ /dev/null @@ -1,44 +0,0 @@ -import re - -from support import SupportFile - - -def write_file(data, filepath): - SupportFile.write_file(filepath, data) - - -def convert_vtt_to_srt(vtt_data): - lines = [] - counter = 1 - - for block in re.split(r"\r?\n\r?\n", vtt_data.strip()): - block = block.strip() - if block == "" or block == "WEBVTT": - continue - - block_lines = [line.strip("\ufeff") for line in block.splitlines() if line.strip() != ""] - if not block_lines: - continue - - if block_lines[0].startswith("WEBVTT"): - block_lines = block_lines[1:] - if not block_lines: - continue - - if "-->" not in block_lines[0] and len(block_lines) > 1 and "-->" in block_lines[1]: - block_lines = block_lines[1:] - - if "-->" not in block_lines[0]: - continue - - timing = block_lines[0].replace(".", ",") - payload = block_lines[1:] - - lines.append(str(counter)) - lines.append(timing) - lines.extend(payload) - lines.append("") - counter += 1 - - return "\n".join(lines).strip() + "\n" - diff --git a/setup.py b/setup.py index a2dcb5f..3532da3 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,10 @@ import importlib.util +import os import subprocess import sys +import yaml +from framework import F from plugin import * @@ -13,6 +16,33 @@ REQUIRED_PACKAGES = [ ] +def _get_declared_package_name(): + info_path = os.path.join(os.path.dirname(__file__), "info.yaml") + try: + with open(info_path, encoding="utf-8") as file: + info = yaml.safe_load(file) or {} + package_name = str(info.get("package_name", "")).strip() + if package_name != "": + return package_name + except Exception: + pass + return os.path.basename(os.path.dirname(__file__)) + + +def ensure_sqlalchemy_bind(package_name=None): + package_name = package_name or _get_declared_package_name() + try: + if getattr(F, "app", None) is None: + return package_name + binds = F.app.config.setdefault("SQLALCHEMY_BINDS", {}) + if package_name not in binds: + db_path = os.path.join(F.config["path_data"], "db", f"{package_name}.db") + binds[package_name] = f"sqlite:///{db_path}?check_same_thread=False" + except Exception: + pass + return package_name + + def _ensure_requirements(): missing = [package for package, module_name in REQUIRED_PACKAGES if importlib.util.find_spec(module_name) is None] if not missing: @@ -46,7 +76,9 @@ setting = { } +ensure_sqlalchemy_bind() P = create_plugin_instance(setting) +ensure_sqlalchemy_bind(P.package_name) _ensure_requirements() try: