From 2d5bac07ad7cf20d5ce7380946acbc760cbeb8bc Mon Sep 17 00:00:00 2001 From: XC-Pro <19840+xc-pro@noreply.example.org> Date: Fri, 14 Aug 2026 11:27:51 +0200 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Cxbpq=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xbpq/stripchat.py | 190 +++++++++++++++++++++++++++++++++ xbpq/擦边.json | 21 ++++ xbpq/擦边1.json | 20 ++++ xbpq/擦边2.json | 24 +++++ xbpq/黄豆擦边.py | 266 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 521 insertions(+) create mode 100644 xbpq/stripchat.py create mode 100644 xbpq/擦边.json create mode 100644 xbpq/擦边1.json create mode 100644 xbpq/擦边2.json create mode 100644 xbpq/黄豆擦边.py diff --git a/xbpq/stripchat.py b/xbpq/stripchat.py new file mode 100644 index 0000000..bba8c74 --- /dev/null +++ b/xbpq/stripchat.py @@ -0,0 +1,190 @@ +# coding=utf-8 +#!/usr/bin/python +import sys, re, base64, json, requests, time +from base.spider import Spider +from datetime import datetime, timedelta +from urllib.parse import quote, urljoin +from urllib3.util.retry import Retry +sys.path.append('..') + +class Spider(Spider): + def init(self, extend="{}"): + origin = 'https://zh.stripchat.com' + self.host = origin + self.Doppiocdn = "doppiocdn.org" + #domains = [ + # "doppiocdn.com", # cf cdn只能图片用,播放不了,可能触发验证码风控 + # "doppiocdn.org", # 靠谱云cdn,国内有节点 + # "doppiocdn.net" # cft cdn + #] + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0" + self.headers = {'Origin': origin, 'Referer': f"{origin}/", 'User-Agent': user_agent, "Accept-Language": "zh,en;q=0.5"} + self.stripchat_preferredVideoCodec = "H265" + self.stripchat_key = "YzWScuyQRGAGcxx1KIJmiQ7BY9Vi35ftwLqUOVO8uoo=" + self.stripchat_pkey = "Fq6m2TO2ZeBkRPm9" + self.stripchat_play='0 0' + self.create_session_with_retry() + + def getName(self): return "StripChat" + + def isVideoFormat(self, url): + pass + + def manualVideoCheck(self): + pass + + def destroy(self): + pass + + def homeVideoContent(self): + pass + + def normalize_username_for_hdstream(self, username): + return username.replace('-', '_').lower() + + def homeContent(self, filter): + CLASSES = [{'type_name': '女主播g', 'type_id': 'girls'}, {'type_name': '情侣c', 'type_id': 'couples'}, {'type_name': '男主播m', 'type_id': 'men'}, {'type_name': '跨性别t', 'type_id': 'trans'}] + VALUE = [{'n': '中国', 'v': 'tagLanguageChinese'}, {'n': '亚洲', 'v': 'ethnicityAsian'}, {'n': '白人', 'v': 'ethnicityWhite'}, {'n': '拉丁', 'v': 'ethnicityLatino'}, {'n': '混血', 'v': 'ethnicityMultiracial'}, {'n': '印度', 'v': 'ethnicityIndian'}, {'n': '阿拉伯', 'v': 'ethnicityMiddleEastern'}, {'n': '黑人', 'v': 'ethnicityEbony'}] + VALUE_MEN = [{'n': '情侣', 'v': 'sexGayCouples'}, {'n': '直男', 'v': 'orientationStraight'}] + TIDS = ('girls', 'couples', 'men', 'trans') + filters = {tid: [{'key': 'tag', 'value': VALUE_MEN + VALUE if tid == 'men' else VALUE}] for tid in TIDS} + return {'class': CLASSES, 'filters': filters} + + def categoryContent(self, tid, pg, filter, extend): + # 🔥 修复:明确定义limit变量 + limit = 60 + offset = limit * (int(pg) - 1) + url = f"{self.host}/api/front/models?improveTs=false&removeShows=false&limit={limit}&offset={offset}&primaryTag={tid}&sortBy=stripRanking&rcmGrp=A&rbCnGr=true&prxCnGr=false&nic=false" + if 'tag' in extend: url += f'&filterGroupTags=[["{extend["tag"]}"]]' + rsp = self.session_get(url).json() + videos = [{"vod_id": str(v['username']), "vod_name": f"{self.country_code_to_flag(str(v['country']))}{v['username']}", "vod_pic": f"https://img.{self.Doppiocdn}/snapshot/{v['id']}/{v['snapshotTimestamp']}", "vod_remarks": "" if v.get('status') == "public" else "🎫"} for v in rsp.get('models', [])] + total = int(rsp.get('filteredCount', 0)) + return {"list": videos, "page": pg, "pagecount": (total + limit - 1) // limit, "limit": limit, "total": total} + + def detailContent(self, array): + username = array[0] + + try: + rsp = self.session_get(f"{self.host}/api/front/v2/models/username/{username}/cam").json() + info, user = rsp['cam'], rsp['user']['user'] + uid, isLive = str(user['id']), user['isLive'] + oldName = self.stripchat_play.rsplit(' ', 1)[-1] + if username != oldName: + timestp = int(time.time()) + self.stripchat_play = f"0 {timestp} {username}" + flag = self.country_code_to_flag(str(user['country']).strip()) + remark = "🔴 直播中" if isLive else "⚫ 已下播" + show = info.get('show') or info.get('groupShowAnnouncement') + if show: + startAt = show.get('createdAt') or show.get('startAt') + if startAt: remark = f"🎫 始于 {(datetime.strptime(startAt, '%Y-%m-%dT%H:%M:%SZ') + timedelta(hours=8)).strftime('%m月%d日 %H:%M')}" + director = f"{flag}{username}" + return {'list': [{"vod_id": username, "vod_name": str(info['topic'])[:80], "vod_pic": str(user['avatarUrl']), "vod_director": director, "vod_remarks": remark, 'vod_play_from': 'StripChat$$$LemonCams', 'vod_play_url': f"{uid}${uid}$$${uid}$lemon_{uid}"}]} + except: return {'list': []} + + def searchContent(self, key, quick, pg="1"): + if int(pg) > 1: return {} + tags = {'G': 'girls', 'C': 'couples', 'M': 'men', 'T': 'trans'} + parts = key.split(maxsplit=1) + tag, key = (tags.get(parts[0].upper()), parts[1].strip()) if len(parts) > 1 and parts[0].upper() in tags else ('girls', key.strip()) + rsp = self.session_get(f"{self.host}/api/front/v4/models/search/group/username?query={key}&limit=900&primaryTag={tag}").json() + return {'list': [{"vod_id": str(u['username']), "vod_name": f"{self.country_code_to_flag(str(u['country']))}{u['username']}", "vod_pic": f"https://img.{self.Doppiocdn}/snapshot/{u['id']}/{u['snapshotTimestamp']}", "vod_remarks": "" if u['status'] == "public" else "🎫"} for u in rsp.get('models', []) if u['isLive']]} + + def playerContent(self, flag, id, vipFlags): + if id.startswith('lemon'): + id = id.split('_')[1] + rsp = self.session_get(f"https://edge-hls.growcdnssedge.com/hls/{id}/master/{id}_auto.m3u8?playlistType=lowLatency").text + lines = rsp.strip().split('\n') + urls = [] + for i, line in enumerate(lines): + if '#EXT-X-STREAM-INF' in line: + qn_start = line.find('NAME="')+6 + qn = line[qn_start:line.find('"', qn_start)] + url = lines[i + 1] + urls.extend([qn, url]) + lemon_headers = { + 'User-Agent': self.headers.get('User-Agent'), + 'Origin': 'https://www.lemoncams.com', + 'Referer': 'https://www.lemoncams.com/' + } + return {"url": urls, "parse": '0', "header": lemon_headers} + + try: + rsp = self.session_get(f"https://edge-hls.{self.Doppiocdn}/hls/{id}/master/{id}_auto.m3u8?playlistType=lowLatency").text + lines = rsp.strip().split('\n') + psch, pkey, urls, processed = 'v2', self.stripchat_pkey, [], False + for i, line in enumerate(lines): + #if line.startswith('#EXT-X-MOUFLON:') and not processed: + # if len(parts := line.split(':')) >= 4: psch, pkey, processed = parts[2], parts[3], True + if '#EXT-X-STREAM-INF' in line: + qn_start = line.find('NAME="')+6 + qn = line[qn_start:line.find('"', qn_start)] + full_url = f"{lines[i+1]}&psch={psch}&pkey={pkey}&preferredVideoCodec={self.stripchat_preferredVideoCodec}" + urls.extend([qn, f"{self.getProxyUrl()}&url={quote(full_url)}"]) + headers = self.headers.copy() + headers.pop('Accept-Language', None) + return {"url": urls, "parse": '0', "header": headers} + except: return {"url": [], "parse": 0} + + def update_vod(self, username): + content_data = self.detailContent([username]).get('list')[0] + #content_data.pop('vod_id') + payload = {"json": json.dumps(content_data)} + self.post("http://127.0.0.1:9978/action?do=refresh&type=vod", data=payload) + + def localProxy(self, param): + url, type = param['url'], param.get('type', '') + if type == 'rec_img': + data = self.session_get(url, self.search_headers) + return [200, 'application/octet-stream', data.content] + rsp = self.session_get(url) + oldCode, oldtmp, username = self.stripchat_play.rsplit(' ') + timestp = int(time.time()) + is_time_up = (timestp - 10) > int(oldtmp) + is_code_changed = (int(oldCode) != 0 and rsp.status_code != int(oldCode)) + if is_time_up or is_code_changed: + self.stripchat_play = f"{rsp.status_code} {timestp} {username}" + self.log('计划更新') + self.update_vod(username) + if is_code_changed: + self.log('code变更') + self.post("http://127.0.0.1:9978/action?do=refresh&type=player") + return [404, "text/plain", ""] + if rsp.status_code == 403: rsp = self.session_get(re.sub(r'(_\d+p\d*)?\.m3u8', '_160p_blurred.m3u8', url)) + if rsp.status_code != 200: return [404, "text/plain", ""] + data = self.process_m3u8(rsp.text) if "#EXT-X-MOUFLON:URI:" in rsp.text else rsp.text + return [200, "application/vnd.apple.mpegur", data] + + URL_PATTERN = re.compile(r'https://media-hls\.doppiocdn\.\w+/b-hls-\d+/media\.mp4') + def process_m3u8(self, content): + lines = content.strip().split('\n') + for i, line in enumerate(lines): + if line.startswith('#EXT-X-MOUFLON:URI:') and 'media.mp4' in lines[i+1]: + mouflon = line.split(':', 2)[2].strip() + encrypted = re.sub(r'(_part\d+)?\.mp4$', '', mouflon).rsplit('_', 2)[1] + lines[i+1] = self.URL_PATTERN.sub(mouflon.replace(encrypted, self._decode(encrypted[::-1], self.stripchat_key)), lines[i+1]) + return '\n'.join(lines) + + def country_code_to_flag(self, code): + return ''.join(chr(ord(c.upper()) - ord('A') + 0x1F1E6) for c in code) if len(code) == 2 and code.isalpha() else code + + def _decode(self, encrypted_b64: str, key_b64: str) -> str: + # 补齐Base64填充,避免Incorrect padding错误 + missing_padding = len(encrypted_b64) % 4 + if missing_padding: + encrypted_b64 += '=' * (4 - missing_padding) + key_bytes = base64.b64decode(key_b64) + encrypted = base64.b64decode(encrypted_b64) + decrypted = bytearray(len(encrypted)) + for i in range(len(encrypted)): + decrypted[i] = encrypted[i] ^ (key_bytes[i % len(key_bytes)] & 0xFF) + return decrypted.decode('utf-8') + + def create_session_with_retry(self): + self.session = requests.Session() + retry = Retry(total=5, backoff_factor=0.3, status_forcelist=[429, 500, 502, 503, 504], raise_on_status=False) + adapter = requests.adapters.HTTPAdapter(max_retries=retry, pool_connections=100, pool_maxsize=100, pool_block=False) + self.session.mount('http://', adapter) + self.session.mount('https://', adapter) + + def session_get(self, url, headers=None, stream=False): return self.session.get(url, headers = self.headers if headers is None else headers, timeout=5, stream=stream, allow_redirects = True) diff --git a/xbpq/擦边.json b/xbpq/擦边.json new file mode 100644 index 0000000..288c027 --- /dev/null +++ b/xbpq/擦边.json @@ -0,0 +1,21 @@ +{ + "作者": "网络", + "站名": "51吃瓜擦边短剧", + "编码": "UTF-8", + "请求头": "User-Agent$MOBILE_UA", + "主页url": "https://hwm6z4.ryhvlsd.com/category/cbdj/", + "首页": "200", + "起始页": "0", + "分类url": "https://hwm6z4.ryhvlsd.com/category/{cateId}/{catePg}/[https://hwm6z4.ryhvlsd.com/category/{cateId}/];;mrcRAz", + "分类": "擦边短剧$cbdj", + "数组": "url mainEntityOfPage\"&&&&", + "副标题": "💗悠着点欣赏💗", + "链接": "&&<", + "链接": "

