delete some trash

This commit is contained in:
2026-04-01 11:48:14 +09:00
parent 6c8cdf9f07
commit db2b5fefcc
14 changed files with 32 additions and 956 deletions
-15
View File
@@ -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
-15
View File
@@ -1,15 +0,0 @@
# linkkf
FlaskFarm용 `linkkf.tv` 플러그인입니다.
설치 전 의존 패키지:
`pip install -r requirements.txt`
플러그인 최초 로딩 시 누락된 패키지는 자동 설치를 시도합니다.
현재 기준 주요 기능:
- 카테고리 목록 조회
- 작품 분석 및 회차 목록 조회
- 재생 URL 추출 및 브라우저 플레이어 프록시
- ffmpeg 다운로드 대기열 연동
- 다운로드 이력 목록 표시
-2
View File
@@ -1,2 +0,0 @@
from .setup import P
-11
View File
@@ -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"
}
-8
View File
@@ -1,8 +0,0 @@
title: "linkkf"
version: "0.3.2.0"
package_name: "linkkf"
developer: "projectdx && persuade"
description: "linkkf 사이트에서 애니 다운로드"
home: "https://linkkf.tv"
more: ""
-17
View File
@@ -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
-137
View File
@@ -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())
-303
View File
@@ -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)
-273
View File
@@ -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
-37
View File
@@ -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()
-4
View File
@@ -1,4 +0,0 @@
cloudscraper
beautifulsoup4
requests-cache
lxml
-90
View File
@@ -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())
-44
View File
@@ -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"
+32
View File
@@ -1,7 +1,10 @@
import importlib.util import importlib.util
import os
import subprocess import subprocess
import sys import sys
import yaml
from framework import F
from plugin import * 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(): def _ensure_requirements():
missing = [package for package, module_name in REQUIRED_PACKAGES if importlib.util.find_spec(module_name) is None] missing = [package for package, module_name in REQUIRED_PACKAGES if importlib.util.find_spec(module_name) is None]
if not missing: if not missing:
@@ -46,7 +76,9 @@ setting = {
} }
ensure_sqlalchemy_bind()
P = create_plugin_instance(setting) P = create_plugin_instance(setting)
ensure_sqlalchemy_bind(P.package_name)
_ensure_requirements() _ensure_requirements()
try: try: