Add remaining linkkf core files
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user