&&

title", + "副标题": "p:div[class*=\"module-item-cover\"]->text+p:div[class*=\"module-item-cover\"]->text[序号:2]", + "图片": "p:img->data-original", + "链接": "p:a->href", + "影片状态": "更新:&&
", + "影片年代": "p:div[class*=\"module-info-tag-link\"]->text", + "影片类型": "p:div[class*=\"module-info-tag-link\"]->text[含序号:3]", + "影片地区": "p:div[class*=\"module-info-tag-link\"]->text[含序号:2]", + "导演": "p:div[class*=\"module-info-item\"] span->text[含序号:3][替换:导演:>>空]", + "主演": "p:div[class*=\"module-info-item\"] span->text[含序号:4][替换:主演:>>空]", + "简介": "p:div[class*=\"module-info-heading\"]->text", + "线路数组": "p:div[class*=\"module-tab-items-box\"] div", + "线路标题": "p:span->text+(+p:small->text+)", + "播放数组": "p:div[class*=\"module-play-list\"]", + "播放列表": "p:a", + "播放标题": "p:span->text", + "播放链接": "p:a->href" +} \ No newline at end of file diff --git a/xbpq/黄豆擦边.py b/xbpq/黄豆擦边.py new file mode 100644 index 0000000..55baf33 --- /dev/null +++ b/xbpq/黄豆擦边.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- +import gzip +import hashlib +import hmac +import json +import os +import time +import uuid +import requests + +try: + from base.spider import Spider as BaseSpider +except Exception: + class BaseSpider: + pass + +class _AESCBC: + @staticmethod + def encrypt(data, key, iv): + try: + from Crypto.Cipher import AES + return AES.new(key, AES.MODE_CBC, iv).encrypt(_AESCBC.pad(data)) + except Exception: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + enc = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).encryptor() + return enc.update(_AESCBC.pad(data)) + enc.finalize() + + @staticmethod + def decrypt(data, key, iv): + try: + from Crypto.Cipher import AES + plain = AES.new(key, AES.MODE_CBC, iv).decrypt(data) + except Exception: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + dec = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).decryptor() + plain = dec.update(data) + dec.finalize() + return _AESCBC.unpad(plain) + + @staticmethod + def pad(data): + n = 16 - len(data) % 16 + return data + bytes([n]) * n + + @staticmethod + def unpad(data): + n = data[-1] if data else 0 + return data[:-n] if 1 <= n <= 16 else data + +class Spider(BaseSpider): + def __init__(self): + self.host = "https://xqjzvcvt.top" + self.api = self.host + "/api" + self.name = "黄豆短剧" + self.platform_key = "7961beb44246e3012ce228d6b5ced05a" + self.version = "2.0.0" + self.device_type = "web" + self.session_id = uuid.uuid4().hex + self.device_id = self.session_id + self.token = "" + self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "*/*", "Origin": self.host, "Referer": self.host + "/home", "Content-Type": "application/octet-stream"} + self.session = requests.Session() + self.session.headers.update(self.headers) + self.class_cache = None + self.filter_cache = {} + + def init(self, extend=""): + if extend: + try: + cfg = json.loads(extend) + self.host = (cfg.get("site") or cfg.get("base_url") or self.host).rstrip("/") + self.api = self.host + "/api" + self.token = cfg.get("token", self.token) + self.headers["Origin"] = self.host + self.headers["Referer"] = self.host + "/home" + self.session.headers.update(self.headers) + except Exception: + None + + def getName(self): + return self.name + + def homeContent(self, filter): + data = self._api("/drama/list", {"page": "1", "page_size": "18"}) + classes = self._classes() + return {"class": classes, "filters": self._filters(classes), "list": [self._vod(x) for x in self._list(data)], "parse": 0, "jx": 0} + + def categoryContent(self, tid, pg, filter, extend): + extend = extend or {} + if tid == "yuandou": + data = self._api("/drama/navBlock", {"code": "yuandou", "tab": "recommend", "page": str(pg)}) + items = self._nav_items(data) + else: + req = {"page": str(pg), "page_size": "18"} + if tid and tid not in ("all", "recommend"): + tabs = self._nav_filter(tid) + idx = self._int(extend.get("sub"), 0) + sub = tabs[idx] if tabs and 0 <= idx < len(tabs) else {} + flt = sub.get("filter", {}) if isinstance(sub, dict) else {} + req["cat_id"] = flt.get("cat_id", "") + if flt.get("tag_id"): + req["tag_id"] = flt.get("tag_id", "") + req["order"] = flt.get("order", "") or extend.get("order", "") + elif extend.get("order"): + req["order"] = extend.get("order") + if extend.get("update_status"): + req["update_status"] = extend.get("update_status") + data = self._api("/drama/list", req) + items = self._list(data) + return {"page": int(pg), "pagecount": int(pg) if len(items) < 18 else int(pg) + 1, "limit": 18, "total": 99999, "list": [self._vod(x) for x in items], "parse": 0, "jx": 0} + + def detailContent(self, ids): + vid = str(ids[0]).replace("rp_", "") + obj = self._api("/drama/detail", {"id": vid}) + data = obj.get("data", obj) if isinstance(obj, dict) else {} + if not isinstance(data, dict): + return {"list": []} + data = self._unlock(data) + vod_id = self._sid(data.get("id") or data.get("drama_id") or vid) + name = data.get("name") or data.get("title") or data.get("t") or vod_id + eps = data.get("episodes") if isinstance(data.get("episodes"), list) else [] + count = self._int(data.get("episode_count") or data.get("free_episodes"), len(eps) or 1) + play = [] + if eps: + for i, ep in enumerate(eps, 1): + seq = ep.get("seq") or ep.get("episode") or ep.get("ep") or i + play.append("%s$%s|%s" % (ep.get("name") or ep.get("title") or "第%s集" % seq, vod_id, seq)) + else: + play = ["第%s集$%s|%s" % (i, vod_id, i) for i in range(1, count + 1)] + vod = {"vod_id": vod_id, "vod_name": name, "vod_pic": self._pic(data), "type_name": data.get("category") or data.get("type") or "", "vod_year": "", "vod_area": "", "vod_remarks": data.get("update_label") or "全%s集" % count, "vod_actor": "", "vod_director": "", "vod_content": data.get("description") or data.get("summary") or name, "vod_play_from": self.name, "vod_play_url": "#".join(play)} + return {"list": [vod], "parse": 0, "jx": 0} + + def searchContent(self, key, quick, pg="1"): + data = self._api("/drama/list", {"page": str(pg), "page_size": "18", "keywords": str(key)}) + items = self._list(data) + return {"page": int(pg), "pagecount": int(pg) if len(items) < 18 else int(pg) + 1, "limit": 18, "total": 99999, "list": [self._vod(x) for x in items], "parse": 0, "jx": 0} + + def playerContent(self, flag, id, vipFlags): + vid, seq = self._split(id) + obj = self._api("/drama/play", {"id": vid, "seq": str(seq)}, True) + data = obj.get("data", {}) if isinstance(obj, dict) else {} + url = data.get("m3u8") or data.get("url") or self._hls(vid, seq) + return {"parse": 0, "playUrl": "", "url": url, "jx": 0, "header": {"User-Agent": self.headers["User-Agent"], "Referer": self.host + "/home", "Origin": self.host}} + + def _api(self, path, data=None, silent=False): + path = "/" + path.lstrip("/") + rid = str(uuid.uuid4()) + key = self._key(rid) + iv = os.urandom(16) + raw = json.dumps({"token": self.token or "", "deviceId": self.device_id, "data": data or {}}, ensure_ascii=False, separators=(",", ":")).encode("utf-8") + body = iv + _AESCBC.encrypt(gzip.compress(raw), key, iv) + ts = int(time.time()) + sign = hashlib.sha256(("Dart|%s|%s|%s|%s" % (self.session_id, rid, ts, path)).encode("utf-8")).hexdigest() + "-" + str(ts) + h = dict(self.headers) + h.update({"version": self.version, "deviceType": self.device_type, "time": str(ts), "sign": sign, "requestId": rid, "sessionId": self.session_id, "deviceBrand": "", "deviceModel": "", "systemName": "", "systemVersion": ""}) + try: + r = self.session.post(self.api + path, data=body, headers=h, timeout=20, verify=False) + r.raise_for_status() + return self._decode(r.content, rid) + except Exception: + return {} + + def _key(self, rid): + return hmac.new(self.platform_key.encode("utf-8"), bytes.fromhex(str(rid).replace("-", "")), hashlib.sha256).digest() + + def _decode(self, blob, rid): + if not blob or len(blob) < 32 or (len(blob) - 16) % 16 != 0: + try: + return json.loads(blob.decode("utf-8")) + except Exception: + return {} + plain = _AESCBC.decrypt(blob[16:], self._key(rid), blob[:16]) + if plain[:2] == b"\x1f\x8b": + plain = gzip.decompress(plain) + return json.loads(plain.decode("utf-8")) + + def _classes(self): + if self.class_cache: + return self.class_cache + arr = [{"type_id": "all", "type_name": "全部短剧"}] + data = self._api("/drama/navList", {}) + for item in self._list(data.get("data", data) if isinstance(data, dict) else data): + tid = str(item.get("code") or item.get("id") or item.get("cat_id") or "") + name = item.get("name") or item.get("title") or tid + if tid and name: + arr.append({"type_id": tid, "type_name": name}) + self.class_cache = arr + return arr + + def _filters(self, classes): + common = [{"key": "order", "name": "排序", "value": [{"n": "默认", "v": ""}, {"n": "最新", "v": "new"}, {"n": "最热", "v": "hot"}]}, {"key": "update_status", "name": "状态", "value": [{"n": "全部", "v": ""}, {"n": "连载", "v": "0"}, {"n": "完结", "v": "1"}]}] + fs = {} + for c in classes: + tid = c["type_id"] + tabs = self._nav_filter(tid) if tid not in ("all", "yuandou") else [] + fs[tid] = ([{"key": "sub", "name": "子分类", "value": [{"n": t.get("name", "默认"), "v": str(i)} for i, t in enumerate(tabs)]}] if tabs else []) + common + return fs + + def _nav_filter(self, code): + if code not in self.filter_cache: + data = self._api("/drama/navFilter", {"code": str(code)}) + self.filter_cache[code] = self._list(data.get("data", data) if isinstance(data, dict) else data) + return self.filter_cache.get(code, []) + + def _list(self, data): + if isinstance(data, list): + return data + if not isinstance(data, dict): + return [] + if isinstance(data.get("list"), list): + return data["list"] + if isinstance(data.get("items"), list): + return data["items"] + if isinstance(data.get("data"), list): + return data["data"] + if isinstance(data.get("data"), dict): + return self._list(data["data"]) + return [] + + def _nav_items(self, data): + blocks = self._list(data.get("data", data) if isinstance(data, dict) else data) + items = [] + for b in blocks: + if isinstance(b, dict) and isinstance(b.get("items"), list): + items += b.get("items") + elif isinstance(b, dict) and (b.get("id") or b.get("drama_id")): + items.append(b) + return items + + def _vod(self, item): + item = item or {} + vid = self._sid(item.get("id") or item.get("drama_id") or "") + remarks = item.get("update_label") or item.get("corner") or ("全%s集" % item.get("episode_count") if item.get("episode_count") else "") + return {"vod_id": vid, "vod_name": item.get("name") or item.get("title") or item.get("t") or vid, "vod_pic": self._pic(item), "vod_remarks": remarks} + + def _pic(self, item): + return item.get("img_y") or item.get("img_x") or item.get("img") or item.get("cover") or item.get("pic") or "" + + def _unlock(self, d): + eps = d.get("episodes") + if isinstance(eps, list): + for ep in eps: + if isinstance(ep, dict): + ep["is_buy"] = True + ep["type"] = "free" + ep["price"] = 0 + ep["methods"] = [] + d.update({"pay_type": "free", "money": 0, "episode_price": 0, "points_price": 0, "can_vip_watch": True, "is_buy_whole": True, "vip_episodes": [], "coin_episodes": [], "points_episodes": []}) + return d + + def _sid(self, x): + return str(x or "").replace("rp_", "") + + def _split(self, x): + p = str(x).split("|", 1) + return self._sid(p[0]), p[1] if len(p) > 1 and p[1] else "1" + + def _hls(self, vid, seq): + return "%s/api/drama/hls/%s/%s/play.m3u8?line=free" % (self.host, self._sid(vid), seq) + + def _int(self, x, d=0): + try: + return int(x) + except Exception: + return d \ No newline at end of file