diff --git a/api/6v.py b/api/6v.py
new file mode 100644
index 0000000..ca83ecb
--- /dev/null
+++ b/api/6v.py
@@ -0,0 +1,306 @@
+# coding=utf-8
+#!/usr/bin/python
+import re
+import sys
+from html import unescape
+from urllib.parse import urljoin, quote
+
+sys.path.append('..')
+from base.spider import Spider
+
+
+class Spider(Spider):
+ host = 'https://www.xb6v.org'
+ headers = {
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120 Mobile Safari/537.36',
+ 'Referer': 'https://www.xb6v.org/',
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'Accept-Language': 'zh-CN,zh;q=0.9',
+ }
+ classes = [
+ {'type_name': '首页', 'type_id': '/'},
+ {'type_name': '喜剧片', 'type_id': '/xijupian/'},
+ {'type_name': '动作片', 'type_id': '/dongzuopian/'},
+ {'type_name': '爱情片', 'type_id': '/aiqingpian/'},
+ {'type_name': '科幻片', 'type_id': '/kehuanpian/'},
+ {'type_name': '恐怖片', 'type_id': '/kongbupian/'},
+ {'type_name': '剧情片', 'type_id': '/juqingpian/'},
+ {'type_name': '战争片', 'type_id': '/zhanzhengpian/'},
+ {'type_name': '纪录片', 'type_id': '/jilupian/'},
+ {'type_name': '动画片', 'type_id': '/donghuapian/'},
+ {'type_name': '电视剧', 'type_id': '/dianshiju/'},
+ {'type_name': '综艺', 'type_id': '/ZongYi/'},
+ ]
+
+ def getName(self):
+ return '6v影视'
+
+ def init(self, extend=''):
+ pass
+
+ def isVideoFormat(self, url):
+ pass
+
+ def manualVideoCheck(self):
+ pass
+
+ def destroy(self):
+ pass
+
+ def homeContent(self, filter):
+ return {'class': self.classes}
+
+ def homeVideoContent(self):
+ return {'list': self._parse_list(self._html(self.host + '/'))}
+
+ # 兼容部分 Python Spider 壳的命名
+ def homeVod(self):
+ return self.homeVideoContent()
+
+ def categoryContent(self, tid, pg, filter, extend):
+ page = int(pg or 1)
+ path = tid or '/'
+ if page > 1:
+ if path.endswith('/'):
+ path = path + 'index_%d.html' % page
+ else:
+ path = path.rstrip('/') + '/index_%d.html' % page
+ html = self._html(self._abs(path))
+ videos = self._parse_list(html)
+ return {'list': videos, 'page': page, 'pagecount': page + 1 if videos else page, 'limit': 18, 'total': 999999 if videos else 0}
+
+ def detailContent(self, ids):
+ url = ids[0] if isinstance(ids, list) else ids
+ url = self._abs(url)
+ html = self._html(url)
+ title = self._first([r'
\s*([^<]+?)(?:-|_|\|).*?', r']*>(.*?)
'], html, '未知影片')
+ pic = self._first([r']+class=["\'][^"\']*thumbnail[^"\']*["\'][\s\S]*?
![]()
]+(?:src|data-original|data-src)=["\']([^"\']+)["\']', r'
![]()
]+(?:src|data-original|data-src)=["\']([^"\']+\.(?:jpg|jpeg|png|webp)[^"\']*)["\']'], html, '')
+ desc = self._first([r'◎简\s*介\s*([\s\S]*?)(?:◎|
|$)', r']+name=["\']description["\'][^>]+content=["\']([^"\']+)["\']'], html, '暂无简介')
+ lines = self._parse_detail_lines(html)
+ play_from = '$$$'.join([x['name'] for x in lines]) or '详情页'
+ play_url = '$$$'.join(['#'.join(['%s$%s' % (ep['title'], ep['url']) for ep in x['episodes']]) for x in lines]) or ('打开详情页$' + url)
+ vod = {
+ 'vod_id': url,
+ 'vod_name': self._clean(title),
+ 'vod_pic': self._abs(pic),
+ 'type_name': '6v影视',
+ 'vod_year': self._year(title + ' ' + html),
+ 'vod_area': '',
+ 'vod_remarks': '',
+ 'vod_actor': '',
+ 'vod_director': '',
+ 'vod_content': self._clean(desc),
+ 'vod_play_from': play_from,
+ 'vod_play_url': play_url,
+ }
+ return {'list': [vod]}
+
+ def searchContent(self, key, quick, pg='1'):
+ url = self.host + '/e/search/11index.php'
+ body = 'keyboard=%s&show=title&tempid=1&tbname=article&mid=1&dopost=search&submit=' % quote(key or '')
+ html = self._html(url, method='post', data=body, extra_headers={'Content-Type': 'application/x-www-form-urlencoded'})
+ return {'list': self._parse_list(html)}
+
+ def playerContent(self, flag, id, vipFlags):
+ url = self._abs(id)
+ low = url.lower()
+ if low.startswith('magnet:') or any(x in low for x in ['pan.quark.cn', 'pan.baidu.com', 'xunlei.com', 'aliyundrive.com', 'alipan.com', 'cloud.189.cn', 'yun.139.com', '123pan']):
+ return {'parse': 0, 'jx': 0, 'url': url, 'header': self.headers}
+ real = self._resolve_play_url(url)
+ return {'parse': 0, 'jx': 0, 'url': real or url, 'header': {'User-Agent': self.headers['User-Agent'], 'Referer': self.host + '/'}}
+
+ def localProxy(self, param):
+ return None
+
+ def _html(self, url, method='get', data=None, extra_headers=None):
+ headers = dict(self.headers)
+ if extra_headers:
+ headers.update(extra_headers)
+ try:
+ if method.lower() == 'post':
+ res = self.fetch(url, headers=headers, data=data, method='post', timeout=20)
+ else:
+ res = self.fetch(url, headers=headers, timeout=20)
+ return self._res_text(res)
+ except Exception:
+ # 某些壳不支持 method 参数,POST 搜索失败时返回空;分类/首页不受影响
+ try:
+ if method.lower() == 'post':
+ res = self.fetch(url, headers=headers, postData=data, timeout=20)
+ return self._res_text(res)
+ except Exception:
+ pass
+ return ''
+
+ def _res_text(self, res):
+ if res is None:
+ return ''
+ if isinstance(res, str):
+ return res
+ if isinstance(res, bytes):
+ return self._decode(res)
+ if isinstance(res, dict):
+ val = res.get('content') or res.get('body') or res.get('data') or res.get('text') or ''
+ if isinstance(val, bytes):
+ return self._decode(val)
+ return str(val or '')
+ if hasattr(res, 'content'):
+ val = getattr(res, 'content')
+ if isinstance(val, bytes):
+ return self._decode(val)
+ return str(val or '')
+ if hasattr(res, 'text'):
+ return str(getattr(res, 'text') or '')
+ return str(res)
+
+ def _decode(self, data):
+ for enc in ('utf-8', 'gbk', 'gb18030'):
+ try:
+ txt = data.decode(enc)
+ if '锟斤拷' not in txt and '\ufffd' not in txt:
+ return txt
+ except Exception:
+ pass
+ try:
+ return data.decode('utf-8', errors='ignore')
+ except Exception:
+ return ''
+
+ def _abs(self, url):
+ if not url:
+ return ''
+ if str(url).startswith('magnet:'):
+ return url
+ return urljoin(self.host + '/', str(url).replace('&', '&'))
+
+ def _clean(self, text):
+ text = re.sub(r'