Sync all projects
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import requests
|
||||
from urllib.parse import quote, urljoin
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
except Exception:
|
||||
BeautifulSoup = None
|
||||
try:
|
||||
from base.spider import Spider as BaseSpider
|
||||
except Exception:
|
||||
BaseSpider = object
|
||||
|
||||
class Spider(BaseSpider):
|
||||
BASE_URL = 'https://www.yasetube.com'
|
||||
HEADERS = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36','Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8','Referer':'https://www.yasetube.com/'}
|
||||
CATS = {'nvce':'女厕偷拍','fc2-ppv':'FC2 PPV','me':'Mesubuta系列','milf':'MILF人妻无码','dalu':'自拍偷拍','madou':'品牌传媒'}
|
||||
|
||||
def __init__(self):
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(self.HEADERS)
|
||||
|
||||
def getName(self):
|
||||
return '亚色影库'
|
||||
|
||||
def init(self, extend=''):
|
||||
return None
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return any(x in url.lower() for x in ['.m3u8','.mp4','.flv','.mkv','.avi','.ts'])
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return True
|
||||
|
||||
def destroy(self):
|
||||
return None
|
||||
|
||||
def _get(self, url):
|
||||
url = url if str(url).startswith('http') else urljoin(self.BASE_URL, url)
|
||||
try:
|
||||
r = self.session.get(url, timeout=12, verify=False, allow_redirects=True)
|
||||
if not r.encoding or r.encoding.lower() == 'iso-8859-1':
|
||||
r.encoding = 'utf-8'
|
||||
return r.text
|
||||
except requests.RequestException:
|
||||
return ''
|
||||
|
||||
def _soup(self, html):
|
||||
return BeautifulSoup(html, 'html.parser') if BeautifulSoup else None
|
||||
|
||||
def _txt(self, s):
|
||||
return re.sub(r'\s+', ' ', s or '').strip()
|
||||
|
||||
def _abs(self, u):
|
||||
if not u:
|
||||
return ''
|
||||
if u.startswith('//'):
|
||||
return 'https:' + u
|
||||
return urljoin(self.BASE_URL, u)
|
||||
|
||||
def _meta(self, html, name):
|
||||
m = re.search(r'<meta[^>]+(?:property|name)=["\']%s["\'][^>]+content=["\']([^"\']+)' % re.escape(name), html, re.I)
|
||||
return self._txt(m.group(1)) if m else ''
|
||||
|
||||
def _id(self, url):
|
||||
url = self._abs(url).split('?')[0].rstrip('/')
|
||||
return url.replace(self.BASE_URL + '/', '')
|
||||
|
||||
def _parse_list(self, html):
|
||||
arr = []
|
||||
if BeautifulSoup and html:
|
||||
soup = self._soup(html)
|
||||
for a in soup.select('article.loop-video.thumb-block a[href]'):
|
||||
href = self._abs(a.get('href'))
|
||||
if '/video/' not in href:
|
||||
continue
|
||||
img = a.select_one('img')
|
||||
title = self._txt(a.get('title') or (img.get('alt') if img else '') or (a.select_one('header.entry-header span').get_text(' ', strip=True) if a.select_one('header.entry-header span') else ''))
|
||||
pic = self._abs((img.get('data-src') or img.get('src') or img.get('data-original')) if img else '')
|
||||
remark = self._txt(' '.join([x.get_text(' ', strip=True) for x in a.select('span.hd-video,span.views,span.duration')]))
|
||||
if title and href:
|
||||
arr.append({'vod_id':self._id(href),'vod_name':title,'vod_pic':pic,'vod_remarks':remark})
|
||||
if not arr:
|
||||
for it in re.findall(r'<article[^>]+loop-video[\s\S]*?</article>', html, re.I):
|
||||
h = re.search(r'<a[^>]+href=["\']([^"\']+)["\'][^>]*title=["\']([^"\']+)', it, re.I)
|
||||
p = re.search(r'<img[^>]+(?:data-src|src)=["\']([^"\']+)', it, re.I)
|
||||
if h:
|
||||
arr.append({'vod_id':self._id(h.group(1)),'vod_name':self._txt(h.group(2)),'vod_pic':self._abs(p.group(1)) if p else '','vod_remarks':'HD' if 'hd-video' in it else ''})
|
||||
seen, out = set(), []
|
||||
for v in arr:
|
||||
if v['vod_id'] not in seen:
|
||||
seen.add(v['vod_id'])
|
||||
out.append(v)
|
||||
return out
|
||||
|
||||
def _cats(self):
|
||||
html = self._get('/categories')
|
||||
classes = []
|
||||
if BeautifulSoup and html:
|
||||
soup = self._soup(html)
|
||||
for a in soup.select('a[href*="/video/category/"]'):
|
||||
href = a.get('href') or ''
|
||||
slug = href.rstrip('/').split('/')[-1]
|
||||
name = self._txt(a.get('title') or a.get_text(' ', strip=True))
|
||||
if slug and name and slug not in [x['type_id'] for x in classes]:
|
||||
classes.append({'type_id':slug,'type_name':name})
|
||||
if not classes:
|
||||
classes = [{'type_id':k,'type_name':v} for k,v in self.CATS.items()]
|
||||
return classes[:30]
|
||||
|
||||
def homeContent(self, filter=False):
|
||||
return {'class':self._cats(),'filters':{},'list':self.homeVideoContent()['list']}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {'list':self._parse_list(self._get('/'))[:20]}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, ext):
|
||||
pg = str(pg or '1')
|
||||
if tid in ['latest','home','']:
|
||||
url = '/' if pg == '1' else '/page/%s/' % pg
|
||||
else:
|
||||
url = '/video/category/%s/' % tid if pg == '1' else '/video/category/%s/page/%s/' % (tid, pg)
|
||||
return {'page':int(pg),'pagecount':999,'limit':20,'total':999,'list':self._parse_list(self._get(url))}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0] if isinstance(ids, list) else ids
|
||||
url = vid if str(vid).startswith('http') else self.BASE_URL + '/' + str(vid).lstrip('/')
|
||||
html = self._get(url)
|
||||
name = self._meta(html, 'og:title')
|
||||
pic = self._meta(html, 'og:image')
|
||||
desc = self._meta(html, 'og:description')
|
||||
if BeautifulSoup and html:
|
||||
soup = self._soup(html)
|
||||
h = soup.select_one('h1.entry-title')
|
||||
if h:
|
||||
name = self._txt(h.get_text(' ', strip=True)) or name
|
||||
im = soup.select_one('meta[itemprop="thumbnailUrl"]')
|
||||
if im and im.get('content'):
|
||||
pic = im.get('content') or pic
|
||||
ds = soup.select_one('meta[itemprop="description"]')
|
||||
if ds and ds.get('content'):
|
||||
desc = ds.get('content') or desc
|
||||
play = self._find_play(html) or url
|
||||
vod = {'vod_id':vid,'vod_name':name or str(vid),'vod_pic':self._abs(pic),'type_name':'','vod_year':'','vod_area':'','vod_actor':'','vod_director':'','vod_content':desc or name or '','vod_play_from':'嗅探','vod_play_url':'正片$%s' % play}
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
pg = str(pg or '1')
|
||||
kw = quote(key)
|
||||
url = '/?s=%s' % kw if pg == '1' else '/page/%s/?s=%s' % (pg, kw)
|
||||
return {'page':int(pg),'pagecount':999,'limit':20,'total':999,'list':self._parse_list(self._get(url))}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
u = self._abs(id)
|
||||
if self.isVideoFormat(u):
|
||||
return {'parse':0,'playUrl':'','url':u,'header':self.HEADERS}
|
||||
html = self._get(u)
|
||||
play = self._find_play(html)
|
||||
if play and self.isVideoFormat(play):
|
||||
return {'parse':0,'playUrl':'','url':play,'header':self.HEADERS}
|
||||
return {'parse':1,'playUrl':'','url':u,'header':self.HEADERS}
|
||||
|
||||
def _find_play(self, html):
|
||||
if not html:
|
||||
return ''
|
||||
pats = [r'<source[^>]+src=["\']([^"\']+)',r'<video[^>]+src=["\']([^"\']+)',r'(https?:\\?/\\?/[^"\'<>]+?\.(?:m3u8|mp4)(?:\?[^"\'<>]*)?)',r'file["\']?\s*[:=]\s*["\']([^"\']+)',r'url["\']?\s*[:=]\s*["\']([^"\']+\.(?:m3u8|mp4)[^"\']*)']
|
||||
for p in pats:
|
||||
m = re.search(p, html, re.I)
|
||||
if m:
|
||||
return self._abs(m.group(1).replace('\\/','/'))
|
||||
return ''
|
||||
@@ -1,557 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
TVBox 本地 Py/Js 爬虫聚合源(精简版)
|
||||
========================================
|
||||
仅保留扫描与配置生成功能。
|
||||
包含:子文件夹后缀、增量合并、锁定站点、分页浏览。
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
import hashlib
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
# ==========================================================================
|
||||
# 📂 【配置区】
|
||||
# ==========================================================================
|
||||
PY_DIR = "/storage/emulated/0/peekpro/py"
|
||||
JS_DIR = "/storage/emulated/0/peekpro/js"
|
||||
JAR_DIR = "/storage/emulated/0/peekpro/jar"
|
||||
SAVE_PATH = "/storage/emulated/0/peekpro/智能接口.json"
|
||||
LOGO_PATH = "/storage/emulated/0/peekpro/jar/头像.gif"
|
||||
|
||||
_LOCKED_SITES = [
|
||||
|
||||
]
|
||||
_LOCKED_KEYS = {""}
|
||||
|
||||
GENERATED_KEY_PREFIX = "local_auto_"
|
||||
PAGE_SIZE = 60
|
||||
# ==========================================================================
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.inited = False
|
||||
|
||||
self.cache = {
|
||||
"categories": [],
|
||||
"file_index": {},
|
||||
"sources": [],
|
||||
"source_index": {},
|
||||
"type_counts": {},
|
||||
}
|
||||
|
||||
self.status = {
|
||||
"scan_time": "-",
|
||||
"included": 0,
|
||||
"manual_sites": 0,
|
||||
"generated_sites": 0,
|
||||
"added": 0,
|
||||
"removed": 0,
|
||||
"unchanged": 0,
|
||||
"write_state": "尚未扫描",
|
||||
"written": False,
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "本地Py/Js聚合源(精简版)"
|
||||
|
||||
def init(self, extend):
|
||||
if self.inited:
|
||||
return
|
||||
self._scan_all()
|
||||
self._save_config_json()
|
||||
self.inited = True
|
||||
|
||||
# ==========================================================================
|
||||
# 🔍 【扫描核心】手动递归
|
||||
# ==========================================================================
|
||||
def _scan_dir(self, base_dir, ext_list):
|
||||
results = []
|
||||
if not base_dir:
|
||||
return results
|
||||
if not os.path.exists(base_dir):
|
||||
try:
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
except Exception:
|
||||
return results
|
||||
if not os.path.isdir(base_dir):
|
||||
return results
|
||||
try:
|
||||
entries = os.listdir(base_dir)
|
||||
except Exception:
|
||||
return results
|
||||
for entry in sorted(entries):
|
||||
full_path = os.path.join(base_dir, entry)
|
||||
if entry.startswith("."):
|
||||
continue
|
||||
if os.path.isdir(full_path):
|
||||
results.extend(self._scan_dir(full_path, ext_list))
|
||||
elif os.path.isfile(full_path):
|
||||
lower_name = entry.lower()
|
||||
for ext in ext_list:
|
||||
if lower_name.endswith(ext):
|
||||
name_no_ext = entry[: -len(ext)]
|
||||
results.append((full_path, name_no_ext, ext))
|
||||
break
|
||||
return results
|
||||
|
||||
def _get_sub_sfx(self, full_path, base_dir):
|
||||
try:
|
||||
rel = os.path.relpath(full_path, base_dir)
|
||||
rel_parts = rel.split(os.sep)
|
||||
subfolder = rel_parts[0] if len(rel_parts) > 1 else ""
|
||||
except (ValueError, IndexError):
|
||||
subfolder = ""
|
||||
if not subfolder:
|
||||
return ""
|
||||
if subfolder.startswith("[") and subfolder.endswith("]"):
|
||||
return subfolder
|
||||
return f"[{subfolder}]"
|
||||
|
||||
def _scan_all(self):
|
||||
sources = []
|
||||
self_path = os.path.abspath(__file__) if hasattr(__file__, '__file__') else ""
|
||||
|
||||
scan_specs = [
|
||||
(self.PY_DIR, [".py"], "PY", 0),
|
||||
(self.JS_DIR, [".js"], "JS", 1),
|
||||
]
|
||||
|
||||
for dir_path, ext_list, type_tag, order in scan_specs:
|
||||
files = self._scan_dir(dir_path, ext_list)
|
||||
for full_path, name, ext in files:
|
||||
if self_path and os.path.abspath(full_path) == self_path:
|
||||
continue
|
||||
|
||||
identity = type_tag + "|" + full_path
|
||||
tid = base64.b64encode(identity.encode("utf-8")).decode("utf-8")
|
||||
sub_sfx = self._get_sub_sfx(full_path, dir_path)
|
||||
display_name = f"【{type_tag}】{name}{sub_sfx}"
|
||||
|
||||
source = {
|
||||
"type_id": tid,
|
||||
"type_name": display_name,
|
||||
"identity": identity,
|
||||
"_path": full_path,
|
||||
"_ext": ext.lstrip("."),
|
||||
"_dir": dir_path,
|
||||
"_type_tag": type_tag,
|
||||
"_sk": (order, name),
|
||||
"_sub_sfx": sub_sfx,
|
||||
}
|
||||
sources.append(source)
|
||||
|
||||
self.cache["file_index"][tid] = {
|
||||
"path": full_path,
|
||||
"ext": source["_ext"],
|
||||
"dir": dir_path,
|
||||
"type_tag": type_tag,
|
||||
"sub_sfx": sub_sfx,
|
||||
}
|
||||
|
||||
sources.sort(key=lambda x: x["_sk"])
|
||||
|
||||
self.cache["sources"] = sources
|
||||
self.cache["source_index"] = {}
|
||||
self.cache["type_counts"] = {}
|
||||
for s in sources:
|
||||
self.cache["source_index"][s["type_id"]] = s
|
||||
tag = s["_type_tag"]
|
||||
self.cache["type_counts"][tag] = self.cache["type_counts"].get(tag, 0) + 1
|
||||
|
||||
self.cache["categories"] = [
|
||||
{"type_id": s["type_id"], "type_name": s["type_name"]}
|
||||
for s in sources
|
||||
]
|
||||
self.status["included"] = len(sources)
|
||||
|
||||
# ==========================================================================
|
||||
# 【增量合并配置生成】
|
||||
# ==========================================================================
|
||||
def _build_api(self, file_info):
|
||||
f_path = file_info["path"]
|
||||
base_dir = file_info["dir"]
|
||||
try:
|
||||
rel = os.path.relpath(f_path, base_dir)
|
||||
except ValueError:
|
||||
rel = os.path.basename(f_path)
|
||||
dir_name = os.path.basename(base_dir)
|
||||
return "./" + dir_name + "/" + rel
|
||||
|
||||
def _build_spider_value(self):
|
||||
jar_dir = self.JAR_DIR
|
||||
if not jar_dir or not os.path.isdir(jar_dir):
|
||||
return ""
|
||||
jar_files = []
|
||||
save_dir = os.path.dirname(self.SAVE_PATH)
|
||||
try:
|
||||
entries = sorted(os.listdir(jar_dir))
|
||||
except Exception:
|
||||
return ""
|
||||
for entry in entries:
|
||||
if entry.startswith("."):
|
||||
continue
|
||||
if entry.lower().endswith(".jar") and os.path.isfile(os.path.join(jar_dir, entry)):
|
||||
abs_jar = os.path.join(jar_dir, entry)
|
||||
try:
|
||||
rel = os.path.relpath(abs_jar, save_dir)
|
||||
except ValueError:
|
||||
rel = "jar/" + entry
|
||||
rel = "./" + rel.replace("\\", "/")
|
||||
if not rel.startswith("./"):
|
||||
rel = "./" + rel.lstrip("./")
|
||||
jar_files.append(rel)
|
||||
return ";".join(jar_files)
|
||||
|
||||
def _get_locked_api_set(self):
|
||||
locked = set()
|
||||
for site in self._LOCKED_SITES:
|
||||
for field in ("api", "homePage", "ext"):
|
||||
val = str(site.get(field, "")).strip()
|
||||
if val.startswith("./"):
|
||||
locked.add(val)
|
||||
return locked
|
||||
|
||||
def _is_generated_key(self, key):
|
||||
return str(key).startswith(self.GENERATED_KEY_PREFIX)
|
||||
|
||||
def _load_existing_config(self):
|
||||
if not os.path.isfile(self.SAVE_PATH):
|
||||
return None
|
||||
try:
|
||||
with open(self.SAVE_PATH, "r", encoding="utf-8") as fp:
|
||||
data = json.load(fp)
|
||||
return data if isinstance(data, dict) else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _generate_auto_sites(self):
|
||||
locked_paths = self._get_locked_api_set()
|
||||
sites = []
|
||||
for source in self.cache["sources"]:
|
||||
file_info = self.cache["file_index"].get(source["type_id"])
|
||||
if not file_info:
|
||||
continue
|
||||
f_path = file_info["path"]
|
||||
type_tag = file_info.get("type_tag", "PY")
|
||||
f_base = os.path.basename(f_path)
|
||||
if "." in f_base:
|
||||
f_base = f_base.rsplit(".", 1)[0]
|
||||
|
||||
api_path = self._build_api(file_info)
|
||||
sub_sfx = file_info.get("sub_sfx", "")
|
||||
|
||||
if api_path in locked_paths:
|
||||
continue
|
||||
|
||||
if not os.path.isfile(f_path):
|
||||
continue
|
||||
|
||||
key = self.GENERATED_KEY_PREFIX + type_tag.lower() + "_" + hashlib.sha256(
|
||||
(type_tag + "|" + f_path).encode("utf-8")
|
||||
).hexdigest()[:14]
|
||||
|
||||
sites.append({
|
||||
"key": key,
|
||||
"name": f"{f_base}{sub_sfx}",
|
||||
"type": 3,
|
||||
"searchable": 1,
|
||||
"quickSearch": 1,
|
||||
"filterable": 1,
|
||||
"api": api_path,
|
||||
})
|
||||
return sites
|
||||
|
||||
def _save_config_json(self):
|
||||
self.status["scan_time"] = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
new_auto_sites = self._generate_auto_sites()
|
||||
|
||||
existing = self._load_existing_config()
|
||||
manual_sites = []
|
||||
old_auto_keys = set()
|
||||
if existing and isinstance(existing.get("sites"), list):
|
||||
for site in existing["sites"]:
|
||||
if not isinstance(site, dict):
|
||||
continue
|
||||
k = site.get("key", "")
|
||||
if k in self._LOCKED_KEYS:
|
||||
continue
|
||||
if self._is_generated_key(k):
|
||||
old_auto_keys.add(k)
|
||||
else:
|
||||
manual_sites.append(site)
|
||||
|
||||
new_auto_keys = {s.get("key") for s in new_auto_sites}
|
||||
self.status["added"] = len(new_auto_keys - old_auto_keys)
|
||||
self.status["removed"] = len(old_auto_keys - new_auto_keys)
|
||||
self.status["unchanged"] = len(old_auto_keys & new_auto_keys)
|
||||
self.status["manual_sites"] = len(manual_sites)
|
||||
self.status["generated_sites"] = len(new_auto_sites)
|
||||
|
||||
config = {
|
||||
"logo": self.LOGO_PATH,
|
||||
"spider": self._build_spider_value(),
|
||||
"sites": list(self._LOCKED_SITES) + manual_sites + new_auto_sites,
|
||||
}
|
||||
|
||||
new_content = json.dumps(config, ensure_ascii=False, indent=2)
|
||||
if existing:
|
||||
old_content = json.dumps(existing, ensure_ascii=False, indent=2)
|
||||
if new_content == old_content:
|
||||
self.status["write_state"] = "配置未变化"
|
||||
self.status["written"] = True
|
||||
return
|
||||
|
||||
save_dir = os.path.dirname(self.SAVE_PATH)
|
||||
if save_dir and not os.path.exists(save_dir):
|
||||
try:
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
tmp = self.SAVE_PATH + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as fp:
|
||||
fp.write(new_content)
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
os.replace(tmp, self.SAVE_PATH)
|
||||
self.status["write_state"] = "已写入配置"
|
||||
self.status["written"] = True
|
||||
except Exception as e:
|
||||
self.status["write_state"] = "写入失败: {}".format(e)
|
||||
self.status["written"] = False
|
||||
|
||||
# ==========================================================================
|
||||
# 🔧 辅助方法
|
||||
# ==========================================================================
|
||||
def _get_file_info(self, tid):
|
||||
return self.cache["file_index"].get(tid)
|
||||
|
||||
def _count_str(self):
|
||||
c = self.cache["type_counts"]
|
||||
return f"共扫描到 {c.get('PY', 0)} 个PY文件, {c.get('JS', 0)} 个JS文件"
|
||||
|
||||
def _count_jar_str(self):
|
||||
if not os.path.isdir(self.JAR_DIR):
|
||||
return "jar 目录不存在"
|
||||
count = sum(
|
||||
1 for f in os.listdir(self.JAR_DIR)
|
||||
if f.lower().endswith(".jar") and os.path.isfile(os.path.join(self.JAR_DIR, f))
|
||||
)
|
||||
return f"共扫描到 {count} 个JAR文件"
|
||||
|
||||
def _page_number(self, value):
|
||||
try:
|
||||
return max(1, int(value))
|
||||
except Exception:
|
||||
return 1
|
||||
|
||||
def _paged_result(self, items, page, make_vod):
|
||||
total = len(items)
|
||||
page_size = max(1, self.PAGE_SIZE)
|
||||
page_count = max(1, (total + page_size - 1) // page_size)
|
||||
page = max(1, min(page, page_count))
|
||||
start = (page - 1) * page_size
|
||||
page_items = items[start: start + page_size]
|
||||
return {
|
||||
"page": page,
|
||||
"pagecount": page_count,
|
||||
"limit": page_size,
|
||||
"total": total,
|
||||
"list": [make_vod(item) for item in page_items],
|
||||
}
|
||||
|
||||
def _source_to_vod(self, source):
|
||||
return {
|
||||
"vod_id": source["type_id"],
|
||||
"vod_name": source["type_name"],
|
||||
"vod_pic": "",
|
||||
"vod_remarks": source["_type_tag"],
|
||||
}
|
||||
|
||||
# ==========================================================================
|
||||
# 📺 【TVBox 标准接口】
|
||||
# ==========================================================================
|
||||
def homeContent(self, filter):
|
||||
classes = [
|
||||
{"type_id": "all", "type_name": f"全部 ({len(self.cache['sources'])})"}
|
||||
]
|
||||
for tag in ("PY", "JS"):
|
||||
count = self.cache["type_counts"].get(tag, 0)
|
||||
if count:
|
||||
classes.append({"type_id": "type:" + tag, "type_name": f"{tag} ({count})"})
|
||||
return {"class": classes, "list": []}
|
||||
|
||||
def homeVod(self):
|
||||
info = self._count_str() + " | " + self._count_jar_str()
|
||||
return {"list": [{
|
||||
"vod_id": "__debug__",
|
||||
"vod_name": info,
|
||||
"vod_pic": "",
|
||||
"vod_remarks": "统计",
|
||||
}]}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, ext):
|
||||
page = self._page_number(pg)
|
||||
|
||||
if tid == "all":
|
||||
return self._paged_result(self.cache["sources"], page, self._source_to_vod)
|
||||
|
||||
if str(tid).startswith("type:"):
|
||||
source_type = str(tid).split(":", 1)[1].upper()
|
||||
items = [s for s in self.cache["sources"] if s["_type_tag"] == source_type]
|
||||
return self._paged_result(items, page, self._source_to_vod)
|
||||
|
||||
return self._category_content_single(tid)
|
||||
|
||||
def _category_content_single(self, tid):
|
||||
file_info = self._get_file_info(tid)
|
||||
if not file_info:
|
||||
return {"list": []}
|
||||
f_path = file_info["path"]
|
||||
if not os.path.exists(f_path):
|
||||
return {"list": []}
|
||||
|
||||
f_base = os.path.basename(f_path)
|
||||
if "." in f_base:
|
||||
f_base = f_base.rsplit(".", 1)[0]
|
||||
ext_name = file_info["ext"]
|
||||
type_tag = file_info.get("type_tag", "PY")
|
||||
sub_sfx = file_info.get("sub_sfx", "")
|
||||
|
||||
v_id = base64.b64encode(
|
||||
(type_tag + "|" + f_path).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
|
||||
vod_name = f"{f_base}{sub_sfx}"
|
||||
vod_remarks = "[" + ext_name.upper() + "]"
|
||||
|
||||
return {
|
||||
"page": 1, "pagecount": 1, "limit": 1, "total": 1,
|
||||
"list": [{
|
||||
"vod_id": v_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": "",
|
||||
"vod_remarks": vod_remarks,
|
||||
}]
|
||||
}
|
||||
|
||||
def detailContent(self, array):
|
||||
try:
|
||||
v_id_raw = str(array[0]) if isinstance(array, (list, tuple)) and array else str(array or "")
|
||||
|
||||
if v_id_raw == "__debug__":
|
||||
return {"list": [self._status_detail()]}
|
||||
|
||||
v_id_padded = v_id_raw + "=" * ((4 - len(v_id_raw) % 4) % 4)
|
||||
raw = base64.b64decode(v_id_padded).decode("utf-8", errors="ignore")
|
||||
|
||||
if "|" in raw:
|
||||
type_tag, f_path = raw.split("|", 1)
|
||||
else:
|
||||
type_tag, f_path = "PY", raw
|
||||
|
||||
if not os.path.exists(f_path):
|
||||
return {"list": [{"vod_name": "文件不存在", "vod_content": "路径: " + f_path}]}
|
||||
|
||||
f_base = os.path.basename(f_path)
|
||||
if "." in f_base:
|
||||
f_base = f_base.rsplit(".", 1)[0]
|
||||
ext_name = f_path.rsplit(".", 1)[-1] if "." in f_path else "unknown"
|
||||
|
||||
file_info = self.cache["file_index"].get(v_id_raw)
|
||||
api_path = self._build_api(file_info) if file_info else f_path
|
||||
sub_sfx = file_info.get("sub_sfx", "") if file_info else ""
|
||||
|
||||
site_info = {
|
||||
"key": f_base + "_" + ext_name,
|
||||
"name": f"{f_base}{sub_sfx}",
|
||||
"type": 3,
|
||||
"searchable": 1,
|
||||
"quickSearch": 1,
|
||||
"filterable": 1,
|
||||
"api": api_path,
|
||||
}
|
||||
info_text = json.dumps(site_info, ensure_ascii=False, indent=2)
|
||||
|
||||
return {"list": [{
|
||||
"vod_name": "[" + ext_name.upper() + "] " + f_base + sub_sfx,
|
||||
"vod_pic": "",
|
||||
"vod_play_from": "配置信息",
|
||||
"vod_play_url": "查看配置$" + f_path,
|
||||
"vod_content": (
|
||||
"配置文件: " + self.SAVE_PATH + "\n\n"
|
||||
"站点类型: " + type_tag + " | 后缀: ." + ext_name + "\n\n"
|
||||
"站点配置:\n" + info_text + "\n\n"
|
||||
"文件路径: " + f_path
|
||||
),
|
||||
}]}
|
||||
except Exception as e:
|
||||
return {"list": [{"vod_name": "解析错误", "vod_content": str(e)}]}
|
||||
|
||||
def _status_detail(self):
|
||||
c = self.cache["type_counts"]
|
||||
content = (
|
||||
"扫描时间: {scan_time}\n"
|
||||
"有效源: {included}\n"
|
||||
"分类统计: PY={py} JS={js}\n\n"
|
||||
"保留手工站点: {manual}\n"
|
||||
"自动注入站点: {generated}\n"
|
||||
"变更预览: +{added} -{removed} ={unchanged}\n"
|
||||
"写入状态: {state}\n\n"
|
||||
"{py_info}\n"
|
||||
"{jar_info}\n\n"
|
||||
"配置文件: {save}\n\n"
|
||||
"已扫描文件列表:\n"
|
||||
"{file_list}"
|
||||
).format(
|
||||
scan_time=self.status["scan_time"],
|
||||
included=self.status["included"],
|
||||
py=c.get("PY", 0), js=c.get("JS", 0),
|
||||
manual=self.status["manual_sites"],
|
||||
generated=self.status["generated_sites"],
|
||||
added=self.status["added"],
|
||||
removed=self.status["removed"],
|
||||
unchanged=self.status["unchanged"],
|
||||
state=self.status["write_state"],
|
||||
py_info=self._count_str(),
|
||||
jar_info=self._count_jar_str(),
|
||||
save=self.SAVE_PATH,
|
||||
file_list="\n".join(
|
||||
f" [{fin.get('type_tag', fin['ext'].upper())}] {fin['path']}"
|
||||
for fin in self.cache["file_index"].values()
|
||||
) or " 无",
|
||||
)
|
||||
return {
|
||||
"vod_id": "__debug__",
|
||||
"vod_name": "扫描状态详情",
|
||||
"vod_pic": "",
|
||||
"vod_remarks": self.status["write_state"],
|
||||
"vod_content": content,
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
page = self._page_number(pg)
|
||||
keyword = str(key or "").strip().lower()
|
||||
if not keyword:
|
||||
return {"list": []}
|
||||
items = [
|
||||
s for s in self.cache["sources"]
|
||||
if keyword in s["type_name"].lower()
|
||||
or keyword in s["_type_tag"].lower()
|
||||
or keyword in os.path.basename(s["_path"]).lower()
|
||||
]
|
||||
return self._paged_result(items, page, self._source_to_vod)
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = id.split("$")[-1] if "$" in id else id
|
||||
return {"url": url, "header": {}, "parse": 0}
|
||||
|
||||
def destroy(self):
|
||||
return "destroy"
|
||||
+26
-22
@@ -14,33 +14,37 @@ class Spider(Spider):
|
||||
# ==========================================================================
|
||||
# 📂 【配置区】
|
||||
# ==========================================================================
|
||||
PY_DIR = "/storage/emulated/0/TV/小百合/py"
|
||||
JS_DIR = "/storage/emulated/0/TV/小百合/js"
|
||||
JAR_DIR = "/storage/emulated/0/TV/小百合/jar"
|
||||
SAVE_PATH = "/storage/emulated/0/TV/小百合/自动接口.json"
|
||||
LOGO_PATH = "/storage/emulated/0/TV/小百合/jar/头像.gif"
|
||||
PY_DIR = "/storage/emulated/0/TV/海豚/py"
|
||||
JS_DIR = "/storage/emulated/0/TV/海豚/js"
|
||||
JAR_DIR = "/storage/emulated/0/TV/海豚/jar"
|
||||
SAVE_PATH = "/storage/emulated/0/TV/海豚/自动接口.json"
|
||||
LOGO_PATH = "/https://img.freepik.com/free-vector/cute-dolphin-swimming-cartoon-vector-icon-illustration-animal-nature-icon-isolated-flat-vector_138676-12582.jpg?semt=ais_hybrid&w=740&q=80"
|
||||
|
||||
# 🔒 锁定在 sites 第 0、1 位的配置,无论扫描结果如何始终存在
|
||||
_LOCKED_SITES = [
|
||||
{
|
||||
"key": "FishConfig",
|
||||
"name": "🍼┆设置┆中心[工具]",
|
||||
"key": "自动加载",
|
||||
"name": "自动加载",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/自动加载.py"
|
||||
},
|
||||
{
|
||||
"name": "弹幕",
|
||||
"key": "弹幕豆瓣",
|
||||
"type": 3,
|
||||
"api": "csp_FishConfig"
|
||||
},
|
||||
{
|
||||
"key": "Local",
|
||||
"name": "📁┆文件┆浏览[工具]",
|
||||
"type": 3,
|
||||
"api": "csp_Local",
|
||||
"searchable": 0,
|
||||
"changeable": 0,
|
||||
"indexs": 0,
|
||||
"style": {
|
||||
"type": "list"
|
||||
},
|
||||
"ext": "https://6800.kstore.vip/share.json"
|
||||
}
|
||||
"api": "csp_SecureDanmu",
|
||||
"searchable": 1,
|
||||
"jar": "https://ghfast.top/https://raw.githubusercontent.com/goodcommunication/mydm/main/danmu-spider-native.jar",
|
||||
"ext": {
|
||||
"apiUrls": [
|
||||
"https://danmu.iyo.us.ci/theft-dastardly-prognosis-hula-agenda2-dropkick|公益源",
|
||||
"https://logo.saodu.work:8888/87654321|公益源1",
|
||||
"https://dm.ljiaovm.com/luosen|公益源2"
|
||||
],
|
||||
"titleMappingsUrl": "https://ghfast.top/https://raw.githubusercontent.com/goodcommunication/mydm/main/yins.json",
|
||||
"filter": "./lib/douban.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
_LOCKED_KEYS = {"FishConfig", "Local"}
|
||||
# ==========================================================================
|
||||
|
||||
Reference in New Issue
Block a user