py_bilibili.py 128 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975
  1. # coding=utf-8
  2. # !/usr/bin/python
  3. import sys, os, json
  4. from base.spider import Spider
  5. from requests import session, utils, get as requests_get
  6. from requests.adapters import HTTPAdapter, Retry
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. import threading
  9. import hashlib
  10. import time
  11. import random
  12. import base64
  13. from functools import reduce
  14. from urllib.parse import quote, urlencode
  15. sys.path.append('..')
  16. dirname, filename = os.path.split(os.path.abspath(__file__))
  17. sys.path.append(dirname)
  18. class Spider(Spider):
  19. #默认设置
  20. defaultConfig = {
  21. 'currentVersion': "20231213_1",
  22. #【建议通过扫码确认】设置Cookie,在双引号内填写
  23. 'raw_cookie_line': "",
  24. #如果主cookie没有vip,可以设置第二cookie,仅用于播放会员番剧,所有的操作、记录还是在主cookie,不会同步到第二cookie
  25. 'raw_cookie_vip': "",
  26. #主页默认显示3图
  27. 'maxHomeVideoContent': '3',
  28. #收藏标签默认显示追番1,追剧2,默认收藏夹0
  29. 'favMode': '0',
  30. #部分视频列表分页,限制每次加载数量
  31. 'page_size': 12,
  32. #上传播放进度间隔时间,单位秒,b站默认间隔15,0则不上传播放历史
  33. 'heartbeatInterval': '15',
  34. #视频默认画质ID
  35. 'vodDefaultQn': '80',
  36. #视频默认解码ID
  37. 'vodDefaultCodec': '7',
  38. #音频默认码率ID
  39. 'vodDefaultAudio': '30280',
  40. #获取视频热门评论
  41. 'show_vod_hot_reply': True,
  42. #从正片中拆分出番剧的预告
  43. 'hide_bangumi_preview': True,
  44. #登陆会员账号后,影视播放页不显示会员专享的标签,更简洁
  45. 'hide_bangumi_vip_badge': True,
  46. #番剧(热门)列表使用横图
  47. 'bangumi_horizontal_cover': True,
  48. #非会员播放会员专享视频时,添加一个页面可以使用解析源,解析源自行解决
  49. 'bangumi_vip_parse': True,
  50. #付费视频添加一个页面可以使用解析,解析源自行解决
  51. 'bangumi_pay_parse': True,
  52. #是否显示直播标签筛选中分区的细化标签, 0为不显示,1为显示
  53. 'showLiveFilterTag': '0',
  54. #主页标签排序, 未登录或cookie失效时自动隐藏动态、收藏、关注、历史
  55. 'cateManual': [
  56. "推荐",
  57. "影视",
  58. "直播",
  59. "动态",
  60. "频道",
  61. "收藏",
  62. "关注",
  63. "历史",
  64. "搜索",
  65. ],
  66. #自定义推荐标签的筛选
  67. 'tuijianLis': [
  68. "热门",
  69. "排行榜",
  70. "每周必看",
  71. "入站必刷",
  72. "番剧时间表",
  73. "国创时间表"
  74. ],
  75. 'rankingLis': [
  76. "动画",
  77. "音乐",
  78. "舞蹈",
  79. "游戏",
  80. "鬼畜",
  81. "知识",
  82. "科技",
  83. "运动",
  84. "生活",
  85. "美食",
  86. "动物",
  87. "汽车",
  88. "时尚",
  89. "娱乐",
  90. "影视",
  91. "原创",
  92. "新人",
  93. ],
  94. }
  95. #在动态标签的筛选中固定显示他,n为用户名或任意都可以,v必须为准确的UID
  96. focus_on_up_list = [
  97. #{"n":"徐云流浪中国", "v":"697166795"},
  98. ]
  99. #在搜索标签的筛选中固定显示搜索词
  100. focus_on_search_key = []
  101. def getName(self):
  102. return "哔哩哔哩"
  103. def load_config(self):
  104. try:
  105. with open(f"{self.configdir}/config.json",encoding="utf-8") as f:
  106. self.userConfig = json.load(f)
  107. old_config = {
  108. 'master': 'cookie_dic',
  109. 'vip': 'cookie_vip_dic',
  110. 'fake': 'cookie_fake_dic',
  111. }
  112. for _type, old in old_config.items():
  113. old = self.userConfig.get(old)
  114. if old:
  115. if not self.userConfig.get('users'):
  116. self.userConfig['users'] = {}
  117. self.userConfig['users'][_type] = {'cookies_dic': old}
  118. users = self.userConfig.get('users', {})
  119. if users.get('master') and users['master'].get('cookies_dic'):
  120. self.session_master.cookies = utils.cookiejar_from_dict(users['master']['cookies_dic'])
  121. self.userid = users['master']['userid']
  122. if users.get('fake') and users['fake'].get('cookies_dic'):
  123. self.session_fake.cookies = utils.cookiejar_from_dict(users['fake']['cookies_dic'])
  124. except:
  125. self.userConfig = {}
  126. self.userConfig = {**self.defaultConfig, **self.userConfig}
  127. dump_config_lock = threading.Lock()
  128. def dump_config(self):
  129. needSaveConfig = ['users', 'channel_list', 'cateLive', 'cateManualLive', 'cateManualLiveExtra']
  130. userConfig_new = {}
  131. for key, value in self.userConfig.items():
  132. dafalutValue = self.defaultConfig.get(key)
  133. if dafalutValue != None and value != dafalutValue or key in needSaveConfig:
  134. userConfig_new[key] = value
  135. self.dump_config_lock.acquire()
  136. with open(f"{self.configdir}/config.json", 'w', encoding="utf-8") as f:
  137. data = json.dumps(userConfig_new, indent=1, ensure_ascii=False)
  138. f.write(data)
  139. self.dump_config_lock.release()
  140. pool = ThreadPoolExecutor(max_workers=8)
  141. task_pool = []
  142. # 主页
  143. def homeContent(self, filter):
  144. self.pool.submit(self.add_live_filter)
  145. self.pool.submit(self.add_channel_filter)
  146. self.pool.submit(self.add_search_key)
  147. self.pool.submit(self.add_focus_on_up_filter)
  148. self.pool.submit(self.get_tuijian_filter)
  149. self.pool.submit(self.add_fav_filter)
  150. #self.pool.submit(self.homeVideoContent)
  151. needLogin = ['频道', '动态', '收藏', '关注', '历史']
  152. cateManual = self.userConfig['cateManual']
  153. if not self.userid and not 'UP' in cateManual or not '动态' in cateManual and not 'UP' in cateManual:
  154. cateManual += ['UP']
  155. classes = []
  156. for k in cateManual:
  157. if k in needLogin and not self.userid:
  158. continue
  159. classes.append({
  160. 'type_name': k,
  161. 'type_id': k
  162. })
  163. self.add_focus_on_up_filter_event.wait()
  164. if 'UP' in cateManual:
  165. self.config["filter"].update({'UP': self.config["filter"].pop('动态')})
  166. result = {'class': classes}
  167. self.add_live_filter_event.wait()
  168. self.add_channel_filter_event.wait()
  169. self.add_fav_filter_event.wait()
  170. self.add_search_key_event.wait()
  171. if filter:
  172. result['filters'] = self.config['filter']
  173. self.pool.submit(self.dump_config)
  174. return result
  175. # 用户cookies
  176. userid = csrf = ''
  177. session_master = session()
  178. session_vip = session()
  179. session_fake = session()
  180. con = threading.Condition()
  181. getCookie_event = threading.Event()
  182. retries = Retry(total=5,
  183. #status_forcelist=[ 500, 502, 503, 504 ],
  184. backoff_factor=0.1)
  185. adapter = HTTPAdapter(max_retries=retries)
  186. session_master.mount('https://', adapter)
  187. session_vip.mount('https://', adapter)
  188. session_fake.mount('https://', adapter)
  189. def getCookie_dosth(self, co):
  190. c = co.strip().split('=', 1)
  191. if not '%' in c[1]:
  192. c[1] = quote(c[1])
  193. return c
  194. def getCookie(self, _type='master'):
  195. raw_cookie = 'raw_cookie_line'
  196. if _type == 'vip':
  197. raw_cookie = 'raw_cookie_vip'
  198. raw_cookie = self.userConfig.get(raw_cookie)
  199. users = self.userConfig.get('users', {})
  200. user = users.get(_type, {})
  201. if not raw_cookie and not user:
  202. if _type == 'master':
  203. self.getCookie_event.set()
  204. with self.con:
  205. self.con.notifyAll()
  206. return
  207. cookies_dic = user.get('cookies_dic', {})
  208. if raw_cookie:
  209. cookies_dic = dict(map(self.getCookie_dosth, raw_cookie.split(';')))
  210. cookies = utils.cookiejar_from_dict(cookies_dic)
  211. url = 'https://api.bilibili.com/x/web-interface/nav'
  212. content = self.fetch(url, headers=self.header, cookies=cookies)
  213. res = json.loads(content.text)
  214. user['isLogin'] = 0
  215. if res["code"] == 0:
  216. user['isLogin'] = 1
  217. user['userid'] = res["data"]['mid']
  218. user['face'] = res['data']['face']
  219. user['uname'] = res['data']['uname']
  220. user['cookies_dic'] = cookies_dic
  221. user['isVIP'] = int(res['data']['vipStatus'])
  222. if _type == 'master':
  223. self.session_master.cookies = cookies
  224. self.userid = user['userid']
  225. self.csrf = cookies_dic['bili_jct']
  226. if user['isVIP']:
  227. self.session_vip.cookies = cookies
  228. else:
  229. self.userid = ''
  230. users[_type] = user
  231. with self.con:
  232. if len(user) > 1:
  233. self.userConfig.update({'users': users})
  234. if _type == 'master':
  235. self.getCookie_event.set()
  236. getFakeCookie_event = threading.Event()
  237. def getFakeCookie(self, fromSearch=None):
  238. if self.session_fake.cookies:
  239. self.getFakeCookie_event.set()
  240. header = {}
  241. header['User-Agent'] = self.header['User-Agent']
  242. rsp = self.fetch('https://space.bilibili.com/2/video', headers=header)
  243. self.session_fake.cookies = rsp.cookies
  244. self.getFakeCookie_event.set()
  245. with self.con:
  246. users = self.userConfig.get('users', {})
  247. users['fake'] = {'cookies_dic': dict(rsp.cookies)}
  248. self.userConfig.update({'users': users})
  249. if not fromSearch:
  250. self.getCookie_event.wait()
  251. if not self.session_master.cookies:
  252. self.session_master.cookies = rsp.cookies
  253. def get_fav_list_dict(self, fav):
  254. fav_dict = {
  255. 'n': fav['title'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;",'"').strip(),
  256. 'v': fav['id']}
  257. return fav_dict
  258. add_fav_filter_event = threading.Event()
  259. def add_fav_filter(self):
  260. users = self.userConfig.get('users', {})
  261. if users.get('master') and users['master'].get('userid'):
  262. userid = self.userConfig['users']['master']['userid']
  263. else:
  264. self.getCookie_event.wait()
  265. userid = self.userid
  266. fav_list = []
  267. if userid:
  268. url = 'https://api.bilibili.com/x/v3/fav/folder/created/list-all?up_mid=%s&jsonp=jsonp' % str(userid)
  269. rsp = self._get_sth(url)
  270. jo = json.loads(rsp.text)
  271. if jo['code'] == 0 and jo.get('data'):
  272. fav = jo['data'].get('list')
  273. fav_list = list(map(self.get_fav_list_dict, fav))
  274. fav_top = [{"n": "追番", "v": "1"},{"n": "追剧", "v": "2"}]
  275. fav_config = self.config["filter"].get('收藏')
  276. if fav_config:
  277. fav_config.insert(0, {
  278. "key": "mlid",
  279. "name": "分区",
  280. "value": fav_top + fav_list,
  281. })
  282. self.add_fav_filter_event.set()
  283. self.userConfig["fav_list"] = fav_list
  284. def get_channel_list_dict(self, channel):
  285. channel_dict = {
  286. 'n': channel['name'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;",'"').strip(),
  287. 'v': channel['id']}
  288. return channel_dict
  289. def get_channel_list(self):
  290. url = 'https://api.bilibili.com/x/web-interface/web/channel/category/channel/list?id=100&offset=0&page_size=15'
  291. rsp = self._get_sth(url, 'fake')
  292. jo = json.loads(rsp.text)
  293. channel_list = []
  294. if jo['code'] == 0:
  295. channel = jo['data'].get('channels')
  296. self.userConfig['channel_list'] = list(map(self.get_channel_list_dict, channel))
  297. return self.userConfig['channel_list']
  298. add_channel_filter_event = threading.Event()
  299. def add_channel_filter(self):
  300. channel_list = self.userConfig.get('channel_list', '')
  301. channel_list_task = self.pool.submit(self.get_channel_list)
  302. if not channel_list:
  303. channel_list = channel_list_task.result()
  304. channel_config = self.config["filter"].get('频道', [])
  305. if channel_config:
  306. channel_config.insert(0, {
  307. "key": "cid",
  308. "name": "分区",
  309. "value": channel_list,
  310. })
  311. self.config["filter"]['频道'] = channel_config
  312. self.add_channel_filter_event.set()
  313. add_focus_on_up_filter_event = threading.Event()
  314. def add_focus_on_up_filter(self):
  315. first_list = [{"n": "上个视频的UP主", "v": "上个视频的UP主"}]
  316. up_list = self.focus_on_up_list
  317. if not self.session_master.cookies:
  318. self.getCookie_event.wait()
  319. focus_on_up_list_mid = list(map(lambda x: x['v'], up_list))
  320. if self.session_master.cookies:
  321. url = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=-480&type=video&page=1'
  322. rsp = self._get_sth(url)
  323. jo = json.loads(rsp.text)
  324. if jo['code'] == 0 and jo.get('data'):
  325. up = jo['data'].get('items', [])
  326. for u in map(lambda x: {'n': x['modules']["module_author"]['name'], 'v': str(x['modules']["module_author"]['mid'])}, up):
  327. if not u in up_list and not u['v'] in focus_on_up_list_mid:
  328. up_list.append(u)
  329. last_list = [{"n": "登录与设置", "v": "登录"}]
  330. if not self.isFongmi:
  331. up_list = first_list + up_list
  332. up_list += last_list
  333. dynamic_config = self.config["filter"].get('动态', [])
  334. if dynamic_config:
  335. dynamic_config.insert(0, {
  336. "key": "mid",
  337. "name": "UP主",
  338. "value": up_list,
  339. })
  340. self.config["filter"]['动态'] = dynamic_config
  341. self.add_focus_on_up_filter_event.set()
  342. def get_live_parent_area_list(self, parent_area):
  343. name = parent_area['name']
  344. id = str(parent_area['id'])
  345. area = parent_area['list']
  346. area_dict = list(map(lambda area: {'n': area['name'], 'v': str(area['parent_id']) + '_' + str(area['id'])}, area))
  347. live_area = {'key': 'tid', 'name': name, 'value': area_dict}
  348. cateLive_name = {'id': id + '_0', 'value': live_area}
  349. return (name, cateLive_name)
  350. def get_live_list(self):
  351. url = 'https://api.live.bilibili.com/xlive/web-interface/v1/index/getWebAreaList?source_id=2'
  352. rsp = self._get_sth(url, 'fake')
  353. jo = json.loads(rsp.text)
  354. cateLive = {}
  355. if jo['code'] == 0:
  356. parent = jo['data']['data']
  357. self.userConfig['cateLive'] = dict(self.pool.map(self.get_live_parent_area_list, parent))
  358. return self.userConfig['cateLive']
  359. def set_default_cateManualLive(self):
  360. cateManualLive = [{'n': '推荐', 'v': '推荐'},]
  361. for name in self.userConfig['cateLive']:
  362. area_dict = {'n': name, 'v': self.userConfig['cateLive'][name]['id']}
  363. cateManualLive.append(area_dict)
  364. self.defaultConfig['cateManualLive'] = cateManualLive
  365. return cateManualLive
  366. add_live_filter_event = threading.Event()
  367. def add_live_filter(self):
  368. cateLive = self.userConfig.get('cateLive', {})
  369. cateLive_task = self.pool.submit(self.get_live_list)
  370. if not cateLive:
  371. cateLive = cateLive_task.result()
  372. default_cateManualLive_task = self.pool.submit(self.set_default_cateManualLive)
  373. self.config["filter"]['直播'] = []
  374. #分区栏
  375. cateManualLive = self.userConfig.get('cateManualLive', [])
  376. if not cateManualLive:
  377. cateManualLive = default_cateManualLive_task.result()
  378. if cateManualLive:
  379. live_area = {'key': 'tid', 'name': '分区', 'value': cateManualLive}
  380. self.config["filter"]['直播'].append(live_area)
  381. #显示分区细分
  382. if int(self.userConfig['showLiveFilterTag']):
  383. for name in cateLive.values():
  384. if len(name['value']['value']) == 1:
  385. continue
  386. self.config["filter"]['直播'].append(name['value'])
  387. self.add_live_filter_event.set()
  388. add_search_key_event = threading.Event()
  389. def add_search_key(self):
  390. focus_on_search_key = self.focus_on_search_key
  391. url = 'https://api.bilibili.com/x/web-interface/search/square?limit=10&platform=web'
  392. rsp = self._get_sth(url, 'fake')
  393. jo = json.loads(rsp.text)
  394. cateLive = {}
  395. if jo['code'] == 0:
  396. trending = jo['data']['trending'].get('list', [])
  397. focus_on_search_key += list(map(lambda x:x['keyword'], trending))
  398. keyword = {"key": "keyword", "name": "搜索词","value": []}
  399. keyword["value"] = list(map(lambda i: {'n': i, 'v': i}, focus_on_search_key))
  400. self.config["filter"]['搜索'].insert(0, keyword)
  401. self.add_search_key_event.set()
  402. def get_tuijian_filter(self):
  403. tuijian_filter = {"番剧时间表": "10001", "国创时间表": "10004", "排行榜": "0", "动画": "1", "音乐": "3", "舞蹈": "129", "游戏": "4", "鬼畜": "119", "知识": "36", "科技": "188", "运动": "234", "生活": "160", "美食": "211", "动物": "217", "汽车": "223", "时尚": "155", "娱乐": "5", "影视": "181", "原创": "origin", "新人": "rookie"}
  404. _dic = [{'n': 'tuijianLis', 'v': '分区'}, {'n': 'rankingLis', 'v': '排行榜'}]
  405. filter_lis = []
  406. for d in _dic:
  407. _filter = {"key": "tid" ,'name': d['v'],"value": []}
  408. t_lis = self.userConfig.get(d['n'], [])
  409. for t in t_lis:
  410. tf = tuijian_filter.get(t)
  411. if not tf:
  412. tf = t
  413. tf_dict = {'n': t, 'v': tf}
  414. _filter["value"].append(tf_dict)
  415. filter_lis.append(_filter)
  416. self.config["filter"]['推荐'] = filter_lis
  417. isFongmi = False
  418. def __init__(self):
  419. self.configdir = dirname
  420. if dirname.startswith('/data/'):
  421. self.isFongmi = True
  422. configdir = os.path.abspath(os.path.join(dirname, ".."))
  423. configdir = os.path.abspath(os.path.join(configdir, ".."))
  424. self.configdir = f"{configdir}/files"
  425. self.load_config()
  426. self.pool.submit(self.getCookie)
  427. self.pool.submit(self.getFakeCookie)
  428. self.pool.submit(self.getCookie, 'vip')
  429. wts = round(time.time())
  430. self.pool.submit(self.get_wbiKey, wts)
  431. def init(self, extend=""):
  432. print("============{0}============".format(extend))
  433. pass
  434. def isVideoFormat(self, url):
  435. pass
  436. def manualVideoCheck(self):
  437. pass
  438. # 降低内存占用
  439. def format_img(self, img):
  440. img += "@672w_378h_1c.webp"
  441. if not img.startswith('http'):
  442. img = 'https:' + img
  443. return img
  444. def pagination(self, array, pg):
  445. max_number = self.userConfig['page_size'] * int(pg)
  446. min_number = max_number - self.userConfig['page_size']
  447. return array[min_number:max_number]
  448. # 将超过10000的数字换成成以万和亿为单位
  449. def zh(self, num):
  450. if int(num) >= 100000000:
  451. p = round(float(num) / float(100000000), 1)
  452. p = str(p) + '亿'
  453. else:
  454. if int(num) >= 10000:
  455. p = round(float(num) / float(10000), 1)
  456. p = str(p) + '万'
  457. else:
  458. p = str(num)
  459. return p
  460. # 将秒数转化为 时分秒的格式
  461. def second_to_time(self, a):
  462. a = int(a)
  463. if a < 3600:
  464. result = time.strftime("%M:%S", time.gmtime(a))
  465. else:
  466. result = time.strftime("%H:%M:%S", time.gmtime(a))
  467. if str(result).startswith('0'):
  468. result = str(result).replace('0', '', 1)
  469. return result
  470. # 字符串时分秒以及分秒形式转换成秒
  471. def str2sec(self, x):
  472. x = str(x)
  473. try:
  474. h, m, s = x.strip().split(':') # .split()函数将其通过':'分隔开,.strip()函数用来除去空格
  475. return int(h) * 3600 + int(m) * 60 + int(s) # int()函数转换成整数运算
  476. except:
  477. m, s = x.strip().split(':') # .split()函数将其通过':'分隔开,.strip()函数用来除去空格
  478. return int(m) * 60 + int(s) # int()函数转换成整数运算
  479. # 按时间过滤
  480. def filter_duration(self, vodlist, key):
  481. if key == '0':
  482. return vodlist
  483. else:
  484. vod_list_new = [i for i in vodlist if
  485. self.time_diff1[key][0] <= self.str2sec(str(i["vod_remarks"])) < self.time_diff1[key][1]]
  486. return vod_list_new
  487. # 提取番剧id
  488. def find_bangumi_id(self, url):
  489. aid = str(url).strip().split(r"/")[-1]
  490. if not aid:
  491. aid = str(url).strip().split(r"/")[-2]
  492. aid = aid.split(r"?")[0]
  493. return aid
  494. # 登录二维码
  495. def get_Login_qrcode(self, pg):
  496. result = {}
  497. if int(pg) != 1:
  498. return result
  499. video = [{
  500. "vod_id": 'setting_tab&filter',
  501. "vod_name": '标签与筛选',
  502. "vod_pic": 'https://www.bilibili.com/favicon.ico'
  503. },{
  504. "vod_id": 'setting_liveExtra',
  505. "vod_name": '查看直播细化标签',
  506. "vod_pic": 'https://www.bilibili.com/favicon.ico'
  507. }]
  508. url = 'https://passport.bilibili.com/x/passport-login/web/qrcode/generate'
  509. rsp = self._get_sth(url, 'fake')
  510. jo = json.loads(rsp.text)
  511. if jo['code'] == 0:
  512. id = jo['data']['qrcode_key']
  513. url = jo['data']['url']
  514. account = {'master': '主账号', 'vip': '副账号'}
  515. isLogin = {0: '未登录', 1: '已登录'}
  516. isVIP = {0: '', 1: '👑'}
  517. users = self.userConfig.get('users', {})
  518. for _type, typeName in account.items():
  519. user = users.get(_type)
  520. if user:
  521. video.append({
  522. "vod_id": 'setting_login_' + id,
  523. "vod_name": user['uname'],
  524. "vod_pic": self.format_img(user['face']),
  525. "vod_remarks": isVIP[user['isVIP']] + typeName + ' ' + isLogin[user['isLogin']]
  526. })
  527. #pic_url = {'qrcode': url}
  528. pic_url = {'qrcode': 'https://passport.bilibili.com/h5-app/passport/login/scan?qrcode_key=' + id + '&navhide=1'}
  529. #if not dirname.startswith('/data/'):
  530. # pic_url['qr_chs'] = '208x117'
  531. pic_url['qr_chs'] = '208x117'
  532. video.append({
  533. "vod_id": 'setting_login_' + id,
  534. #"vod_name": '扫码后点击',
  535. 'vod_pic': 'http://jm92swf.s1002.xrea.com/?' + urlencode(pic_url),
  536. })
  537. result['list'] = video
  538. result['page'] = 1
  539. result['pagecount'] = 1
  540. result['limit'] = 1
  541. result['total'] = 1
  542. return result
  543. time_diff1 = {'1': [0, 300],
  544. '2': [300, 900], '3': [900, 1800], '4': [1800, 3600],
  545. '5': [3600, 99999999999999999999999999999999]
  546. }
  547. time_diff = '0'
  548. dynamic_offset = ''
  549. def get_dynamic(self, pg, mid, order):
  550. if mid == '0':
  551. result = {}
  552. if int(pg) == 1:
  553. self.dynamic_offset = ''
  554. url = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=-480&type=video&offset=%s&page=%s' % (self.dynamic_offset, pg)
  555. rsp = self._get_sth(url)
  556. jo = json.loads(rsp.text)
  557. if jo['code'] == 0:
  558. self.dynamic_offset = jo['data'].get('offset')
  559. videos = []
  560. vodList = jo['data']['items']
  561. for vod in vodList:
  562. if not vod['visible']:
  563. continue
  564. up = vod['modules']["module_author"]['name']
  565. ivod = vod['modules']['module_dynamic']['major']['archive']
  566. aid = str(ivod['aid']).strip()
  567. title = ivod['title'].strip().replace("<em class=\"keyword\">", "").replace("</em>", "")
  568. img = ivod['cover'].strip()
  569. # remark = str(ivod['duration_text']).strip()
  570. remark = str(self.second_to_time(self.str2sec(ivod['duration_text']))).strip() + ' 🆙' + str(
  571. up).strip() # 显示分钟数+up主名字
  572. videos.append({
  573. "vod_id": 'av' + aid,
  574. "vod_name": title,
  575. "vod_pic": self.format_img(img),
  576. "vod_remarks": remark
  577. })
  578. result['list'] = videos
  579. result['page'] = pg
  580. result['pagecount'] = 9999
  581. result['limit'] = 99
  582. result['total'] = 999999
  583. return result
  584. else:
  585. return self.get_up_videos(mid=mid, pg=pg, order=order)
  586. def get_found_vod(self, vod):
  587. aid = vod.get('aid', '')
  588. if not aid:
  589. aid = vod.get('id', '')
  590. goto = vod.get('goto', '')
  591. if not goto or goto and goto == 'av':
  592. aid = 'av' + str(aid).strip()
  593. elif goto == 'ad':
  594. return []
  595. title = vod['title'].strip()
  596. img = vod['pic'].strip()
  597. is_followed = vod.get('is_followed')
  598. if goto == 'live':
  599. room_info = vod['room_info']
  600. remark = ''
  601. live_status = room_info.get('live_status', '')
  602. if live_status:
  603. remark = '直播中 '
  604. else:
  605. return []
  606. remark += '👁' + room_info['watched_show']['text_small'] + ' 🆙' + vod['owner']['name'].strip()
  607. else:
  608. rcmd_reason = vod.get('rcmd_reason', '')
  609. if rcmd_reason and type(rcmd_reason) == dict and rcmd_reason.get('content'):
  610. reason= ' 🔥' + rcmd_reason['content'].strip()
  611. if '人气飙升' in reason:
  612. reason= ' 🔥人气飙升'
  613. elif is_followed:
  614. reason = ' 已关注'
  615. else:
  616. #reason = " 💬" + self.zh(vod['stat']['danmaku'])
  617. reason = ' 🆙' + vod['owner']['name'].strip()
  618. remark = str(self.second_to_time(vod['duration'])).strip() + " ▶" + self.zh(vod['stat']['view']) + reason
  619. video = [{
  620. "vod_id": aid,
  621. "vod_name": title,
  622. "vod_pic": self.format_img(img),
  623. "vod_remarks": remark
  624. }]
  625. for v in self.pool.map(self.get_found_vod, vod.get('others', [])):
  626. video.extend(v)
  627. return video
  628. _popSeriesInit = 0
  629. def get_found(self, tid, rid, pg):
  630. result = {}
  631. if tid == '推荐':
  632. query = self.encrypt_wbi(fresh_type=4, feed_version='V3', brush=1, fresh_idx=pg, fresh_idx_1h=pg, ps=self.userConfig['page_size'])
  633. url = f'https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd?{query}'
  634. else:
  635. url = 'https://api.bilibili.com/x/web-interface/ranking/v2?rid={0}&type={1}'.format(rid, tid)
  636. if tid == '热门':
  637. url = 'https://api.bilibili.com/x/web-interface/popular?pn={0}&ps={1}'.format(pg, self.userConfig['page_size'])
  638. elif tid == "入站必刷":
  639. url = 'https://api.bilibili.com/x/web-interface/popular/precious'
  640. elif tid == "每周必看":
  641. if not self._popSeriesInit or int(pg) == 1:
  642. url = 'https://api.bilibili.com/x/web-interface/popular/series/list'
  643. rsp = self._get_sth(url, 'fake')
  644. jo = json.loads(rsp.text)
  645. number = self._popSeriesInit = jo['data']['list'][0]['number']
  646. self._popSeriesNum = [int(number), 1]
  647. else:
  648. number = self._popSeriesNum[0]
  649. url = 'https://api.bilibili.com/x/web-interface/popular/series/one?number=' + str(number)
  650. rsp = self._get_sth(url)
  651. jo = json.loads(rsp.text)
  652. if jo['code'] == 0:
  653. videos = []
  654. vodList = jo['data'].get('item')
  655. if not vodList:
  656. vodList = jo['data']['list']
  657. if len(vodList) > self.userConfig['page_size']:
  658. if tid == "每周必看":
  659. _tmp_pg = int(self._popSeriesNum[1])
  660. value = len(vodList) / self.userConfig['page_size'] - _tmp_pg
  661. if value > 0:
  662. value += 1
  663. if not int(value):
  664. self._popSeriesNum = [int(number) - 1, 1]
  665. else:
  666. self._popSeriesNum[1] = _tmp_pg + 1
  667. else:
  668. _tmp_pg = pg
  669. vodList = self.pagination(vodList, _tmp_pg)
  670. for v in self.pool.map(self.get_found_vod, vodList):
  671. videos.extend(v)
  672. result['list'] = videos
  673. result['page'] = pg
  674. result['pagecount'] = 9999
  675. result['limit'] = 99
  676. result['total'] = 999999
  677. return result
  678. def get_bangumi(self, tid, pg, order, season_status):
  679. result = {}
  680. if order == '追番剧':
  681. url = 'https://api.bilibili.com/x/space/bangumi/follow/list?type={0}&vmid={1}&pn={2}&ps={3}'.format(tid, self.userid, pg, self.userConfig['page_size'])
  682. rsp = self._get_sth(url)
  683. else:
  684. url = 'https://api.bilibili.com/pgc/season/index/result?type=1&season_type={0}&page={1}&order={2}&season_status={3}&pagesize={4}'.format(tid, pg, order, season_status, self.userConfig['page_size'])
  685. if order == '热门':
  686. if tid == '1':
  687. url = 'https://api.bilibili.com/pgc/web/rank/list?season_type={0}&day=3'.format(tid)
  688. else:
  689. url = 'https://api.bilibili.com/pgc/season/rank/web/list?season_type={0}&day=3'.format(tid)
  690. rsp = self._get_sth(url, 'fake')
  691. jo = json.loads(rsp.text)
  692. if jo['code'] == 0:
  693. if 'data' in jo:
  694. vodList = jo['data']['list']
  695. else:
  696. vodList = jo['result']['list']
  697. if len(vodList) > self.userConfig['page_size']:
  698. vodList = self.pagination(vodList, pg)
  699. videos = []
  700. for vod in vodList:
  701. aid = str(vod['season_id']).strip()
  702. title = vod['title']
  703. img = vod.get('ss_horizontal_cover')
  704. if not img or tid == '1' and not self.userConfig['bangumi_horizontal_cover']:
  705. if vod.get('first_ep_info') and 'cover' in vod['first_ep_info']:
  706. img = vod['first_ep_info']['cover']
  707. elif vod.get('first_ep') and 'cover' in vod['first_ep']:
  708. img = vod['first_ep']['cover']
  709. else:
  710. img = vod['cover'].strip()
  711. remark = vod.get('index_show', '')
  712. if not remark and vod.get('new_ep') and vod['new_ep'].get('index_show'):
  713. remark = vod['new_ep']['index_show']
  714. remark = remark.replace('更新至', '🆕')
  715. stat = vod.get('stat')
  716. if stat:
  717. remark = '▶' + self.zh(stat.get('view')) + ' ' + remark
  718. videos.append({
  719. "vod_id": 'ss' + aid,
  720. "vod_name": title,
  721. "vod_pic": self.format_img(img),
  722. "vod_remarks": remark
  723. })
  724. result['list'] = videos
  725. result['page'] = pg
  726. result['pagecount'] = 9999
  727. result['limit'] = 90
  728. result['total'] = 999999
  729. return result
  730. def get_timeline(self, tid, pg):
  731. result = {}
  732. url = 'https://api.bilibili.com/pgc/web/timeline/v2?season_type={0}&day_before=2&day_after=4'.format(tid)
  733. rsp = self._get_sth(url, 'fake')
  734. content = rsp.text
  735. jo = json.loads(content)
  736. if jo['code'] == 0:
  737. videos1 = []
  738. vodList = jo['result']['latest']
  739. for vod in vodList:
  740. aid = str(vod['season_id']).strip()
  741. title = vod['title'].strip()
  742. img = vod['ep_cover'].strip()
  743. remark = '🆕' + vod['pub_index'] + ' ❤ ' + vod['follows'].replace('系列', '').replace('追番', '')
  744. videos1.append({
  745. "vod_id": 'ss' + aid,
  746. "vod_name": title,
  747. "vod_pic": self.format_img(img),
  748. "vod_remarks": remark
  749. })
  750. videos2 = []
  751. vodList2 = jo['result']['timeline']
  752. for i in range(len(vodList2)):
  753. vodList = vodList2[i]['episodes']
  754. for vod in vodList:
  755. if str(vod['published']) == "0":
  756. aid = str(vod['season_id']).strip()
  757. title = str(vod['title']).strip()
  758. img = str(vod['ep_cover']).strip()
  759. date = str(time.strftime("%m-%d %H:%M", time.localtime(vod['pub_ts'])))
  760. remark = date + " " + vod['pub_index']
  761. videos2.append({
  762. "vod_id": 'ss' + aid,
  763. "vod_name": title,
  764. "vod_pic": self.format_img(img),
  765. "vod_remarks": remark
  766. })
  767. result['list'] = videos2 + videos1
  768. result['page'] = 1
  769. result['pagecount'] = 1
  770. result['limit'] = 90
  771. result['total'] = 999999
  772. return result
  773. def get_live(self, pg, parent_area_id, area_id):
  774. result = {}
  775. if parent_area_id == '推荐':
  776. url = 'https://api.live.bilibili.com/xlive/web-interface/v1/webMain/getList?platform=web&page=%s' % pg
  777. rsp = self._get_sth(url)
  778. else:
  779. url = 'https://api.live.bilibili.com/xlive/web-interface/v1/second/getList?platform=web&parent_area_id=%s&area_id=%s&sort_type=online&page=%s' % (parent_area_id, area_id, pg)
  780. if parent_area_id == '热门':
  781. url = 'https://api.live.bilibili.com/room/v1/room/get_user_recommend?page=%s&page_size=%s' % (pg, self.userConfig['page_size'])
  782. rsp = self._get_sth(url, 'fake')
  783. jo = json.loads(rsp.text)
  784. if jo['code'] == 0:
  785. videos = []
  786. vodList = jo['data']
  787. if 'recommend_room_list' in vodList:
  788. vodList = vodList['recommend_room_list']
  789. elif 'list' in vodList:
  790. vodList = vodList['list']
  791. for vod in vodList:
  792. aid = str(vod['roomid']).strip()
  793. title = vod['title'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;", '"')
  794. img = vod.get('user_cover')
  795. if not img:
  796. img = vod.get('cover')
  797. remark = '👁' + vod['watched_show']['text_small'].strip() + " 🆙" + vod['uname'].strip()
  798. videos.append({
  799. "vod_id": aid,
  800. "vod_name": title,
  801. "vod_pic": self.format_img(img),
  802. "vod_remarks": remark
  803. })
  804. result['list'] = videos
  805. result['page'] = pg
  806. result['pagecount'] = 9999
  807. result['limit'] = 99
  808. result['total'] = 999999
  809. return result
  810. def get_up_series(self, mid, pg):
  811. result = {}
  812. url = 'https://api.bilibili.com/x/polymer/web-space/seasons_series_list?mid=%s&page_num=%s&page_size=%s' % (mid, pg, self.userConfig['page_size'])
  813. rsp = self._get_sth(url, 'fake')
  814. jo = json.loads(rsp.text)
  815. if jo['code'] == 0:
  816. videos = []
  817. jo = jo['data']['items_lists']
  818. vodList = jo['seasons_list'] + jo['series_list']
  819. for vod in vodList:
  820. vod = vod.get('meta')
  821. aid = str(vod.get('season_id', '')).strip()
  822. if aid:
  823. aid = 'list_' + str(mid) + '_season_' + aid
  824. else:
  825. aid = 'list_' + str(mid) + '_series_' + str(vod.get('series_id', '')).strip()
  826. title = vod['name'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;", '"')
  827. img = vod.get('cover')
  828. remark = vod.get('description', '').strip()
  829. videos.append({
  830. "vod_id": aid,
  831. "vod_name": title,
  832. "vod_pic": self.format_img(img),
  833. "vod_remarks": remark
  834. })
  835. result['list'] = videos
  836. result['page'] = pg
  837. result['pagecount'] = 9999
  838. result['limit'] = 99
  839. result['total'] = 999999
  840. return result
  841. get_up_videos_result = {}
  842. def get_up_videos(self, mid, pg, order):
  843. result = {}
  844. if not mid.isdigit():
  845. if int(pg) == 1:
  846. self.get_up_videos_mid = mid = self.detailContent_args.get('mid', '')
  847. if not mid in self.get_up_videos_result:
  848. self.get_up_videos_result.clear()
  849. self.get_up_videos_result[mid] = []
  850. else:
  851. mid = self.get_up_videos_mid
  852. if int(pg) == 1:
  853. self.get_up_videoNum_event.clear()
  854. self.get_up_info_event.clear()
  855. self.pool.submit(self.get_up_info, mid)
  856. Space = order2 = ''
  857. if order == 'oldest':
  858. order2 = order
  859. order = 'pubdate'
  860. elif order == 'quicksearch':
  861. Space = '投稿: '
  862. videos = self.get_up_videos_result.get(mid, [])
  863. if videos:
  864. result['list'] = videos
  865. return result
  866. elif order == 'series':
  867. return self.get_up_series(mid=mid, pg=pg)
  868. tmp_pg = pg
  869. if order2:
  870. self.get_up_videoNum_event.wait()
  871. tmp_pg = self.up_info[mid]['vod_pc'] - int(pg) + 1
  872. query = self.encrypt_wbi(mid=mid, pn=tmp_pg, ps=self.userConfig['page_size'], order=order)
  873. url = f'https://api.bilibili.com/x/space/wbi/arc/search?{query}'
  874. rsp = self._get_sth(url, 'fake')
  875. content = rsp.text
  876. jo = json.loads(content)
  877. videos = []
  878. if jo['code'] == 0:
  879. vodList = jo['data']['list']['vlist']
  880. for vod in vodList:
  881. aid = str(vod['aid']).strip()
  882. title = vod['title'].strip().replace("<em class=\"keyword\">", "").replace("</em>", "")
  883. img = vod['pic'].strip()
  884. remark = self.second_to_time(self.str2sec(str(vod['length']).strip())) + " ▶" + self.zh(vod['play'])
  885. if not Space:
  886. remark += " 💬" + self.zh(vod['video_review'])
  887. videos.append({
  888. "vod_id": 'av' + aid,
  889. "vod_name": Space + title,
  890. "vod_pic": self.format_img(img),
  891. "vod_remarks": remark
  892. })
  893. if order2:
  894. videos.reverse()
  895. if int(pg) == 1:
  896. self.get_up_info_event.wait()
  897. vodname = self.up_info[mid]['name'] + " 个人主页"
  898. if Space:
  899. vodname = 'UP: ' + self.up_info[mid]['name']
  900. gotoUPHome={
  901. "vod_id": 'up' + str(mid),
  902. "vod_name": vodname,
  903. "vod_pic": self.format_img(self.up_info[mid]['face']),
  904. "vod_remarks": self.up_info[mid]['following'] + ' 👥' + self.up_info[mid]['fans'] + ' 🎬' + str(self.up_info[mid]['vod_count'])
  905. }
  906. videos.insert(0, gotoUPHome)
  907. if Space:
  908. self.get_up_videos_result[mid] = videos
  909. result['list'] = videos
  910. result['page'] = pg
  911. result['pagecount'] = 99
  912. result['limit'] = 99
  913. result['total'] = 999999
  914. return result
  915. history_view_at = 0
  916. def get_history(self, type, pg):
  917. result = {}
  918. if int(pg) == 1:
  919. self.history_view_at = 0
  920. url = 'https://api.bilibili.com/x/web-interface/history/cursor?ps={0}&view_at={1}&type={2}'.format(self.userConfig['page_size'], self.history_view_at, type)
  921. if type == '稍后再看':
  922. url = 'https://api.bilibili.com/x/v2/history/toview'
  923. rsp = self._get_sth(url)
  924. jo = json.loads(rsp.text)
  925. if jo['code'] == 0:
  926. videos = []
  927. vodList = jo['data'].get('list', [])
  928. if type == '稍后再看':
  929. vodList = self.pagination(vodList, pg)
  930. else:
  931. self.history_view_at = jo['data']['cursor']['view_at']
  932. for vod in vodList:
  933. history = vod.get('history', '')
  934. if history:
  935. business = history['business']
  936. aid = str(history['oid']).strip()
  937. img = vod['cover'].strip()
  938. part = str(history['part']).strip()
  939. else:
  940. business = 'archive'
  941. aid = str(vod["aid"]).strip()
  942. img = vod['pic'].strip()
  943. part = str(vod['page']['part']).strip()
  944. if business == 'article':
  945. continue
  946. elif business == 'pgc':
  947. aid = 'ep' + str(history['epid'])
  948. _total = vod['total']
  949. part = vod.get('show_title')
  950. elif business == 'archive':
  951. aid = 'av' + aid
  952. _total = vod['videos']
  953. title = vod['title'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;", '"')
  954. if business == 'live':
  955. live_status = vod.get('badge', '')
  956. remark = live_status + ' 🆙' + vod['author_name'].strip()
  957. else:
  958. if str(vod['progress']) == '-1':
  959. remark = '已看完'
  960. elif str(vod['progress']) == '0':
  961. remark = '刚开始看'
  962. else:
  963. process = str(self.second_to_time(vod['progress'])).strip()
  964. remark = '看到 ' + process
  965. if not _total in [0, 1] and part:
  966. remark += ' (' + str(part) + ')'
  967. videos.append({
  968. "vod_id": aid,
  969. "vod_name": title,
  970. "vod_pic": self.format_img(img),
  971. "vod_remarks": remark
  972. })
  973. result['list'] = videos
  974. result['page'] = pg
  975. result['pagecount'] = 9999
  976. result['limit'] = 90
  977. result['total'] = 999999
  978. return result
  979. def get_fav_detail(self, pg, mlid, order):
  980. result = {}
  981. url = 'https://api.bilibili.com/x/v3/fav/resource/list?media_id=%s&order=%s&pn=%s&ps=10&platform=web&type=0' % (mlid, order, pg)
  982. rsp = self._get_sth(url)
  983. content = rsp.text
  984. jo = json.loads(content)
  985. if jo['code'] == 0:
  986. videos = []
  987. vodList = jo['data'].get('medias', [])
  988. for vod in vodList:
  989. # 只展示类型为 视频的条目
  990. # 过滤去掉收藏中的 已失效视频;如果不喜欢可以去掉这个 if条件
  991. if vod.get('type') in [2] and vod.get('title') != '已失效视频':
  992. aid = str(vod['id']).strip()
  993. title = vod['title'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;",
  994. '"')
  995. img = vod['cover'].strip()
  996. remark = str(self.second_to_time(vod['duration'])).strip() + " ▶" + self.zh(vod['cnt_info']['play']) + " 💬" + self.zh(vod['cnt_info']['danmaku'])
  997. videos.append({
  998. "vod_id": 'av' + aid + '_mlid' + str(mlid),
  999. "vod_name": title,
  1000. "vod_pic": self.format_img(img),
  1001. "vod_remarks": remark
  1002. })
  1003. # videos=self.filter_duration(videos, duration_diff)
  1004. result['list'] = videos
  1005. result['page'] = pg
  1006. result['pagecount'] = 9999
  1007. result['limit'] = 99
  1008. result['total'] = 999999
  1009. return result
  1010. get_up_videoNum_event = threading.Event()
  1011. def get_up_videoNum(self, mid):
  1012. url = "https://api.bilibili.com/x/space/navnum?mid={0}".format(mid)
  1013. rsp = self._get_sth(url)
  1014. jRoot = json.loads(rsp.text)
  1015. if jRoot['code'] == 0:
  1016. return jRoot['data']['video']
  1017. else:
  1018. return 0
  1019. get_up_info_event = threading.Event()
  1020. up_info = {}
  1021. def get_up_info(self, mid, **kwargs):
  1022. if mid in self.up_info:
  1023. self.get_up_info_event.set()
  1024. get_up_videoNum = self.pool.submit(self.get_up_videoNum, mid)
  1025. data = kwargs.get('data')
  1026. if not data:
  1027. url = "https://api.bilibili.com/x/web-interface/card?mid={0}".format(mid)
  1028. rsp = self._get_sth(url)
  1029. jRoot = json.loads(rsp.text)
  1030. if jRoot['code'] == 0:
  1031. data = jRoot['data']
  1032. else:
  1033. self.get_up_info_event.set()
  1034. return
  1035. jo = data['card']
  1036. info = {}
  1037. info['following'] = '未关注'
  1038. if data['following']:
  1039. info['following'] = '已关注'
  1040. info['name'] = info['crname'] = jo['name'].replace("<em class=\"keyword\">", "").replace("</em>", "")
  1041. if self.isFongmi:
  1042. info['crname'] = '[a=cr:{"id": "' + mid + '_pubdate_getupvideos","name": "' + info['name'].replace('"', '\\"') + '"}/]' + info['name'] + '[/a]'
  1043. info['face'] = jo['face']
  1044. info['fans'] = self.zh(jo['fans'])
  1045. info['like_num'] = self.zh(data['like_num'])
  1046. info['desc'] = jo['Official']['desc'] + " " + jo['Official']['title']
  1047. info['vod_count'] = str(data['archive_count']).strip()
  1048. self.up_info[mid] = info
  1049. self.get_up_info_event.set()
  1050. self.up_info[mid]['vod_count'] = str(get_up_videoNum.result()).strip()
  1051. pc = divmod(int(info['vod_count']), self.userConfig['page_size'])
  1052. vod_pc = pc[0]
  1053. if pc[1] != 0:
  1054. vod_pc += 1
  1055. self.up_info[mid]['vod_pc'] = vod_pc
  1056. self.get_up_videoNum_event.set()
  1057. def get_vod_relation(self, id):
  1058. if id.isdigit():
  1059. urlarg = 'aid=' + str(id)
  1060. elif '=' in id:
  1061. urlarg = id
  1062. else:
  1063. urlarg = 'bvid=' + id
  1064. url = 'https://api.bilibili.com/x/web-interface/archive/relation?' + urlarg
  1065. rsp = self._get_sth(url)
  1066. jo = json.loads(rsp.text)
  1067. relation = []
  1068. if jo['code'] == 0:
  1069. jo = jo['data']
  1070. if jo['attention']:
  1071. relation.append('已关注')
  1072. else:
  1073. relation.append('未关注')
  1074. triple = []
  1075. if jo['favorite']:
  1076. triple.append('⭐')
  1077. if jo['like']:
  1078. triple.append('👍')
  1079. coin = jo.get('coin')
  1080. if coin:
  1081. triple.append('💰'*coin)
  1082. if len(triple) == 3:
  1083. relation.append('👍💰⭐')
  1084. else:
  1085. relation.extend(triple)
  1086. if jo['dislike']:
  1087. relation.append('👎')
  1088. if jo['season_fav']:
  1089. relation.append('已订阅合集')
  1090. return relation
  1091. def get_channel(self, pg, cid, order):
  1092. result = {}
  1093. if str(pg) == '1':
  1094. self.channel_offset = ''
  1095. if order == "featured":
  1096. url = 'https://api.bilibili.com/x/web-interface/web/channel/featured/list?channel_id={0}&filter_type=0&offset={1}&page_size={2}'.format(cid, self.channel_offset, self.userConfig['page_size'])
  1097. else:
  1098. url = 'https://api.bilibili.com/x/web-interface/web/channel/multiple/list?channel_id={0}&sort_type={1}&offset={2}&page_size={3}'.format(cid, order, self.channel_offset, self.userConfig['page_size'])
  1099. rsp = self._get_sth(url, 'master')
  1100. jo = json.loads(rsp.text)
  1101. if jo.get('code') == 0:
  1102. self.channel_offset = jo['data'].get('offset')
  1103. videos = []
  1104. vodList = jo['data']['list']
  1105. if pg == '1' and 'items' in vodList[0]:
  1106. vodList_rank = vodList[0]['items']
  1107. del (vodList[0])
  1108. vodList = vodList_rank + vodList
  1109. for vod in vodList:
  1110. if 'uri' in vod and 'bangumi' in vod['uri']:
  1111. aid = self.find_bangumi_id(vod['uri'])
  1112. else:
  1113. aid = 'av' + str(vod['id']).strip()
  1114. title = vod['name'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;", '"')
  1115. img = vod['cover'].strip()
  1116. remark = "▶" + str(vod['view_count'])
  1117. duration = vod.get('duration', '')
  1118. if duration:
  1119. remark = str(self.second_to_time(self.str2sec(duration))).strip() + ' ' + remark
  1120. danmaku = vod.get('danmaku', '')
  1121. like_count = vod.get('like_count', '')
  1122. follow_count = vod.get('follow_count', '')
  1123. if danmaku:
  1124. remark += " 💬" + self.zh(danmaku)
  1125. elif like_count:
  1126. remark += " 👍" + str(like_count)
  1127. elif follow_count:
  1128. remark += " ❤" + str(follow_count)
  1129. videos.append({
  1130. "vod_id": aid,
  1131. "vod_name": title,
  1132. "vod_pic": self.format_img(img),
  1133. "vod_remarks": remark
  1134. })
  1135. result['list'] = videos
  1136. result['page'] = pg
  1137. result['pagecount'] = 9999
  1138. result['limit'] = 99
  1139. result['total'] = 999999
  1140. return result
  1141. def get_follow(self, pg, sort):
  1142. result = {}
  1143. if sort == "最常访问":
  1144. url = 'https://api.bilibili.com/x/relation/followings?vmid={0}&pn={1}&ps=10&order=desc&order_type=attention' .format(self.userid, pg)
  1145. elif sort == "最近关注":
  1146. url = 'https://api.bilibili.com/x/relation/followings?vmid={0}&pn={1}&ps=10&order=desc&order_type='.format(self.userid, pg)
  1147. elif sort == "正在直播":
  1148. url = 'https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/GetWebList?page={0}&page_size=10'.format(pg)
  1149. elif sort == "最近访问":
  1150. url = 'https://api.bilibili.com/x/v2/history?pn={0}&ps=15'.format(pg)
  1151. elif sort == "特别关注":
  1152. url = 'https://api.bilibili.com/x/relation/tag?mid={0}&tagid=-10&pn={1}&ps=10'.format(self.userid, pg)
  1153. elif sort == "悄悄关注":
  1154. url = 'https://api.bilibili.com/x/relation/whispers?pn={0}&ps=10'.format(pg)
  1155. else:
  1156. url = 'https://api.bilibili.com/x/relation/followers?vmid={0}&pn={1}&ps=10&order=desc&order_type=attention'.format(self.userid, pg)
  1157. rsp = self._get_sth(url)
  1158. jo = json.loads(rsp.text)
  1159. if jo['code'] != 0:
  1160. return result
  1161. if sort == "特别关注" or sort == "最近访问":
  1162. vodList = jo['data']
  1163. elif sort == "正在直播":
  1164. vodList = jo['data']['rooms']
  1165. else:
  1166. vodList = jo['data']['list']
  1167. if int(pg) == 1:
  1168. self.recently_up_list = []
  1169. follow = []
  1170. for f in vodList:
  1171. remark = ''
  1172. if sort == "最近访问":
  1173. mid = 'up' + str(f['owner']['mid'])
  1174. if mid in self.recently_up_list:
  1175. continue
  1176. self.recently_up_list.append(mid)
  1177. title = str(f['owner']['name']).strip()
  1178. img = str(f['owner']['face']).strip()
  1179. elif sort == "正在直播":
  1180. mid = str(f['room_id'])
  1181. title = f['title'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;", '"')
  1182. img = f['cover_from_user'].strip()
  1183. remark = f['uname'].strip()
  1184. else:
  1185. mid = 'up' + str(f['mid'])
  1186. title = str(f['uname']).strip()
  1187. img = str(f['face']).strip()
  1188. if 'special' in f and f['special'] == 1:
  1189. remark = '特别关注'
  1190. follow.append({
  1191. "vod_id": mid,
  1192. "vod_name": title,
  1193. "vod_pic": self.format_img(img),
  1194. "vod_remarks": remark
  1195. })
  1196. result['list'] = follow
  1197. result['page'] = pg
  1198. result['pagecount'] = 9999
  1199. result['limit'] = 99
  1200. result['total'] = 999999
  1201. return result
  1202. homeVideoContent_result = {}
  1203. def homeVideoContent(self):
  1204. if not self.homeVideoContent_result:
  1205. videos = self.get_found(rid='0', tid='all', pg=1)['list'][0:int(self.userConfig['maxHomeVideoContent'])]
  1206. self.homeVideoContent_result['list'] = videos
  1207. return self.homeVideoContent_result
  1208. def categoryContent(self, tid, pg, filter, extend):
  1209. self.stop_heartbeat_event.set()
  1210. if tid == "推荐":
  1211. if 'tid' in extend:
  1212. tid = extend['tid']
  1213. if tid.isdigit():
  1214. tid = int(tid)
  1215. if tid > 10000:
  1216. tid -= 10000
  1217. return self.get_timeline(tid=tid, pg=pg)
  1218. rid = tid
  1219. tid = 'all'
  1220. return self.get_found(tid=tid, rid=rid, pg=pg)
  1221. rid = '0'
  1222. return self.get_found(tid=tid, rid=rid, pg=pg)
  1223. elif tid == "影视":
  1224. tid = '1'
  1225. order = '热门'
  1226. season_status = '-1'
  1227. if 'tid' in extend:
  1228. tid = extend['tid']
  1229. if 'order' in extend:
  1230. order = extend['order']
  1231. if 'season_status' in extend:
  1232. if order == '热门':
  1233. order = '2'
  1234. season_status = extend['season_status']
  1235. return self.get_bangumi(tid, pg, order, season_status)
  1236. elif tid == "动态":
  1237. mid = '0'
  1238. order = 'pubdate'
  1239. if 'mid' in extend:
  1240. mid = extend['mid']
  1241. if 'order' in extend:
  1242. order = extend['order']
  1243. if mid == '0' and not self.userid or mid == '登录':
  1244. return self.get_Login_qrcode(pg)
  1245. return self.get_dynamic(pg=pg, mid=mid, order=order)
  1246. elif tid == '频道':
  1247. order = 'hot'
  1248. cid = random.choice(self.userConfig['channel_list'])
  1249. cid = cid['v']
  1250. if 'order' in extend:
  1251. order = extend['order']
  1252. if 'cid' in extend:
  1253. cid = extend['cid']
  1254. return self.get_channel(pg=pg, cid=cid, order=order)
  1255. elif tid == '直播':
  1256. tid = "热门"
  1257. area_id = '0'
  1258. if 'tid' in extend:
  1259. tid = extend['tid']
  1260. if '_' in tid:
  1261. tids = tid.split('_')
  1262. tid = tids[0]
  1263. area_id = tids[1]
  1264. return self.get_live(pg=pg, parent_area_id=tid, area_id=area_id)
  1265. elif tid == "UP":
  1266. mid = self.detailContent_args.get('mid', '')
  1267. if 'mid' in extend:
  1268. mid = extend['mid']
  1269. if not mid or mid == '登录':
  1270. return self.get_Login_qrcode(pg)
  1271. up_config = self.config["filter"].get('UP')
  1272. if not mid and up_config:
  1273. for i in up_config:
  1274. if i['key'] == 'mid':
  1275. if len(i['value']) > 1:
  1276. mid = i['value'][1]['v']
  1277. break
  1278. order = 'pubdate'
  1279. if 'order' in extend:
  1280. order = extend['order']
  1281. return self.get_up_videos(mid=mid, pg=pg, order=order)
  1282. elif tid == "关注":
  1283. sort = "最常访问"
  1284. if 'sort' in extend:
  1285. sort = extend['sort']
  1286. return self.get_follow(pg, sort)
  1287. elif tid == "收藏":
  1288. mlid = str(self.userConfig['favMode'])
  1289. if 'mlid' in extend:
  1290. mlid = extend['mlid']
  1291. fav_config = self.config["filter"].get('收藏')
  1292. if mlid in ['1', '2']:
  1293. return self.get_bangumi(tid=mlid, pg=pg, order='追番剧', season_status='')
  1294. elif mlid == '0' and fav_config:
  1295. for i in fav_config:
  1296. if i['key'] == 'mlid':
  1297. if len(i['value']) > 1:
  1298. mlid = i['value'][2]['v']
  1299. break
  1300. order = 'mtime'
  1301. if 'order' in extend:
  1302. order = extend['order']
  1303. return self.get_fav_detail(pg=pg, mlid=mlid, order=order)
  1304. elif tid == '历史':
  1305. type = 'all'
  1306. if 'type' in extend:
  1307. type = extend['type']
  1308. if type == 'UP主':
  1309. return self.get_follow(pg=pg, sort='最近访问')
  1310. return self.get_history(type=type, pg=pg)
  1311. elif tid.endswith('_getbangumiseasons'):
  1312. if int(pg) == 1:
  1313. return {'list': self.detailContent_args.get('seasons', [])}
  1314. elif tid.endswith('_getupvideos'):
  1315. mid, order, clicklink = tid.split('_')
  1316. return self.get_up_videos(pg=pg, mid=mid, order=order)
  1317. elif tid.endswith('_clicklink'):
  1318. keyword = tid.replace('_clicklink', '')
  1319. duration_diff = '0'
  1320. if 'duration' in extend:
  1321. duration_diff = extend['duration']
  1322. return self.get_search_content(key=keyword, pg=pg, duration_diff=duration_diff, order='', type='video', ps=self.userConfig['page_size'])
  1323. else:
  1324. duration_diff = '0'
  1325. if 'duration' in extend:
  1326. duration_diff = extend['duration']
  1327. type = 'video'
  1328. if 'type' in extend:
  1329. type = extend['type']
  1330. order = 'totalrank'
  1331. if 'order' in extend:
  1332. order = extend['order']
  1333. keyword = str(self.search_key)
  1334. search_config = self.config["filter"].get('搜索')
  1335. if not keyword and search_config:
  1336. for i in search_config:
  1337. if i['key'] == 'keyword':
  1338. if len(i['value']) > 0:
  1339. keyword = i['value'][0]['v']
  1340. break
  1341. if 'keyword' in extend:
  1342. keyword = extend['keyword']
  1343. return self.get_search_content(key=keyword, pg=pg, duration_diff=duration_diff, order=order, type=type, ps=self.userConfig['page_size'])
  1344. def get_search_content(self, key, pg, duration_diff, order, type, ps):
  1345. value = None
  1346. if not pg.isdigit():
  1347. value = pg
  1348. pg = 1
  1349. query = self.encrypt_wbi(keyword=key, page=pg, duration=duration_diff, order=order, search_type=type, page_size=ps)
  1350. url = f'https://api.bilibili.com/x/web-interface/wbi/search/type?{query}'
  1351. rsp = self._get_sth(url, 'fake')
  1352. content = rsp.text
  1353. jo = json.loads(content)
  1354. result = {}
  1355. if jo.get('code') == 0 and 'result' in jo['data']:
  1356. videos = []
  1357. vodList = jo['data'].get('result')
  1358. if vodList and type == 'live':
  1359. vodList = vodList.get('live_room')
  1360. if not vodList:
  1361. return result
  1362. for vod in vodList:
  1363. if type != vod['type']:
  1364. continue
  1365. title = ''
  1366. if type == 'bili_user':
  1367. aid = 'up' + str(vod['mid']).strip()
  1368. img = vod['upic'].strip()
  1369. remark = '👥' + self.zh(vod['fans']) + " 🎬" + self.zh(vod['videos'])
  1370. title = vod['uname']
  1371. elif type == 'live':
  1372. aid = str(vod['roomid']).strip()
  1373. img = vod['cover'].strip()
  1374. remark = '👁' + self.zh(vod['online']) + ' 🆙' + vod['uname']
  1375. elif 'media' in type:
  1376. aid = str(vod['season_id']).strip()
  1377. if self.detailContent_args:
  1378. seasons = self.detailContent_args.get('seasons')
  1379. if seasons:
  1380. bangumi_seasons_id = []
  1381. for ss in self.detailContent_args['seasons']:
  1382. bangumi_seasons_id.append(ss['vod_id'])
  1383. if aid + 'ss' in bangumi_seasons_id:
  1384. continue
  1385. aid = 'ss' + aid
  1386. img = vod['cover'].strip()
  1387. remark = str(vod['index_show']).strip().replace('更新至', '🆕')
  1388. else:
  1389. aid = 'av' + str(vod['aid']).strip()
  1390. img = vod['pic'].strip()
  1391. remark = str(self.second_to_time(self.str2sec(vod['duration']))).strip() + " ▶" + self.zh(vod['play'])
  1392. if value == None:
  1393. remark += " 💬" + self.zh(vod['danmaku'])
  1394. if not title:
  1395. title = vod['title'].replace("<em class=\"keyword\">", "").replace("</em>", "").replace("&quot;",'"').replace('&amp;', '&')
  1396. if value:
  1397. title = value + title
  1398. videos.append({
  1399. "vod_id": aid,
  1400. "vod_name": title,
  1401. "vod_pic": self.format_img(img),
  1402. "vod_remarks": remark
  1403. })
  1404. result['list'] = videos
  1405. result['page'] = pg
  1406. result['pagecount'] = 9999
  1407. result['limit'] = 99
  1408. result['total'] = 999999
  1409. return result
  1410. def cleanSpace(self, str):
  1411. return str.replace('\n', '').replace('\t', '').replace('\r', '').replace(' ', '')
  1412. def get_normal_episodes(self, episode):
  1413. ssid = epid = ''
  1414. aid = episode.get('aid', '')
  1415. if not aid:
  1416. aid = self.detailContent_args['aid']
  1417. cid = episode.get('cid', '')
  1418. ep_title = episode.get('title', '')
  1419. if not ep_title:
  1420. ep_title = episode.get('part', '')
  1421. duration = episode.get('duration', '')
  1422. if not duration:
  1423. page = episode.get('page', '')
  1424. if page:
  1425. duration = page['duration']
  1426. badge = long_title = preview = parse = ''
  1427. ssid = self.detailContent_args.get('ssid', '')
  1428. if ssid:
  1429. ssid = '_ss' + ssid
  1430. epid = episode.get('id', '')
  1431. if epid:
  1432. epid = '_ep' + str(epid)
  1433. if duration and str(duration).endswith('000'):
  1434. duration = int(duration / 1000)
  1435. if ep_title.isdigit():
  1436. ep_title = '第' + ep_title + self.detailContent_args['title_type']
  1437. badge = episode.get('badge', '')
  1438. if not self.session_vip.cookies and badge == '会员' and self.userConfig['bangumi_vip_parse'] or badge == '付费' and self.userConfig['bangumi_pay_parse']:
  1439. parse = '_parse'
  1440. if self.session_vip.cookies and self.userConfig['hide_bangumi_vip_badge']:
  1441. badge = badge.replace('会员', '')
  1442. if self.userConfig['hide_bangumi_preview'] and badge == '预告':
  1443. badge = badge.replace('预告', '')
  1444. preview = 1
  1445. if badge:
  1446. badge = '【' + badge + '】'
  1447. long_title = episode.get('long_title', '')
  1448. if not badge and long_title:
  1449. long_title = ' ' + long_title
  1450. title = ep_title + badge + long_title
  1451. title = title.replace("#", "﹟").replace("$", "﹩")
  1452. if duration:
  1453. duration = '_dur' + str(duration)
  1454. url = '{0}${1}_{2}{3}{4}{5}'.format(title, aid, cid, ssid, epid, duration)
  1455. fromep = self.detailContent_args.get('fromep', '')
  1456. if '_' + str(fromep) == epid:
  1457. self.detailContent_args['fromep'] = url
  1458. replyList = self.detailContent_args.get('Reply')
  1459. if '_' + str(fromep) == epid or not fromep and replyList == None:
  1460. self.detailContent_args['Reply'] = ''
  1461. if self.userConfig['show_vod_hot_reply']:
  1462. self.get_vod_hot_reply_event.clear()
  1463. self.pool.submit(self.get_vod_hot_reply, aid)
  1464. if ssid:
  1465. if preview:
  1466. return url, ''
  1467. if parse:
  1468. self.detailContent_args['parse'] = 1
  1469. if long_title:
  1470. long_title = '【解析】' + long_title
  1471. ep_title += long_title
  1472. parseurl = '{0}${1}_{2}{3}{4}{5}{6}'.format(ep_title, aid, cid, ssid, epid, duration, parse)
  1473. if '_' + str(fromep) == epid:
  1474. self.detailContent_args['fromep'] += '#' + parseurl
  1475. else:
  1476. parseurl = url
  1477. return url, parseurl
  1478. else:
  1479. return url
  1480. def get_ugc_season(self, section, sections_len):
  1481. if sections_len > 1:
  1482. sec_title = self.detailContent_args['season_title'] + ' ' + section['title']
  1483. else:
  1484. sec_title = self.detailContent_args['season_title']
  1485. sec_title = sec_title.replace("#", "﹟").replace("$", "﹩")
  1486. episodes = section.get('episodes')
  1487. playUrl = '#'.join(map(self.get_normal_episodes, episodes))
  1488. result = (sec_title, playUrl)
  1489. return result
  1490. get_vod_hot_reply_event = threading.Event()
  1491. def get_vod_hot_reply(self, oid):
  1492. query = self.encrypt_wbi(type=1, ps=30, oid=str(oid))
  1493. url = f'http://api.bilibili.com/x/v2/reply/main?{query}'
  1494. rsp = self._get_sth(url, 'fake')
  1495. jRoot = json.loads(rsp.text)
  1496. if jRoot['code'] == 0:
  1497. replies = jRoot['data'].get('replies')
  1498. top_replies = jRoot['data'].get('top_replies')
  1499. if replies and top_replies:
  1500. replies = top_replies + replies
  1501. if replies:
  1502. up_mid = jRoot['data']['upper']['mid']
  1503. ReplyList = []
  1504. Reply_jump = []
  1505. for r in replies:
  1506. rpid = r['rpid']
  1507. sex = r['member']['sex']
  1508. if sex and sex == '女':
  1509. sex = '👧'
  1510. else:
  1511. sex = '👦'
  1512. name = sex + r['member']['uname'] + ':'
  1513. mid = r['mid']
  1514. if mid == up_mid:
  1515. name = '🆙' + name
  1516. like = '👍' + self.zh(r['like'])
  1517. message = r['content']['message']
  1518. if '/note-app/' in message:
  1519. continue
  1520. content = like + ' ' + name + message
  1521. content = content.replace("#", "﹟").replace("$", "﹩")
  1522. content += '$' + str(oid) + '_' + str(rpid) + '_notplay_reply'
  1523. ReplyList.append(content)
  1524. jump_url = r['content'].get('jump_url',{})
  1525. for key, value in jump_url.items():
  1526. if not value.get('app_url_schema') and not value.get('pc_url'):
  1527. if key.startswith('https://www.bilibili.com/'):
  1528. key = str(key).split('?')[0].split('/')
  1529. while key[-1] == '':
  1530. key.pop(-1)
  1531. key = key[-1]
  1532. if key.startswith('https://b23.tv/') or key.startswith('BV') or key.startswith('ep') or key.startswith('ss'):
  1533. title = str(value['title']).replace("#", "﹟").replace("$", "﹩")
  1534. vod = {'vod_id': str(key), 'vod_name': '评论:' + title}
  1535. if not vod in Reply_jump:
  1536. Reply_jump.append(vod)
  1537. title = '快搜:' + str(key) +' ' + title
  1538. content = title + '$ '
  1539. ReplyList.append(content)
  1540. self.detailContent_args['Reply'] = '#'.join(ReplyList)
  1541. self.detailContent_args['Reply_jump'] = Reply_jump
  1542. self.get_vod_hot_reply_event.set()
  1543. detailContent_args = {}
  1544. def detailContent(self, array):
  1545. self.stop_heartbeat_event.set()
  1546. aid = array[0]
  1547. if aid.startswith('edgeid'):
  1548. return self.interaction_detailContent(aid)
  1549. elif aid.startswith('list'):
  1550. return self.series_detailContent(aid)
  1551. self.detailContent_args = {}
  1552. if aid.startswith('https://b23.tv/'):
  1553. try:
  1554. r = requests_get(url=aid, headers=self.header, allow_redirects=False)
  1555. url = r.headers['Location'].split('?')[0].split('/')
  1556. while url[-1] == '':
  1557. url.pop(-1)
  1558. aid = url[-1]
  1559. if not aid.startswith('BV', 0, 2):
  1560. return {}
  1561. except:
  1562. return {}
  1563. id = mlid = urlargs = ''
  1564. self.get_vod_hot_reply_event.set()
  1565. if aid.startswith('setting'):
  1566. aid = aid.split('_')
  1567. if aid[1] == 'tab&filter':
  1568. return self.setting_tab_filter_detailContent()
  1569. elif aid[1] == 'liveExtra':
  1570. return self.setting_liveExtra_detailContent()
  1571. elif aid[1] == 'login':
  1572. key = aid[2]
  1573. return self.setting_login_detailContent(key)
  1574. elif aid.startswith('av') or aid.startswith('BV'):
  1575. for i in aid.split('_'):
  1576. if i.startswith('av'):
  1577. id = i.replace('av', '', 1)
  1578. urlargs = 'aid=' + str(id)
  1579. elif i.startswith('BV'):
  1580. id = i
  1581. urlargs = 'bvid=' + id
  1582. elif i.startswith('mlid'):
  1583. mlid = i.replace('mlid', '', 1)
  1584. #获取热门评论
  1585. if self.userConfig['show_vod_hot_reply']:
  1586. self.detailContent_args['Reply'] = ''
  1587. self.get_vod_hot_reply_event.clear()
  1588. self.pool.submit(self.get_vod_hot_reply, id)
  1589. elif 'up' in aid:
  1590. return self.up_detailContent(array)
  1591. elif 'ss' in aid or 'ep' in aid:
  1592. return self.ysContent(array)
  1593. elif aid.isdigit():
  1594. return self.live_detailContent(array)
  1595. relation = self.pool.submit(self.get_vod_relation, urlargs)
  1596. url = 'https://api.bilibili.com/x/web-interface/view/detail?' + urlargs
  1597. rsp = self._get_sth(url, 'fake')
  1598. jRoot = json.loads(rsp.text)
  1599. if jRoot['code'] != 0:
  1600. return {}
  1601. jo = jRoot['data']['View']
  1602. redirect_url = jo.get('redirect_url', '')
  1603. if 'bangumi' in redirect_url:
  1604. ep_id = self.find_bangumi_id(redirect_url)
  1605. new_array = []
  1606. for i in array:
  1607. new_array.append(i)
  1608. new_array[0] = ep_id
  1609. return self.ysContent(new_array)
  1610. self.detailContent_args['mid'] = up_mid = str(jo['owner']['mid'])
  1611. self.detailContent_args['aid'] = aid = jo.get('aid')
  1612. self.pool.submit(self.get_up_info, mid=up_mid, data=jRoot['data'].get('Card'))
  1613. #相关合集
  1614. ugc_season = jo.get('ugc_season')
  1615. if ugc_season:
  1616. self.detailContent_args['season_title'] = ugc_season['title']
  1617. sections = ugc_season['sections']
  1618. sections_len = len(sections)
  1619. ugc_season_task = []
  1620. for section in sections:
  1621. t = self.pool.submit(self.get_ugc_season, section, sections_len)
  1622. ugc_season_task.append(t)
  1623. #相关推荐
  1624. jo_Related = jRoot['data'].get('Related')
  1625. #正片
  1626. pages = jo['pages']
  1627. title = jo['title'].replace("<em class=\"keyword\">", "").replace("</em>", "")
  1628. pic = jo['pic']
  1629. desc = jo['desc'].strip()
  1630. typeName = jo['tname']
  1631. date = time.strftime("%Y%m%d", time.localtime(jo['pubdate'])) # 投稿时间本地年月日表示
  1632. stat = jo['stat']
  1633. # 演员项展示视频状态,包括以下内容:
  1634. remark = []
  1635. remark.append('▶' + self.zh(stat['view']))
  1636. remark.append('💬' + self.zh(stat['danmaku']))
  1637. remark.append('👍' + self.zh(stat['like']))
  1638. remark.append('💰' + self.zh(stat['coin']))
  1639. remark.append('⭐' + self.zh(stat['favorite']))
  1640. _is_stein_gate = jo['rights'].get('is_stein_gate', 0)
  1641. vod = {
  1642. "vod_id": 'av' + str(aid),
  1643. "vod_name": title,
  1644. "vod_pic": pic,
  1645. "type_name": typeName,
  1646. "vod_year": date,
  1647. "vod_content": desc
  1648. }
  1649. if self.isFongmi:
  1650. vod['vod_remarks'] = " ".join(remark)
  1651. vod_tags = ', '.join(sorted(map(lambda x: '[a=cr:{"id": "' + x['tag_name'].replace('"', '\\"') + '_clicklink","name":"' + x['tag_name'].replace('"', '\\"') + '"}/]' + x['tag_name'] + '[/a]', jRoot['data'].get('Tags', [])), key=len))
  1652. vod['vod_content'] = vod_tags + ' \n' + desc
  1653. self.userConfig['show_vod_hot_reply'] = False
  1654. else:
  1655. vod['vod_actor'] = " ".join(remark)
  1656. vod['vod_area'] = "bilidanmu"
  1657. secondP = []
  1658. if self.userid:
  1659. #做点什么
  1660. follow = '➕关注$1_notplay_follow'
  1661. unfollow = '➖取关$2_notplay_follow'
  1662. like = '👍点赞$1_notplay_like'
  1663. unlike = '👍🏻取消点赞$2_notplay_like'
  1664. coin1 = '👍💰投币$1_notplay_coin'
  1665. coin2 = '👍💰💰$2_notplay_coin'
  1666. triple = '👍💰⭐三连$notplay_triple'
  1667. secondPList = [follow, triple, like, coin1, coin2, unfollow, unlike]
  1668. if mlid:
  1669. favdel = f"☆取消收藏${mlid}_del_notplay_fav"
  1670. secondPList.append(favdel)
  1671. for fav in self.userConfig.get("fav_list", []):
  1672. folder = fav['n'].replace("#", "﹟").replace("$", "﹩")
  1673. ids = fav['v']
  1674. fav = '⭐{}${}_add_notplay_fav'.format(folder, ids)
  1675. secondPList.append(fav)
  1676. defaultQn = int(self.userConfig['vodDefaultQn'])
  1677. if defaultQn > 116:
  1678. secondPList.append('⚠️限高1080$116_notplay_vodTMPQn')
  1679. secondP = ['#'.join(secondPList)]
  1680. AllPt = []
  1681. AllPu = []
  1682. if pages:
  1683. AllPt = ['B站']
  1684. if _is_stein_gate:
  1685. AllPt = ['互动视频【快搜继续】']
  1686. AllPu = ['#'.join(self.pool.map(self.get_normal_episodes, pages))]
  1687. if secondP:
  1688. AllPt.append('做点什么')
  1689. AllPu.extend(secondP)
  1690. if jo_Related:
  1691. AllPt.append('相关推荐')
  1692. AllPu.append('#'.join(self.pool.map(self.get_normal_episodes, jo_Related)))
  1693. if self.userConfig['show_vod_hot_reply']:
  1694. self.get_vod_hot_reply_event.wait()
  1695. replyList = self.detailContent_args.get('Reply', '')
  1696. if replyList:
  1697. AllPt.append('热门评论')
  1698. AllPu.extend([replyList])
  1699. if ugc_season:
  1700. for t in as_completed(ugc_season_task):
  1701. AllPt.append(t.result()[0])
  1702. AllPu.append(t.result()[1])
  1703. vod['vod_play_from'] = "$$$".join(AllPt)
  1704. vod['vod_play_url'] = "$$$".join(AllPu)
  1705. #视频关系
  1706. vod['vod_director'] = '🆙 ' + self.up_info[up_mid]['crname'] + ' 👥 ' + self.up_info[up_mid]['fans'] + ' ' + ' '.join(relation.result())
  1707. #互动视频套用
  1708. if _is_stein_gate:
  1709. self.detailContent_args['AllPt'] = AllPt.copy()
  1710. self.detailContent_args['AllPu'] = AllPu.copy()
  1711. self.detailContent_args['vod_list'] = vod.copy()
  1712. result = {
  1713. 'list': [
  1714. vod
  1715. ]
  1716. }
  1717. return result
  1718. def series_detailContent(self, array):
  1719. mid, type, sid = array.replace('list_', '').split('_')
  1720. pg = 1
  1721. ps = 99
  1722. vod = {"vod_id": array, "vod_area": "bilidanmu", "vod_play_from": "B站"}
  1723. urlL = []
  1724. while True:
  1725. if type == 'season':
  1726. url = 'https://api.bilibili.com/x/polymer/web-space/seasons_archives_list?mid=%s&season_id=%s&page_num=%s&page_size=%s' % (mid, sid, pg, ps)
  1727. else:
  1728. url = 'https://api.bilibili.com/x/series/archives?mid=%s&series_id=%s&pn=%s&ps=%s' % (mid, sid, pg, ps)
  1729. rsp = self._get_sth(url, 'fake')
  1730. jo = json.loads(rsp.text)
  1731. data = jo.get('data')
  1732. if not vod.get("vod_name"):
  1733. if data.get('meta'):
  1734. vod["vod_name"] = data['meta']['name']
  1735. vod["vod_pic"] = data['meta']['cover']
  1736. vod["vod_content"] = data['meta']['description']
  1737. else:
  1738. vod["vod_name"] = data['archives'][0]['title']
  1739. playUrl = '#'.join(map(self.get_normal_episodes, data.get('archives')))
  1740. urlL.append(playUrl)
  1741. total = data['page']['total']
  1742. if (ps * pg) >= total:
  1743. break
  1744. pg += 1
  1745. vod['vod_play_url'] = '#'.join(urlL)
  1746. result = {
  1747. 'list': [
  1748. vod
  1749. ]
  1750. }
  1751. return result
  1752. def interaction_detailContent(self, array=''):
  1753. array = array.split('_')
  1754. cid = edgeid = 0
  1755. for i in array:
  1756. if i.startswith('edgeid'):
  1757. edgeid = i.replace('edgeid', '')
  1758. elif i.startswith('cid'):
  1759. cid = i.replace('cid', '')
  1760. aid = self.detailContent_args.get('aid')
  1761. graph_version = self.detailContent_args.get('graph_version')
  1762. url = 'https://api.bilibili.com/x/stein/edgeinfo_v2?aid={0}&graph_version={1}&edge_id={2}'.format(aid, graph_version, edgeid)
  1763. rsp = self._get_sth(url, 'fake')
  1764. jo = json.loads(rsp.text)
  1765. data = jo.get('data')
  1766. result = {}
  1767. if data:
  1768. questions = data['edges'].get('questions', [])
  1769. choice_lis = []
  1770. for question in questions:
  1771. q_title = str(question.get('title', ''))
  1772. if q_title:
  1773. q_title += ' '
  1774. for choice in question.get('choices', []):
  1775. c_edgeid = str(choice['id'])
  1776. c_cid = str(choice['cid'])
  1777. option = str(choice.get('option', ''))
  1778. choice_lis.append({
  1779. "vod_id": 'edgeid' + c_edgeid + '_' + 'cid' + c_cid,
  1780. "vod_name": '互动:' + q_title + option,
  1781. })
  1782. self.detailContent_args['interaction'] = choice_lis.copy()
  1783. if edgeid:
  1784. AllPt = self.detailContent_args['AllPt'].copy()
  1785. if not choice_lis:
  1786. AllPt[0] = '互动视频'
  1787. AllPu = self.detailContent_args['AllPu'].copy()
  1788. title = str(data['title']).replace("#", "﹟").replace("$", "﹩")
  1789. url = '{0}${1}_{2}'.format(title, aid, cid)
  1790. AllPu[0] = url
  1791. vod = self.detailContent_args['vod_list'].copy()
  1792. vod['vod_play_from'] = "$$$".join(AllPt)
  1793. vod['vod_play_url'] = "$$$".join(AllPu)
  1794. result['list'] = [vod]
  1795. return result
  1796. def up_detailContent(self, array):
  1797. self.detailContent_args['mid'] = mid = array[0].replace('up', '')
  1798. self.get_up_info_event.clear()
  1799. self.pool.submit(self.get_up_info, mid)
  1800. first = '是否关注$ '
  1801. follow = '关注$1_notplay_follow'
  1802. unfollow = '取消关注$2_notplay_follow'
  1803. qqfollow = '悄悄关注$3_notplay_follow'
  1804. spfollow = '特别关注$-10_notplay_special_follow'
  1805. unspfollow = '取消特别关注$0_notplay_special_follow'
  1806. doWhat = [first, follow, qqfollow, spfollow, unfollow, unspfollow]
  1807. doWhat = '#'.join(doWhat)
  1808. self.get_up_info_event.wait()
  1809. up_info = self.up_info[mid]
  1810. vod = {
  1811. "vod_id": 'up' + str(mid),
  1812. "vod_name": up_info['name'] + " 个人主页",
  1813. "vod_pic": up_info['face'],
  1814. "vod_director": '🆙 ' + up_info['name'] + " " + up_info['following'] + ' UID:' + str(mid),
  1815. "vod_content": up_info['desc'],
  1816. 'vod_play_from': '关注TA$$$视频投稿在动态标签——筛选——上个UP,选择后查看',
  1817. 'vod_play_url': doWhat
  1818. }
  1819. remark = "👥 " + up_info['fans'] + " 🎬 " + up_info['vod_count'] + " 👍 " + up_info['like_num']
  1820. if self.isFongmi:
  1821. vod["vod_remarks"] = remark
  1822. tabfilter = self.config['filter'].get('动态')
  1823. if not tabfilter:
  1824. tabfilter = self.config['filter'].get('UP', [])
  1825. vod["vod_actor"] = ' '.join(map(lambda x: '[a=cr:{"id": "' + str(mid) + '_' + x['v'] +'_getupvideos","name": "' + up_info['name'].replace('"', '\\"') + ' ' + x['n'] + '"}/]' + x['n'] + '[/a]', tabfilter[-1]['value']))
  1826. else:
  1827. vod["vod_actor"] = remark
  1828. result = {
  1829. 'list': [
  1830. vod
  1831. ]
  1832. }
  1833. return result
  1834. def setting_login_detailContent(self, key):
  1835. cookie_dic_tmp = self.cookie_dic_tmp.get(key, '')
  1836. message = ''
  1837. if not cookie_dic_tmp:
  1838. message = self.get_cookies(key)
  1839. if message:
  1840. message = f"【{message}】通过手机客户端扫码确认登录后点击相应按钮设置账号"
  1841. else:
  1842. message = '【已扫码并确认登录】请点击相应按钮设置当前获取的账号为:'
  1843. vod = {
  1844. "vod_name": "登录与设置",
  1845. "vod_content": '通过手机客户端扫码并确认登录后,点击相应按钮设置cookie,设置后不需要管嗅探结果,直接返回二维码页面刷新,查看是否显示已登录,已登录即可重新打开APP以加载全部标签',
  1846. }
  1847. vod_play_from = ['登录$$$退出登录']
  1848. vod_play_url = []
  1849. first = message + '$ '
  1850. login = '设置为主账号,动态收藏关注等内容源于此$' + str(key) + '_master_login_setting'
  1851. login_vip = '设置为备用的VIP账号,仅用于播放会员番剧$' + str(key) + '_vip_login_setting'
  1852. vod_play_url.append('#'.join([first, login, login_vip]))
  1853. second = '点击相应按钮退出账号>>>$ '
  1854. logout = '退出主账号$master_logout_setting'
  1855. logout_vip = '退出备用的VIP账号$vip_logout_setting'
  1856. vod_play_url.append('#'.join([second, logout, logout_vip]))
  1857. cate_lis = [{
  1858. 'f': '主页站点推荐栏',
  1859. 'c': 'maxHomeVideoContent',
  1860. 'd': {
  1861. '3': '3图',
  1862. '4': '4图',
  1863. '6': '6图',
  1864. '8': '8图',
  1865. '10': '10图',
  1866. }
  1867. },{
  1868. 'f': '视频画质',
  1869. 'c': 'vodDefaultQn',
  1870. 'd': self.vod_qn_id
  1871. },{
  1872. 'f': '视频编码',
  1873. 'c': 'vodDefaultCodec',
  1874. 'd': self.vod_codec_id
  1875. },{
  1876. 'f': '音频码率',
  1877. 'c': 'vodDefaultAudio',
  1878. 'd': self.vod_audio_id
  1879. },{
  1880. 'f': '收藏默认显示',
  1881. 'c': 'favMode',
  1882. 'd': {
  1883. '0': '默认收藏夹',
  1884. '1': '追番',
  1885. '2': '追剧',
  1886. }
  1887. },{
  1888. 'f': '上传播放进度',
  1889. 'c': 'heartbeatInterval',
  1890. 'd': {
  1891. '0': '关',
  1892. '15': '开',
  1893. }
  1894. },{
  1895. 'f': '直播筛选细化',
  1896. 'c': 'showLiveFilterTag',
  1897. 'd': {
  1898. '0': '关',
  1899. '1': '开',
  1900. }
  1901. }]
  1902. for cate in cate_lis:
  1903. vod_play_from.append(cate['f'])
  1904. defaultConfig = cate['d'][str(int(self.userConfig[cate['c']]))]
  1905. if 'vodDefaultAudio' == cate['c']:
  1906. defaultConfig = str(defaultConfig).replace('000', 'k')
  1907. url = ['当前:' + defaultConfig + '$ ']
  1908. for id, name in cate['d'].items():
  1909. if 'vodDefaultAudio' == cate['c']:
  1910. name = str(name).replace('000', 'k')
  1911. url.append(name + '$' + str(id) + '_' + cate['c'] + '_setting')
  1912. vod_play_url.append('#'.join(url))
  1913. vod['vod_play_from'] = '$$$'.join(vod_play_from)
  1914. vod['vod_play_url'] = '$$$'.join(vod_play_url)
  1915. result = {
  1916. 'list': [
  1917. vod
  1918. ]
  1919. }
  1920. return result
  1921. def setting_tab_filter_detailContent(self):
  1922. vod = {
  1923. "vod_name": "标签与筛选",
  1924. "vod_content": '依次点击各标签,同一标签第一次点击为添加,第二次删除,可以返回到二维码页后重进本页查看预览,最后点击保存,未选择的将追加到末尾,如果未保存就重启app,将丢失未保存的配置',
  1925. }
  1926. vod_play_from = []
  1927. vod_play_url = []
  1928. cate_lis = [
  1929. {'n': 'cateManual', 'v': '标签'},
  1930. {'n': 'tuijianLis', 'v': '推荐[分区]'},
  1931. {'n': 'rankingLis', 'v': '推荐[排行榜]'},
  1932. {'n': 'cateManualLive', 'v': '直播'},
  1933. ]
  1934. for cate in cate_lis:
  1935. _List = cate['n']
  1936. vod_play_from.append(cate['v'])
  1937. List_tmp = self.userConfig.get(str(_List) + '_tmp', [])
  1938. status = ''
  1939. if List_tmp:
  1940. status = '【未保存】'
  1941. else:
  1942. List_tmp = self.userConfig.get(_List, [])
  1943. if not List_tmp:
  1944. List_tmp = self.defaultConfig.get(_List)
  1945. if List_tmp and type(List_tmp[0]) == dict:
  1946. List_tmp = list(map(lambda x:x['n'], List_tmp))
  1947. url = ['当前: ' + ','.join(List_tmp) + '$ ', f"{status}点击这里保存$_{_List}_save_setting", f"点击这里恢复默认并保存$_{_List}_clear_setting"]
  1948. defaultConfig = self.defaultConfig[_List].copy()
  1949. if _List == 'cateManual' and not 'UP' in defaultConfig:
  1950. defaultConfig.append('UP')
  1951. elif _List == 'cateManualLive':
  1952. extra_live_filter = self.userConfig.get('cateManualLiveExtra', [])
  1953. defaultConfig.extend(extra_live_filter.copy())
  1954. for name in defaultConfig:
  1955. value = str(name)
  1956. if type(name) == dict:
  1957. value = name['n'] + '@@@' + name['v'].replace('_', '@@@')
  1958. name = name['n']
  1959. url.append(f"{name}${value}_{_List}_setting")
  1960. vod_play_url.append('#'.join(url))
  1961. vod['vod_play_from'] = '$$$'.join(vod_play_from)
  1962. vod['vod_play_url'] = '$$$'.join(vod_play_url)
  1963. result = {
  1964. 'list': [
  1965. vod
  1966. ]
  1967. }
  1968. return result
  1969. def setting_liveExtra_detailContent(self):
  1970. vod = {
  1971. "vod_name": "查看直播细化标签",
  1972. "vod_content": '点击想要添加的标签,同一标签第一次点击为添加,第二次删除,完成后在[标签与筛选]页继续操作,以添加到直播筛选分区列中',
  1973. }
  1974. vod_play_from = ['已添加']
  1975. cateManualLiveExtra = self.userConfig.get('cateManualLiveExtra', [])
  1976. vod_play_url = ['点击相应标签(只)可以删除$ #清空$clear_liveFilter_setting']
  1977. for name in cateManualLiveExtra:
  1978. value = name['v']
  1979. name = name['n']
  1980. vod_play_url.append(name + '$' + 'del_' + name + '_' + value + '_liveFilter_setting')
  1981. vod_play_url = ['#'.join(vod_play_url)]
  1982. cateLive = self.userConfig.get('cateLive', {})
  1983. for parent, parent_dic in cateLive.items():
  1984. area_dic = parent_dic['value']['value']
  1985. if len(area_dic) == 1:
  1986. continue
  1987. vod_play_from.append(parent)
  1988. url = []
  1989. for area in area_dic:
  1990. name = str(area['n']).replace('_', '-').replace("#", "﹟").replace("$", "﹩")
  1991. id = str(area['v']).replace('_', '@@@').replace("#", "﹟").replace("$", "﹩")
  1992. url.append(name + '$add_' + name + '_' + id + '_liveFilter_setting')
  1993. vod_play_url.append('#'.join(url))
  1994. vod['vod_play_from'] = '$$$'.join(vod_play_from)
  1995. vod['vod_play_url'] = '$$$'.join(vod_play_url)
  1996. result = {
  1997. 'list': [
  1998. vod
  1999. ]
  2000. }
  2001. return result
  2002. def get_all_season(self, season):
  2003. season_id = str(season['season_id'])
  2004. season_title = season['season_title']
  2005. if season_id == self.detailContent_args['ssid']:
  2006. self.detailContent_args['s_title'] = season_title
  2007. pic = season['cover']
  2008. remark = season['new_ep']['index_show']
  2009. result = {
  2010. "vod_id": season_id + 'ss',
  2011. "vod_name": '系列:' + season_title,
  2012. "vod_pic": self.format_img(pic),
  2013. "vod_remarks": remark}
  2014. return result
  2015. def get_bangumi_section(self, section):
  2016. sec_title = section['title'].replace("#", "﹟").replace("$", "﹩")
  2017. sec_type = section['type']
  2018. if sec_type in [1, 2] and len(section['episode_ids']) == 0:
  2019. episodes = section['episodes']
  2020. playUrl = '#'.join(map(lambda x: self.get_normal_episodes(x)[0], episodes))
  2021. return (sec_title, playUrl)
  2022. def ysContent(self, array):
  2023. aid = array[0]
  2024. if 'ep' in aid:
  2025. self.detailContent_args['fromep'] = aid
  2026. aid = 'ep_id=' + aid.replace('ep', '')
  2027. elif 'ss' in aid:
  2028. aid = 'season_id=' + aid.replace('ss', '')
  2029. url = "https://api.bilibili.com/pgc/view/web/season?{0}".format(aid)
  2030. rsp = self._get_sth(url, 'fake')
  2031. jRoot = json.loads(rsp.text)
  2032. jo = jRoot['result']
  2033. self.detailContent_args['ssid'] = str(jo['season_id'])
  2034. title = jo['title']
  2035. self.detailContent_args['s_title'] = jo['season_title']
  2036. self.detailContent_args['title_type'] = '集'
  2037. if jo['type'] in [1, 4]:
  2038. self.detailContent_args['title_type'] = '话'
  2039. #添加系列到搜索
  2040. seasons = jo.get('seasons')
  2041. if len(seasons) == 1:
  2042. self.detailContent_args['s_title'] = seasons[0]['season_title']
  2043. seasons = 0
  2044. else:
  2045. self.detailContent_args['seasons'] = list(self.pool.map(self.get_all_season, seasons))
  2046. #获取正片
  2047. episodes = jo.get('episodes')
  2048. #获取花絮
  2049. section_task = []
  2050. for s in jo.get('section', []):
  2051. if s:
  2052. t = self.pool.submit(self.get_bangumi_section, s)
  2053. section_task.append(t)
  2054. pic = jo['cover']
  2055. typeName = jo['share_sub_title']
  2056. date = jo['publish']['pub_time'][0:4]
  2057. dec = jo['evaluate']
  2058. remark = jo['new_ep']['desc']
  2059. stat = jo['stat']
  2060. # 演员和导演框展示视频状态,包括以下内容:
  2061. status = "▶" + self.zh(stat['views']) + " ❤" + self.zh(stat['favorites'])
  2062. vod = {
  2063. "vod_id": 'ss' + self.detailContent_args['ssid'],
  2064. "vod_name": title,
  2065. "vod_pic": pic,
  2066. "type_name": typeName,
  2067. "vod_year": date,
  2068. "vod_actor": status,
  2069. "vod_content": dec
  2070. }
  2071. if self.isFongmi:
  2072. if seasons:
  2073. remark += ' [a=cr:{"id": "_getbangumiseasons","name": "' + title.replace('"', '\\"') + '"}/]更多系列[/a]'
  2074. seasons = 0
  2075. if 'rating' in jo:
  2076. remark = str(jo['rating']['score']) + '分 ' + remark
  2077. self.userConfig['show_vod_hot_reply'] = False
  2078. else:
  2079. vod["vod_area"] = "bilidanmu"
  2080. vod["vod_remarks"] = remark
  2081. ZhuiPf = []
  2082. ZhuiPu = []
  2083. if self.userid:
  2084. ZhuiPf = ['做点什么']
  2085. ZhuiPu = '是否追番剧$ #❤追番剧$add_notplay_zhui#💔取消追番剧$del_notplay_zhui'
  2086. defaultQn = int(self.userConfig['vodDefaultQn'])
  2087. if defaultQn > 116:
  2088. ZhuiPu += '#⚠️限高1080$116_notplay_vodTMPQn'
  2089. ZhuiPu = [ZhuiPu]
  2090. if seasons:
  2091. ZhuiPf.append('更多系列')
  2092. ZhuiPu.append('更多系列在快速搜索中查看$ #')
  2093. FirstPf = []
  2094. FirstPu = []
  2095. PreviewPf = []
  2096. PreviewPu = []
  2097. ParsePf = []
  2098. ParsePu = []
  2099. if episodes:
  2100. for x, y in self.pool.map(self.get_normal_episodes, episodes):
  2101. if y:
  2102. FirstPu.append(x)
  2103. ParsePu.append(y)
  2104. else:
  2105. PreviewPu.append(x)
  2106. if FirstPu:
  2107. FirstPf = [self.detailContent_args['s_title']]
  2108. FirstPu = ['#'.join(FirstPu)]
  2109. if PreviewPu:
  2110. PreviewPf = ['预告']
  2111. PreviewPu = ['#'.join(PreviewPu)]
  2112. if not self.detailContent_args.get('parse'):
  2113. ParsePu = []
  2114. if ParsePu:
  2115. ParsePf = [str(self.detailContent_args['s_title']) + '【解析】']
  2116. ParsePu = ['#'.join(ParsePu)]
  2117. fromL = ParsePf + FirstPf + PreviewPf
  2118. urlL = ParsePu + FirstPu + PreviewPu
  2119. for t in as_completed(section_task):
  2120. s = t.result()
  2121. if s:
  2122. fromL.append(s[0])
  2123. urlL.append(s[1])
  2124. fromep = self.detailContent_args.get('fromep', '')
  2125. if '_' in fromep:
  2126. fromL = ['B站'] + fromL
  2127. urlL = [fromep] + urlL
  2128. if self.userConfig['show_vod_hot_reply']:
  2129. self.get_vod_hot_reply_event.wait()
  2130. ReplyPu = self.detailContent_args.get('Reply', '')
  2131. if ReplyPu:
  2132. ZhuiPf.append('热门评论')
  2133. ZhuiPu.append(ReplyPu)
  2134. fromL.insert(1, '$$$'.join(ZhuiPf))
  2135. urlL.insert(1, '$$$'.join(ZhuiPu))
  2136. vod['vod_play_from'] = '$$$'.join(fromL)
  2137. vod['vod_play_url'] = '$$$'.join(urlL)
  2138. result = {
  2139. 'list': [
  2140. vod
  2141. ]
  2142. }
  2143. return result
  2144. def get_live_api2_playurl(self, room_id):
  2145. playFrom = []
  2146. playUrl = []
  2147. url = 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?room_id={0}&no_playurl=0&mask=1&qn=0&platform=web&protocol=0,1&format=0,1,2&codec=0,1&dolby=5&panorama=1'.format(room_id)
  2148. rsp = self._get_sth(url, 'fake')
  2149. jo = json.loads(rsp.text)
  2150. if jo['code'] == 0:
  2151. playurl_info = jo['data'].get('playurl_info', '')
  2152. if playurl_info:
  2153. stream = playurl_info['playurl']['stream']
  2154. liveDic = {
  2155. 'codec': {'avc': '0', 'hevc': '1'},
  2156. 'format': {'flv': '0', 'ts': '1', 'fmp4': '2'},
  2157. }
  2158. liveDic['qn'] = dict(self.pool.map(lambda x:(x['qn'], x['desc']), playurl_info['playurl']['g_qn_desc']))
  2159. vodList = []
  2160. for i in stream:
  2161. vodList.extend(i['format'])
  2162. api2_playUrl = {}
  2163. for v in vodList:
  2164. format = str(v.get('format_name'))
  2165. for c in v['codec']:
  2166. codec = str(c.get('codec_name'))
  2167. accept_qn = c.get('accept_qn')
  2168. for qn in accept_qn:
  2169. url = format + '_' + codec + '$liveapi2_' + str(qn) + '_' + liveDic['format'][format] + '_' + liveDic['codec'][codec] + '_' + str(room_id)
  2170. if not api2_playUrl.get(liveDic['qn'][qn]):
  2171. api2_playUrl[liveDic['qn'][qn]] = []
  2172. api2_playUrl[liveDic['qn'][qn]].append(url)
  2173. for key, value in api2_playUrl.items():
  2174. playFrom.append(key)
  2175. playUrl.append('#'.join(value))
  2176. result = {'From': playFrom, 'url': playUrl}
  2177. return result
  2178. def live_detailContent(self, array):
  2179. room_id = array[0]
  2180. get_live_api2_playurl = self.pool.submit(self.get_live_api2_playurl, room_id)
  2181. url = "https://api.live.bilibili.com/room/v1/Room/get_info?room_id=" + str(room_id)
  2182. rsp = self._get_sth(url, 'fake')
  2183. jRoot = json.loads(rsp.text)
  2184. result = {}
  2185. if jRoot.get('code') == 0:
  2186. jo = jRoot['data']
  2187. self.detailContent_args['mid'] = mid = str(jo["uid"])
  2188. self.get_up_info_event.clear()
  2189. self.pool.submit(self.get_up_info, mid)
  2190. title = jo['title'].replace("<em class=\"keyword\">", "").replace("</em>", "")
  2191. pic = jo.get("user_cover")
  2192. desc = jo.get('description')
  2193. typeName = jo.get('parent_area_name') + '--' + jo.get('area_name')
  2194. live_status = jo.get('live_status', '')
  2195. if live_status:
  2196. live_status = "开播时间:" + jo.get('live_time').replace('-', '.')
  2197. else:
  2198. live_status = "未开播"
  2199. vod = {
  2200. "vod_id": room_id,
  2201. "vod_name": title,
  2202. "vod_pic": pic,
  2203. "type_name": typeName,
  2204. "vod_content": desc,
  2205. }
  2206. remark = "房间号:" + room_id + " " + live_status
  2207. if self.isFongmi:
  2208. vod["vod_remarks"] = remark
  2209. else:
  2210. vod["vod_actor"] = remark
  2211. vod["vod_area"] = "bililivedanmu"
  2212. secondPFrom = ''
  2213. secondP = ''
  2214. if self.userid:
  2215. secondPFrom = '关注Ta'
  2216. first = '是否关注$ '
  2217. follow = '➕关注$1_notplay_follow'
  2218. unfollow = '➖取关$2_notplay_follow'
  2219. secondPList = [first, follow, unfollow]
  2220. secondP = '#'.join(secondPList)
  2221. playFrom = get_live_api2_playurl.result().get('From', [])
  2222. playUrl = get_live_api2_playurl.result().get('url', [])
  2223. if playFrom:
  2224. api1_playFrom = 'API_1'
  2225. api1_playUrl = 'flv线路原画$platform=web&quality=4_' + room_id + '#flv线路高清$platform=web&quality=3_' + room_id + '#h5线路原画$platform=h5&quality=4_' + room_id + '#h5线路高清$platform=h5&quality=3_' + room_id
  2226. playFrom.append(api1_playFrom)
  2227. playUrl.append(api1_playUrl)
  2228. if secondPFrom:
  2229. playFrom.insert(1, secondPFrom)
  2230. playUrl.insert(1, secondP)
  2231. vod['vod_play_from'] = '$$$'.join(playFrom)
  2232. vod['vod_play_url'] = '$$$'.join(playUrl)
  2233. self.get_up_info_event.wait()
  2234. up_info = self.up_info[mid]
  2235. vod["vod_director"] = '🆙 ' + up_info['crname'] + " 👥 " + self.zh(jo.get('attention')) + ' ' + up_info['following']
  2236. result['list'] = [vod]
  2237. return result
  2238. search_key = ''
  2239. def searchContent(self, key, quick):
  2240. if not self.session_fake.cookies:
  2241. self.pool.submit(self.getFakeCookie, True)
  2242. for t in self.task_pool:
  2243. t.cancel()
  2244. self.task_pool = []
  2245. self.search_key = key
  2246. mid = self.detailContent_args.get('mid', '')
  2247. if quick and mid:
  2248. get_up_videos = self.pool.submit(self.get_up_videos, mid, 1, 'quicksearch')
  2249. types = {'video': '','media_bangumi': '番剧: ', 'media_ft': '影视: ', 'bili_user': '用户: ', 'live': '直播: '}
  2250. for type, value in types.items():
  2251. t = self.pool.submit(self.get_search_content, key = key, pg = value, duration_diff = 0, order = '', type = type, ps = self.userConfig['page_size'])
  2252. self.task_pool.append(t)
  2253. result = {'list': []}
  2254. for t in as_completed(self.task_pool):
  2255. res = t.result().get('list', [])
  2256. result['list'].extend(res)
  2257. self.task_pool.remove(t)
  2258. if quick:
  2259. if not self.isFongmi:
  2260. if mid:
  2261. result['list'] = self.detailContent_args.get('interaction', []) + get_up_videos.result().get('list', []) + self.detailContent_args.get('Reply_jump', []) + result['list']
  2262. else:
  2263. result['list'] = self.detailContent_args.get('seasons', []) + result['list']
  2264. return result
  2265. stop_heartbeat_event = threading.Event()
  2266. def start_heartbeat(self, aid, cid, ids):
  2267. duration = ssid = epid = ''
  2268. for i in ids:
  2269. if 'ss' in i:
  2270. ssid = i.replace('ss', '')
  2271. elif 'ep' in i:
  2272. epid = i.replace('ep', '')
  2273. elif 'dur' in i:
  2274. duration = int(i.replace('dur', ''))
  2275. url = 'https://api.bilibili.com/x/player/v2?aid={0}&cid={1}'.format(aid, cid)
  2276. rsp = self._get_sth(url)
  2277. jo = json.loads(rsp.text)
  2278. data = jo.get('data',{})
  2279. interaction = data.get('interaction', {})
  2280. if interaction.get('graph_version'):
  2281. graph_version = interaction.get('graph_version')
  2282. old = self.detailContent_args.get('graph_version')
  2283. if old != graph_version:
  2284. self.detailContent_args['graph_version'] = graph_version
  2285. self.pool.submit(self.interaction_detailContent)
  2286. heartbeatInterval = int(self.userConfig['heartbeatInterval'])
  2287. if not self.userid or not heartbeatInterval:
  2288. return
  2289. if not duration:
  2290. url = 'https://api.bilibili.com/x/web-interface/view?aid={0}&cid={1}'.format(aid, cid)
  2291. rsp = self._get_sth(url, 'fake')
  2292. jRoot = json.loads(rsp.text)
  2293. duration = jRoot['data']['duration']
  2294. played_time = 0
  2295. if int(data.get('last_play_cid', 0)) == int(cid):
  2296. last_play_time = int(data.get('last_play_time'))
  2297. if last_play_time > 0:
  2298. played_time = int(last_play_time / 1000)
  2299. heartbeat_times = int((duration - played_time) / heartbeatInterval) + 1
  2300. url = 'https://api.bilibili.com/x/click-interface/web/heartbeat'
  2301. data = {'aid': str(aid), 'cid': str(cid), 'csrf': str(self.csrf)}
  2302. if ssid:
  2303. data['sid'] = str(ssid)
  2304. data['epid'] = str(epid)
  2305. data['type'] = '4'
  2306. heartbeat_count = 0
  2307. self.stop_heartbeat_event.clear()
  2308. while True:
  2309. if heartbeat_count == heartbeatInterval or self.stop_heartbeat_event.is_set():
  2310. played_time += heartbeat_count
  2311. heartbeat_count = 0
  2312. if not heartbeat_count:
  2313. heartbeat_times -= 1
  2314. if not heartbeat_times:
  2315. #播完为-1
  2316. played_time = -1
  2317. self.stop_heartbeat_event.set()
  2318. data['played_time'] = str(played_time)
  2319. self.pool.submit(self._post_sth, url=url, data=data)
  2320. if self.stop_heartbeat_event.is_set():
  2321. break
  2322. time.sleep(1)
  2323. heartbeat_count += 1
  2324. wbi_key = {}
  2325. def get_wbiKey(self, wts):
  2326. r = self.fetch("https://api.bilibili.com/x/web-interface/nav", headers=self.header)
  2327. wbi_img_url = r.json()['data']['wbi_img']['img_url']
  2328. wbi_sub_url = r.json()['data']['wbi_img']['sub_url']
  2329. oe = [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12,
  2330. 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62,
  2331. 11, 36, 20, 34, 44, 52]
  2332. ae = wbi_img_url.split("/")[-1].split(".")[0] + wbi_sub_url.split("/")[-1].split(".")[0]
  2333. le = reduce(lambda s, i: s + ae[i], oe, "")[:32]
  2334. self.wbi_key = {
  2335. "key": le,
  2336. "wts": wts
  2337. }
  2338. def encrypt_wbi(self, **params):
  2339. wts = round(time.time())
  2340. if not self.wbi_key or abs(self.wbi_key['wts']) < 120:
  2341. self.get_wbiKey(wts)
  2342. params["wts"] = wts
  2343. params["dm_img_list"] = []
  2344. dm_rand = ['QQ','Qg','Qw','RA','RQ']
  2345. params["dm_img_str"] = random.choice(dm_rand)
  2346. params["dm_cover_img_str"] = random.choice(dm_rand)
  2347. params = dict(sorted(params.items()))
  2348. params = {k : ''.join(filter(lambda chr: chr not in "!'()*", str(v))) for k, v in params.items()}
  2349. Ae = urlencode(params)
  2350. return Ae + "&w_rid=" + hashlib.md5((Ae + self.wbi_key['key']).encode(encoding='utf-8')).hexdigest()
  2351. def _get_sth(self, url, _type='master'):
  2352. if _type == 'vip' and self.session_vip.cookies:
  2353. rsp = self.session_vip.get(url, headers=self.header)
  2354. elif _type == 'fake':
  2355. if not self.session_fake.cookies:
  2356. self.getFakeCookie_event.wait()
  2357. rsp = self.session_fake.get(url, headers=self.header)
  2358. else:
  2359. rsp = self.session_master.get(url, headers=self.header)
  2360. return rsp
  2361. def _post_sth(self, url, data):
  2362. return self.session_master.post(url, headers=self.header, data=data)
  2363. def post_live_history(self, room_id):
  2364. data = {'room_id': str(room_id), 'platform': 'pc', 'csrf': str(self.csrf)}
  2365. url = 'https://api.live.bilibili.com/xlive/web-room/v1/index/roomEntryAction'
  2366. self._post_sth(url=url, data=data)
  2367. def do_notplay(self, ids):
  2368. aid = self.detailContent_args.get('aid')
  2369. mid = self.detailContent_args.get('mid')
  2370. ssid = self.detailContent_args.get('ssid')
  2371. data = {'csrf': str(self.csrf)}
  2372. url = ''
  2373. if 'vodTMPQn' in ids:
  2374. self.detailContent_args['vodTMPQn'] = str(ids[0])
  2375. return
  2376. elif 'follow' in ids:
  2377. if 'special' in ids:
  2378. data.update({'fids': str(mid), 'tagids': str(ids[0])})
  2379. url = 'https://api.bilibili.com/x/relation/tags/addUsers'
  2380. else:
  2381. data.update({'fid': str(mid), 'act': str(ids[0])})
  2382. url = 'https://api.bilibili.com/x/relation/modify'
  2383. elif 'zhui' in ids:
  2384. data.update({'season_id': str(ssid)})
  2385. url = 'https://api.bilibili.com/pgc/web/follow/' + str(ids[0])
  2386. elif 'like' in ids:
  2387. data.update({'aid': str(aid), 'like': str(ids[0])})
  2388. url = 'https://api.bilibili.com/x/web-interface/archive/like'
  2389. elif 'coin' in ids:
  2390. data.update({'aid': str(aid), 'multiply': str(ids[0]), 'select_like': '1'})
  2391. url = 'https://api.bilibili.com/x/web-interface/coin/add'
  2392. elif 'fav' in ids:
  2393. data.update({'rid': str(aid), 'type': '2'})
  2394. data[ids[1] + '_media_ids'] = str(ids[0])
  2395. url = 'https://api.bilibili.com/x/v3/fav/resource/deal'
  2396. elif 'triple' in ids:
  2397. data.update({'aid': str(aid)})
  2398. url = 'https://api.bilibili.com/x/web-interface/archive/like/triple'
  2399. elif 'reply' in ids:
  2400. data.update({'oid': str(ids[0]), 'rpid': str(ids[1]), 'type': '1', 'action': '1'})
  2401. url = 'http://api.bilibili.com/x/v2/reply/action'
  2402. self._post_sth(url=url, data=data)
  2403. def get_cid(self, video):
  2404. url = "https://api.bilibili.com/x/web-interface/view?aid=%s" % str(video['aid'])
  2405. rsp = self._get_sth(url)
  2406. jRoot = json.loads(rsp.text)
  2407. jo = jRoot['data']
  2408. video['cid'] = jo['cid']
  2409. video['duration'] = jo['duration']
  2410. if 'redirect_url' in jo and 'bangumi' in jo['redirect_url']:
  2411. video['ep'] = self.find_bangumi_id(jo['redirect_url'])
  2412. cookie_dic_tmp = {}
  2413. def get_cookies(self, key):
  2414. url = 'https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=' + key
  2415. rsp = self._get_sth(url, 'fake')
  2416. jo = json.loads(rsp.text)
  2417. if jo['code'] == 0:
  2418. message = jo['data']['message']
  2419. if not message:
  2420. self.cookie_dic_tmp[key] = dict(self.session_fake.cookies)
  2421. self.pool.submit(self.getFakeCookie)
  2422. return message
  2423. return '网络错误'
  2424. def set_cookie(self, key, _type):
  2425. cookie_dic_tmp = self.cookie_dic_tmp.get(key, '')
  2426. if not cookie_dic_tmp:
  2427. message = self.get_cookies(key)
  2428. if message:
  2429. return
  2430. users = self.userConfig.get('users', {})
  2431. users[_type] = {'cookies_dic': self.cookie_dic_tmp.get(key, {})}
  2432. self.userConfig.update({'users': users})
  2433. self.getCookie(_type)
  2434. self.dump_config()
  2435. def unset_cookie(self, _type):
  2436. if _type == 'vip':
  2437. self.session_vip.cookies.clear()
  2438. else:
  2439. self.session_master.cookies = self.session_fake.cookies
  2440. self.userid = self.csrf = ''
  2441. if _type in self.userConfig.get('users', {}):
  2442. self.userConfig['users'].pop(_type)
  2443. self.dump_config()
  2444. def set_normal_default(self, id, type):
  2445. self.userConfig[type] = str(id)
  2446. self.dump_config()
  2447. def set_normal_cateManual(self, name, _List, action):
  2448. List_tmp = self.userConfig.get(str(_List) + '_tmp')
  2449. if not List_tmp:
  2450. List_tmp = self.userConfig[str(_List) + '_tmp'] = []
  2451. if action == 'save':
  2452. for _item in self.defaultConfig[_List]:
  2453. if not _item in List_tmp.copy():
  2454. self.userConfig[str(_List) + '_tmp'].append(_item)
  2455. self.userConfig[_List] = self.userConfig[str(_List) + '_tmp'].copy()
  2456. self.userConfig.pop(_List + '_tmp')
  2457. self.dump_config()
  2458. elif action == 'clear':
  2459. self.userConfig[_List] = self.defaultConfig[_List].copy()
  2460. self.userConfig.pop(str(_List) + '_tmp')
  2461. self.dump_config()
  2462. else:
  2463. if _List == 'cateManualLive':
  2464. name = name.split('@@@')
  2465. if len(name) == 3:
  2466. name[1] += '_' + str(name[2])
  2467. name = {'n': name[0], 'v': str(name[1])}
  2468. if name in List_tmp:
  2469. self.userConfig[str(_List) + '_tmp'].remove(name)
  2470. else:
  2471. self.userConfig[str(_List) + '_tmp'].append(name)
  2472. def add_cateManualLiveExtra(self, action, name, id):
  2473. _Extra = self.userConfig.get('cateManualLiveExtra', [])
  2474. if not _Extra:
  2475. _Extra = self.userConfig['cateManualLiveExtra'] = []
  2476. if action == 'clear':
  2477. for _ext in _Extra:
  2478. _ext['v'] = _ext['v'].replace('@@@', '_')
  2479. if _ext in self.userConfig.get('cateManualLive', []):
  2480. self.userConfig['cateManualLive'].remove(_ext)
  2481. if _ext in self.userConfig.get('cateManualLive_tmp', []):
  2482. self.userConfig['cateManualLive_tmp'].remove(_ext)
  2483. self.userConfig.pop('cateManualLiveExtra')
  2484. elif id in list(map(lambda x:x['v'], self.userConfig.get('cateManualLiveExtra', []))):
  2485. area_dict = {'n': name, 'v': id}
  2486. self.userConfig['cateManualLiveExtra'].remove(area_dict)
  2487. area_dict['v'] = id.replace('@@@', '_')
  2488. if area_dict in self.userConfig.get('cateManualLive', []):
  2489. self.userConfig['cateManualLive'].remove(area_dict)
  2490. if area_dict in self.userConfig.get('cateManualLive_tmp', []):
  2491. self.userConfig['cateManualLive_tmp'].remove(area_dict)
  2492. else:
  2493. area_dict = {'n': name, 'v': id}
  2494. self.userConfig['cateManualLiveExtra'].append(area_dict)
  2495. self.dump_config()
  2496. vod_qn_id = {
  2497. '127': "8K",
  2498. '126': "杜比视界",
  2499. '125': "HDR",
  2500. '120': "4K",
  2501. '116': "1080P60帧",
  2502. '112': "1080P+",
  2503. '80': "1080P",
  2504. '64': "720P",
  2505. }
  2506. vod_codec_id = {
  2507. '7': 'avc',
  2508. '12': 'hevc',
  2509. '13': 'av1',
  2510. }
  2511. vod_audio_id = {
  2512. '30280': '192000',
  2513. '30232': '132000',
  2514. '30216': '64000',
  2515. }
  2516. def get_dash_media(self, video):
  2517. qnid = str(video.get('id'))
  2518. codecid = video.get('codecid')
  2519. media_codecs = video.get('codecs')
  2520. media_bandwidth = video.get('bandwidth')
  2521. media_startWithSAP = video.get('startWithSap')
  2522. media_mimeType = video.get('mimeType')
  2523. media_BaseURL = video.get('baseUrl').replace('&', '&amp;')
  2524. media_SegmentBase_indexRange = video['SegmentBase'].get('indexRange')
  2525. media_SegmentBase_Initialization = video['SegmentBase'].get('Initialization')
  2526. mediaType = media_mimeType.split('/')[0]
  2527. if mediaType == 'video':
  2528. media_frameRate = video.get('frameRate')
  2529. media_sar = video.get('sar')
  2530. media_width = video.get('width')
  2531. media_height = video.get('height')
  2532. media_type_params = f"height='{media_height}' width='{media_width}' frameRate='{media_frameRate}' sar='{media_sar}'"
  2533. elif mediaType == 'audio':
  2534. audioSamplingRate = self.vod_audio_id.get(qnid, '192000')
  2535. media_type_params = f"numChannels='2' sampleRate='{audioSamplingRate}'"
  2536. if codecid:
  2537. qnid += '_' + str(codecid)
  2538. result = f"""
  2539. <Representation id="{qnid}" bandwidth="{media_bandwidth}" codecs="{media_codecs}" mimeType="{media_mimeType}" {media_type_params} startWithSAP="{media_startWithSAP}">
  2540. <BaseURL>{media_BaseURL}</BaseURL>
  2541. <SegmentBase indexRange="{media_SegmentBase_indexRange}">
  2542. <Initialization range="{media_SegmentBase_Initialization}"/>
  2543. </SegmentBase>
  2544. </Representation>"""
  2545. return result
  2546. def get_dash_media_list(self, media_lis):
  2547. if not media_lis:
  2548. return ""
  2549. mediaType = media_lis[0]['mimeType'].split('/')[0]
  2550. defaultQn = defaultCodec = ''
  2551. if mediaType == 'video':
  2552. defaultQn = vodTMPQn = self.detailContent_args.get('vodTMPQn', '')
  2553. if vodTMPQn:
  2554. vodTMPQn = int(vodTMPQn)
  2555. else:
  2556. defaultQn = str(self.userConfig['vodDefaultQn'])
  2557. vodTMPQn = 120
  2558. defaultCodec = str(self.userConfig['vodDefaultCodec'])
  2559. elif mediaType == 'audio':
  2560. defaultQn = str(self.userConfig['vodDefaultAudio'])
  2561. vodTMPQn = int(defaultQn)
  2562. defaultCodec = '0'
  2563. qn_codec = list(map(lambda x: str(x['id']) + '_' + str(x['codecid']), media_lis))
  2564. Qn_available_lis = []
  2565. #按设定的质量和设定的编码找
  2566. if defaultQn + '_' + defaultCodec in qn_codec:
  2567. Qn_available_lis.append(media_lis[qn_codec.index(defaultQn + '_' + defaultCodec)])
  2568. #按设定的质量找推荐的编码
  2569. if not Qn_available_lis and mediaType == 'video':
  2570. for c in self.vod_codec_id.keys():
  2571. if defaultQn + '_' + str(c) in qn_codec:
  2572. Qn_available_lis.append(media_lis[qn_codec.index(defaultQn + '_' + str(c))])
  2573. #找4K及以下最高可用画质/音质
  2574. if not Qn_available_lis:
  2575. qn_top = ''
  2576. for q in qn_codec:
  2577. q_c = q.split('_')
  2578. if qn_top and int(qn_top) > int(q_c[0]):
  2579. break
  2580. elif mediaType == 'video' and int(q_c[0]) <= vodTMPQn and not qn_top or mediaType == 'audio' and not qn_top or int(q_c[0]) == qn_top:
  2581. qn_top = int(q_c[0])
  2582. #匹配设定的编码,否则全部
  2583. if mediaType == 'video' and str(q_c[1]) == defaultCodec:
  2584. Qn_available_lis = [media_lis[qn_codec.index(str(q))]]
  2585. break
  2586. Qn_available_lis.append(media_lis[qn_codec.index(str(q))])
  2587. result = f"""
  2588. <AdaptationSet>
  2589. <ContentComponent contentType="{mediaType}"/>{''.join(map(self.get_dash_media, Qn_available_lis))}
  2590. </AdaptationSet>"""
  2591. return result
  2592. get_dash_event = threading.Event()
  2593. def get_dash(self, ja):
  2594. duration = ja.get('duration')
  2595. minBufferTime = ja.get('minBufferTime')
  2596. video_list = self.pool.submit(self.get_dash_media_list, ja.get('video'))
  2597. audio_list = self.pool.submit(self.get_dash_media_list, ja.get('audio'))
  2598. mpd = f"""<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" type="static" mediaPresentationDuration="PT{duration}S" minBufferTime="PT{minBufferTime}S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
  2599. <Period duration="PT{duration}S" start="PT0S">{video_list.result()}{audio_list.result()}
  2600. </Period>
  2601. </MPD>"""
  2602. with open(f"{dirname}/playurl.mpd", 'w', encoding="utf-8") as f:
  2603. f.write(mpd)
  2604. self.get_dash_event.set()
  2605. #time.sleep(3)
  2606. #os.remove(f"{dirname}/playurl.mpd")
  2607. def get_durl(self, ja):
  2608. maxSize = -1
  2609. position = -1
  2610. for i in range(len(ja)):
  2611. tmpJo = ja[i]
  2612. if maxSize < int(tmpJo['size']):
  2613. maxSize = int(tmpJo['size'])
  2614. position = i
  2615. url = ''
  2616. if len(ja) > 0:
  2617. if position == -1:
  2618. position = 0
  2619. url = ja[position]['url']
  2620. return url
  2621. def miao(self, m):
  2622. m = str(m).partition('.')[2] #取小数部分
  2623. if len(m)==0:m = '000' #补齐三位小数
  2624. if len(m)==1:m = m + '00'
  2625. if len(m)==2:m = m + '0'
  2626. return m #返回标准三位的毫秒数
  2627. def down_sub(self, url):
  2628. rsp = self._get_sth(url, 'fake')
  2629. data = json.loads(rsp.text)['body']
  2630. srt = ''
  2631. for d in data:
  2632. f = round(d['from'],3) # 开始时间 (round(n,3)四舍五入为三位小数)
  2633. t = round(d['to'],3) # 结束时间
  2634. c = d['content'] # 字幕内容
  2635. ff = time.strftime("%H:%M:%S",time.gmtime(f)) + ',' + self.miao(f) # 开始时间,秒数转 时:分:秒 格式,加逗号、毫秒修正为三位
  2636. tt = time.strftime("%H:%M:%S",time.gmtime(t)) + ',' + self.miao(t) # 结束时间,处理方式同上
  2637. srt += '\n' + ff + ' ' + '-->' + ' ' + tt + '\n' + c + '\n\n' # 格式化为Srt字幕
  2638. return srt
  2639. localProxyUrl = 'http://127.0.0.1:UndCover/proxy'
  2640. def get_subs(self, aid, cid):
  2641. result = []
  2642. query = self.encrypt_wbi(aid=aid, cid=cid)
  2643. url = f'https://api.bilibili.com/x/player/wbi/v2?{query}'
  2644. rsp = self._get_sth(url, 'master')
  2645. jRoot = json.loads(rsp.text)
  2646. if jRoot['code'] == 0:
  2647. for sub in jRoot['data']['subtitle'].get('subtitles', []):
  2648. lanDoc = str(sub.get('lan_doc', ''))
  2649. lanUrl = sub.get('subtitle_url')
  2650. if lanUrl.startswith('//'):
  2651. lanUrl = 'https:' + lanUrl
  2652. lanUrl = base64.b64encode(lanUrl.encode('utf-8')).decode('utf-8')
  2653. result.append(
  2654. {
  2655. "url": f"{self.localProxyUrl}&siteType=3&siteKey=py_bilibili&type=subtitle&url={lanUrl}",
  2656. "name": lanDoc,
  2657. "format": "application/x-subrip"
  2658. }
  2659. )
  2660. return result
  2661. def playerContent(self, flag, id, vipFlags):
  2662. self.stop_heartbeat_event.set()
  2663. result = {'playUrl': '', 'url': ''}
  2664. ids = id.split("_")
  2665. if 'web' in id or 'liveapi2' == ids[0]:
  2666. return self.live_playerContent(flag, id, vipFlags)
  2667. if len(ids) < 2:
  2668. return result
  2669. aid = ids[0]
  2670. cid = ids[1]
  2671. if 'setting' in ids:
  2672. if 'liveFilter' in id:
  2673. id = ids[2]
  2674. self.add_cateManualLiveExtra(aid, cid, id)
  2675. elif cid in ['cateManual', 'cateManualLive', 'tuijianLis', 'rankingLis']:
  2676. action = ids[2]
  2677. self.set_normal_cateManual(aid, cid, action)
  2678. elif 'login' in id:
  2679. self.set_cookie(aid, cid)
  2680. elif 'logout' in id:
  2681. self.unset_cookie(aid)
  2682. else:
  2683. self.set_normal_default(aid, cid)
  2684. return result
  2685. elif 'notplay' in ids:
  2686. self.pool.submit(self.do_notplay, ids)
  2687. return result
  2688. elif cid == 'cid' or not cid:
  2689. video = {'aid': str(aid)}
  2690. self.get_cid(video, )
  2691. cid = video['cid']
  2692. ids.append('dur' + str(video['duration']))
  2693. ep = video.get('ep')
  2694. if ep:
  2695. id += '_' + ep
  2696. ids.append(ep)
  2697. query = self.encrypt_wbi(avid=aid, cid=cid, fnval=4048, fnver=0, fourk=1)
  2698. url = f'https://api.bilibili.com/x/player/wbi/playurl?{query}'
  2699. if 'ep' in id:
  2700. if 'parse' in id:
  2701. test = list(x for x in map(lambda x: x if 'ep' in x else None, ids) if x is not None)
  2702. url = 'https://www.bilibili.com/bangumi/play/' + test[0]
  2703. result["url"] = url
  2704. result["flag"] = 'bilibili'
  2705. result["parse"] = '1'
  2706. result['jx'] = '1'
  2707. result["header"] = str({"User-Agent": self.header["User-Agent"]})
  2708. result["danmaku"] = 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + str(cid)
  2709. return result
  2710. url = 'https://api.bilibili.com/pgc/player/web/playurl?aid={}&cid={}&fnval=4048&fnver=0&fourk=1'.format(aid, cid)
  2711. rsp = self._get_sth(url, 'vip')
  2712. jRoot = json.loads(rsp.text)
  2713. if jRoot['code'] == 0:
  2714. if 'data' in jRoot:
  2715. jo = jRoot['data']
  2716. elif 'result' in jRoot:
  2717. jo = jRoot['result']
  2718. else:
  2719. return result
  2720. else:
  2721. return result
  2722. ja = jo.get('dash')
  2723. if ja:
  2724. self.get_dash_event.clear()
  2725. get_dash = self.pool.submit(self.get_dash, ja)
  2726. self.get_dash_event.wait()
  2727. result["url"] = f"{dirname}/playurl.mpd"
  2728. else:
  2729. result["url"] = self.get_durl(jo.get('durl', {}))
  2730. result["parse"] = '0'
  2731. result["contentType"] = ''
  2732. result["header"] = self.header
  2733. result["danmaku"] = 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + str(cid)
  2734. if self.isFongmi:
  2735. result["subs"] = self.get_subs(aid, cid)
  2736. #回传播放记录
  2737. self.pool.submit(self.start_heartbeat, aid, cid, ids)
  2738. return result
  2739. def live_playerContent(self, flag, id, vipFlags):
  2740. result = {'playUrl': '', 'url': ''}
  2741. ids = id.split("_")
  2742. if len(ids) < 2:
  2743. return result
  2744. # 回传观看直播记录
  2745. if self.userid and int(self.userConfig['heartbeatInterval']) > 0:
  2746. self.pool.submit(self.post_live_history, ids[-1])
  2747. if ids[0] == 'liveapi2':
  2748. qn = int(ids[1])
  2749. format = int(ids[2])
  2750. codec = int(ids[3])
  2751. room_id = int(ids[-1])
  2752. url = 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?room_id={0}&protocol=0,1&format={1}&codec={2}&qn={3}&ptype=8&platform=web&dolby=5&panorama=1&no_playurl=0&mask=1'.format(room_id, format, codec, qn)
  2753. rsp = self._get_sth(url, 'fake')
  2754. jo = json.loads(rsp.text)
  2755. if jo['code'] == 0:
  2756. try:
  2757. playurl = jo['data']['playurl_info'].get('playurl')
  2758. codec = playurl['stream'][0]['format'][0]['codec'][0]
  2759. except:
  2760. return result
  2761. base_url = str(codec['base_url'])
  2762. host = str(codec['url_info'][0]['host'])
  2763. extra = str(codec['url_info'][0]['extra'])
  2764. playurl = host + base_url + extra
  2765. result["url"] = playurl
  2766. if ".flv" in playurl:
  2767. result["contentType"] = 'video/x-flv'
  2768. else:
  2769. result["contentType"] = ''
  2770. else:
  2771. return result
  2772. else:
  2773. url = 'https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&%s' % (ids[1], ids[0])
  2774. # raise Exception(url)
  2775. try:
  2776. rsp = self._get_sth(url)
  2777. except:
  2778. return result
  2779. jRoot = json.loads(rsp.text)
  2780. if jRoot['code'] == 0:
  2781. jo = jRoot['data']
  2782. ja = jo['durl']
  2783. if len(ja) > 0:
  2784. result["url"] = ja[0]['url']
  2785. if "h5" in ids[0]:
  2786. result["contentType"] = ''
  2787. else:
  2788. result["contentType"] = 'video/x-flv'
  2789. else:
  2790. return result
  2791. result["parse"] = '0'
  2792. # result['type'] ="m3u8"
  2793. result["header"] = {
  2794. "Referer": "https://live.bilibili.com",
  2795. "User-Agent": self.header["User-Agent"]
  2796. }
  2797. return result
  2798. config = {
  2799. "player": {},
  2800. "filter": {
  2801. "关注": [{"key": "sort", "name": "分类",
  2802. "value": [{"n": "正在直播", "v": "正在直播"}, {"n": "最常访问", "v": "最常访问"},
  2803. {"n": "最近关注", "v": "最近关注"}, {"n": "特别关注", "v": "特别关注"},
  2804. {"n": "悄悄关注", "v": "悄悄关注"}, {"n": "我的粉丝", "v": "我的粉丝"}]}],
  2805. "动态": [{"key": "order", "name": "别人投稿排序",
  2806. "value": [{"n": "最新发布", "v": "pubdate"}, {"n": "最多播放", "v": "click"},
  2807. {"n": "最多收藏", "v": "stow"}, {"n": "最早发布", "v": "oldest"}, {"n": "合集和列表", "v": "series"}]}, ],
  2808. "影视": [{"key": "tid", "name": "分类",
  2809. "value": [{"n": "番剧", "v": "1"}, {"n": "国创", "v": "4"}, {"n": "电影", "v": "2"},
  2810. {"n": "电视剧", "v": "5"}, {"n": "纪录片", "v": "3"}, {"n": "综艺", "v": "7"}]},
  2811. {"key": "order", "name": "排序",
  2812. "value": [{"n": "热门", "v": "热门"}, {"n": "播放数量", "v": "2"}, {"n": "更新时间", "v": "0"},
  2813. {"n": "最高评分", "v": "4"}, {"n": "弹幕数量", "v": "1"}, {"n": "追看人数", "v": "3"},
  2814. {"n": "开播时间", "v": "5"}, {"n": "上映时间", "v": "6"}]},
  2815. {"key": "season_status", "name": "付费",
  2816. "value": [{"n": "全部", "v": "-1"}, {"n": "免费", "v": "1"},
  2817. {"n": "付费", "v": "2%2C6"}, {"n": "大会员", "v": "4%2C6"}]}],
  2818. "频道": [{"key": "order", "name": "排序",
  2819. "value": [{"n": "近期热门", "v": "hot"}, {"n": "月播放量", "v": "view"},
  2820. {"n": "最新投稿", "v": "new"}, {"n": "频道精选", "v": "featured"}, ]}, ],
  2821. "收藏": [{"key": "order", "name": "排序",
  2822. "value": [{"n": "收藏时间", "v": "mtime"}, {"n": "播放量", "v": "view"},
  2823. {"n": "投稿时间", "v": "pubtime"}]}, ],
  2824. "历史": [{"key": "type", "name": "分类",
  2825. "value": [{"n": "全部", "v": "all"}, {"n": "视频", "v": "archive"}, {"n": "直播", "v": "live"}, {"n": "UP主", "v": "UP主"}, {"n": "稍后再看", "v": "稍后再看"}]}, ],
  2826. "搜索": [{"key": "type", "name": "类型",
  2827. "value": [{"n": "视频", "v": "video"}, {"n": "番剧", "v": "media_bangumi"}, {"n": "影视", "v": "media_ft"},
  2828. {"n": "直播", "v": "live"}, {"n": "用户", "v": "bili_user"}]},
  2829. {"key": "order", "name": "视频排序",
  2830. "value": [{"n": "综合排序", "v": "totalrank"}, {"n": "最多点击", "v": "click"}, {"n": "最新发布", "v": "pubdate"},
  2831. {"n": "最多收藏", "v": "stow"}, {"n": "最多弹幕", "v": "dm"}]},
  2832. {"key": "duration", "name": "视频时长",
  2833. "value": [{"n": "全部", "v": "0"}, {"n": "60分钟以上", "v": "4"}, {"n": "30~60分钟", "v": "3"},
  2834. {"n": "5~30分钟", "v": "2"}, {"n": "5分钟以下", "v": "1"}]}],
  2835. }
  2836. }
  2837. header = {
  2838. 'Origin': 'https://www.bilibili.com',
  2839. 'Referer': 'https://www.bilibili.com',
  2840. 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15'
  2841. }
  2842. def localProxy(self, param):
  2843. action = {
  2844. 'url': '',
  2845. 'header': '',
  2846. 'param': '',
  2847. 'type': 'string',
  2848. 'after': ''
  2849. }
  2850. if param['type'] == 'subtitle':
  2851. url = base64.b64decode(param['url']).decode('utf-8')
  2852. content = self.down_sub(url)
  2853. return [200, "application/octet-stream", action, content]
  2854. return [200, "video/MP2T", action, ""]