api_old.py 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180
  1. import logging, os, sys, json
  2. import threading
  3. import schedule, time
  4. import random
  5. import aiohttp, asyncio
  6. import traceback
  7. import copy
  8. import webbrowser
  9. from functools import partial
  10. import http.cookies
  11. from typing import *
  12. from flask import Flask, send_from_directory, render_template, request, jsonify
  13. from flask_socketio import SocketIO, emit
  14. from flask_cors import CORS
  15. from utils.common import Common
  16. from utils.config import Config
  17. from utils.logger import Configure_logger
  18. from utils.my_handle import My_handle
  19. """
  20. 全局变量
  21. """
  22. # 创建一个全局变量,用于表示程序是否正在运行
  23. running_flag = False
  24. # 创建一个子进程对象,用于存储正在运行的外部程序
  25. running_process = None
  26. config = None
  27. common = None
  28. my_handle = None
  29. # last_liveroom_data = None
  30. last_username_list = None
  31. # 空闲时间计数器
  32. global_idle_time = 0
  33. # 键盘监听线程
  34. thread = None
  35. do_listen_and_comment_thread = None
  36. stop_do_listen_and_comment_thread_event = None
  37. # 这里填一个已登录账号的cookie。不填cookie也可以连接,但是收到弹幕的用户名会打码,UID会变成0
  38. SESSDATA = ''
  39. session: Optional[aiohttp.ClientSession] = None
  40. # 最新的直播间数据
  41. last_liveroom_data = {
  42. 'OnlineUserCount': 0,
  43. 'TotalUserCount': 0,
  44. 'TotalUserCountStr': '0',
  45. 'OnlineUserCountStr': '0',
  46. 'MsgId': 0,
  47. 'User': None,
  48. 'Content': '当前直播间人数 0,累计直播间人数 0',
  49. 'RoomId': 0
  50. }
  51. # 最新入场的用户名列表
  52. last_username_list = [""]
  53. common = Common()
  54. # 日志文件路径
  55. log_path = "./log/log-" + common.get_bj_time(1) + ".txt"
  56. Configure_logger(log_path)
  57. # 获取 werkzeug 库的日志记录器
  58. werkzeug_logger = logging.getLogger("werkzeug")
  59. # 设置 httpx 日志记录器的级别为 WARNING
  60. werkzeug_logger.setLevel(logging.WARNING)
  61. # 点火起飞
  62. def start_server(config_path, sub_thread_exit_events):
  63. global log_path, config, common, my_handle, last_username_list, last_liveroom_data
  64. global SESSDATA
  65. global thread, do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event
  66. # 创建和启动子线程
  67. sub_threads = []
  68. config = Config(config_path)
  69. # 获取 httpx 库的日志记录器
  70. httpx_logger = logging.getLogger("httpx")
  71. # 设置 httpx 日志记录器的级别为 WARNING
  72. httpx_logger.setLevel(logging.WARNING)
  73. my_handle = My_handle(config_path)
  74. if my_handle is None:
  75. logging.error("程序初始化失败!")
  76. os._exit(0)
  77. # 添加用户名到最新的用户名列表
  78. def add_username_to_last_username_list(data):
  79. global last_username_list
  80. # 添加数据到 最新入场的用户名列表
  81. last_username_list.append(data)
  82. # 保留最新的3个数据
  83. last_username_list = last_username_list[-3:]
  84. # 定时任务
  85. def schedule_task(index):
  86. logging.debug("定时任务执行中...")
  87. hour, min = common.get_bj_time(6)
  88. if 0 <= hour and hour < 6:
  89. time = f"凌晨{hour}点{min}分"
  90. elif 6 <= hour and hour < 9:
  91. time = f"早晨{hour}点{min}分"
  92. elif 9 <= hour and hour < 12:
  93. time = f"上午{hour}点{min}分"
  94. elif hour == 12:
  95. time = f"中午{hour}点{min}分"
  96. elif 13 <= hour and hour < 18:
  97. time = f"下午{hour - 12}点{min}分"
  98. elif 18 <= hour and hour < 20:
  99. time = f"傍晚{hour - 12}点{min}分"
  100. elif 20 <= hour and hour < 24:
  101. time = f"晚上{hour - 12}点{min}分"
  102. # 根据对应索引从列表中随机获取一个值
  103. random_copy = random.choice(config.get("schedule")[index]["copy"])
  104. # 假设有多个未知变量,用户可以在此处定义动态变量
  105. variables = {
  106. 'time': time,
  107. 'user_num': "N",
  108. 'last_username': last_username_list[-1],
  109. }
  110. # 使用字典进行字符串替换
  111. if any(var in random_copy for var in variables):
  112. content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy})
  113. else:
  114. content = random_copy
  115. data = {
  116. "platform": "哔哩哔哩",
  117. "username": None,
  118. "content": content
  119. }
  120. logging.info(f"定时任务:{content}")
  121. my_handle.process_data(data, "schedule")
  122. # 启动定时任务
  123. def run_schedule(exit_event):
  124. global config
  125. try:
  126. for index, task in enumerate(config.get("schedule")):
  127. if task["enable"]:
  128. # logging.info(task)
  129. # 设置定时任务,每隔n秒执行一次
  130. schedule.every(task["time"]).seconds.do(partial(schedule_task, index))
  131. except Exception as e:
  132. logging.error(traceback.format_exc())
  133. while True:
  134. schedule.run_pending()
  135. # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源
  136. if exit_event.is_set():
  137. return
  138. if any(item['enable'] for item in config.get("schedule")):
  139. # 创建定时任务子线程并启动
  140. schedule_thread = threading.Thread(target=run_schedule, args=(sub_thread_exit_events[1],))
  141. schedule_thread.start()
  142. sub_threads.append(schedule_thread)
  143. # 启动动态文案
  144. async def run_trends_copywriting(exit_event):
  145. global config
  146. try:
  147. if False == config.get("trends_copywriting", "enable"):
  148. return
  149. logging.info(f"动态文案任务线程运行中...")
  150. while True:
  151. # 文案文件路径列表
  152. copywriting_file_path_list = []
  153. # 获取动态文案列表
  154. for copywriting in config.get("trends_copywriting", "copywriting"):
  155. # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名
  156. for tmp in common.get_all_file_paths(copywriting["folder_path"]):
  157. copywriting_file_path_list.append(tmp)
  158. # 是否开启随机播放
  159. if config.get("trends_copywriting", "random_play"):
  160. random.shuffle(copywriting_file_path_list)
  161. logging.debug(f"copywriting_file_path_list={copywriting_file_path_list}")
  162. # 遍历文案文件路径列表
  163. for copywriting_file_path in copywriting_file_path_list:
  164. # 获取文案文件内容
  165. copywriting_file_content = common.read_file_return_content(copywriting_file_path)
  166. # 是否启用提示词对文案内容进行转换
  167. if copywriting["prompt_change_enable"]:
  168. data_json = {
  169. "username": "trends_copywriting",
  170. "content": copywriting["prompt_change_content"] + copywriting_file_content
  171. }
  172. # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现
  173. data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json)
  174. else:
  175. data_json = {
  176. "username": "trends_copywriting",
  177. "content": copywriting_file_content
  178. }
  179. logging.debug(f'copywriting_file_content={copywriting_file_content},content={data_json["content"]}')
  180. # 空数据判断
  181. if data_json["content"] != None and data_json["content"] != "":
  182. # 发给直接复读进行处理
  183. my_handle.reread_handle(data_json)
  184. await asyncio.sleep(config.get("trends_copywriting", "play_interval"))
  185. if exit_event.is_set():
  186. return
  187. except Exception as e:
  188. logging.error(traceback.format_exc())
  189. if config.get("trends_copywriting", "enable"):
  190. # 创建动态文案子线程并启动
  191. trends_copywriting_thread = threading.Thread(target=lambda: asyncio.run(run_trends_copywriting()), args=(sub_thread_exit_events[2],))
  192. trends_copywriting_thread.start()
  193. sub_threads.append(trends_copywriting_thread)
  194. # 闲时任务
  195. async def idle_time_task(exit_event):
  196. global config, global_idle_time
  197. try:
  198. if False == config.get("idle_time_task", "enable"):
  199. return
  200. logging.info(f"闲时任务线程运行中...")
  201. # 记录上一次触发的任务类型
  202. last_mode = 0
  203. comment_copy_list = None
  204. local_audio_path_list = None
  205. overflow_time = int(config.get("idle_time_task", "idle_time"))
  206. # 是否开启了随机闲时时间
  207. if config.get("idle_time_task", "random_time"):
  208. overflow_time = random.randint(0, overflow_time)
  209. logging.info(f"闲时时间={overflow_time}秒")
  210. def load_data_list(type):
  211. if type == "comment":
  212. tmp = config.get("idle_time_task", "comment", "copy")
  213. elif type == "local_audio":
  214. tmp = config.get("idle_time_task", "local_audio", "path")
  215. tmp2 = copy.copy(tmp)
  216. return tmp2
  217. comment_copy_list = load_data_list("comment")
  218. local_audio_path_list = load_data_list("local_audio")
  219. logging.debug(f"comment_copy_list={comment_copy_list}")
  220. logging.debug(f"local_audio_path_list={local_audio_path_list}")
  221. while True:
  222. # 每隔一秒的睡眠进行闲时计数
  223. await asyncio.sleep(1)
  224. global_idle_time = global_idle_time + 1
  225. # 闲时计数达到指定值,进行闲时任务处理
  226. if global_idle_time >= overflow_time:
  227. # 闲时计数清零
  228. global_idle_time = 0
  229. # 闲时任务处理
  230. if config.get("idle_time_task", "comment", "enable"):
  231. if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"):
  232. # 是否开启了随机触发
  233. if config.get("idle_time_task", "comment", "random"):
  234. logging.debug("切换到文案触发模式")
  235. if comment_copy_list != []:
  236. # 随机打乱列表中的元素
  237. random.shuffle(comment_copy_list)
  238. comment_copy = comment_copy_list.pop(0)
  239. else:
  240. # 刷新list数据
  241. comment_copy_list = load_data_list("comment")
  242. # 随机打乱列表中的元素
  243. random.shuffle(comment_copy_list)
  244. comment_copy = comment_copy_list.pop(0)
  245. else:
  246. if comment_copy_list != []:
  247. comment_copy = comment_copy_list.pop(0)
  248. else:
  249. # 刷新list数据
  250. comment_copy_list = load_data_list("comment")
  251. comment_copy = comment_copy_list.pop(0)
  252. # 发送给处理函数
  253. data = {
  254. "platform": "哔哩哔哩2",
  255. "username": "闲时任务",
  256. "type": "comment",
  257. "content": comment_copy
  258. }
  259. my_handle.process_data(data, "idle_time_task")
  260. # 模式切换
  261. last_mode = 1
  262. overflow_time = int(config.get("idle_time_task", "idle_time"))
  263. # 是否开启了随机闲时时间
  264. if config.get("idle_time_task", "random_time"):
  265. overflow_time = random.randint(0, overflow_time)
  266. logging.info(f"闲时时间={overflow_time}秒")
  267. continue
  268. if config.get("idle_time_task", "local_audio", "enable"):
  269. if last_mode == 1 or (not config.get("idle_time_task", "comment", "enable")):
  270. logging.debug("切换到本地音频模式")
  271. # 是否开启了随机触发
  272. if config.get("idle_time_task", "local_audio", "random"):
  273. if local_audio_path_list != []:
  274. # 随机打乱列表中的元素
  275. random.shuffle(local_audio_path_list)
  276. local_audio_path = local_audio_path_list.pop(0)
  277. else:
  278. # 刷新list数据
  279. local_audio_path_list = load_data_list("local_audio")
  280. # 随机打乱列表中的元素
  281. random.shuffle(local_audio_path_list)
  282. local_audio_path = local_audio_path_list.pop(0)
  283. else:
  284. if local_audio_path_list != []:
  285. local_audio_path = local_audio_path_list.pop(0)
  286. else:
  287. # 刷新list数据
  288. local_audio_path_list = load_data_list("local_audio")
  289. local_audio_path = local_audio_path_list.pop(0)
  290. logging.debug(f"local_audio_path={local_audio_path}")
  291. # 发送给处理函数
  292. data = {
  293. "platform": "哔哩哔哩2",
  294. "username": "闲时任务",
  295. "type": "local_audio",
  296. "content": common.extract_filename(local_audio_path, False),
  297. "file_path": local_audio_path
  298. }
  299. my_handle.process_data(data, "idle_time_task")
  300. # 模式切换
  301. last_mode = 0
  302. overflow_time = int(config.get("idle_time_task", "idle_time"))
  303. # 是否开启了随机闲时时间
  304. if config.get("idle_time_task", "random_time"):
  305. overflow_time = random.randint(0, overflow_time)
  306. logging.info(f"闲时时间={overflow_time}秒")
  307. continue
  308. if exit_event.is_set():
  309. return
  310. except Exception as e:
  311. logging.error(traceback.format_exc())
  312. if config.get("idle_time_task", "enable"):
  313. # 创建闲时任务子线程并启动
  314. idle_time_task_thread = threading.Thread(target=lambda: asyncio.run(idle_time_task()), args=(sub_thread_exit_events[3],))
  315. idle_time_task_thread.start()
  316. sub_threads.append(idle_time_task_thread)
  317. if config.get("platform") == "bilibili":
  318. try:
  319. # 导入所需的库
  320. from bilibili_api import Credential, live, sync, login
  321. if config.get("bilibili", "login_type") == "cookie":
  322. logging.info("b站登录后F12抓网络包获取cookie,强烈建议使用小号!有封号风险")
  323. logging.info("b站登录后,F12控制台,输入 window.localStorage.ac_time_value 回车获取(如果没有,请重新登录)")
  324. bilibili_cookie = config.get("bilibili", "cookie")
  325. bilibili_ac_time_value = config.get("bilibili", "ac_time_value")
  326. if bilibili_ac_time_value == "":
  327. bilibili_ac_time_value = None
  328. # print(f'SESSDATA={common.parse_cookie_data(bilibili_cookie, "SESSDATA")}')
  329. # print(f'bili_jct={common.parse_cookie_data(bilibili_cookie, "bili_jct")}')
  330. # print(f'buvid3={common.parse_cookie_data(bilibili_cookie, "buvid3")}')
  331. # print(f'DedeUserID={common.parse_cookie_data(bilibili_cookie, "DedeUserID")}')
  332. # 生成一个 Credential 对象
  333. credential = Credential(
  334. sessdata=common.parse_cookie_data(bilibili_cookie, "SESSDATA"),
  335. bili_jct=common.parse_cookie_data(bilibili_cookie, "bili_jct"),
  336. buvid3=common.parse_cookie_data(bilibili_cookie, "buvid3"),
  337. dedeuserid=common.parse_cookie_data(bilibili_cookie, "DedeUserID"),
  338. ac_time_value=bilibili_ac_time_value
  339. )
  340. elif config.get("bilibili", "login_type") == "手机扫码":
  341. credential = login.login_with_qrcode()
  342. elif config.get("bilibili", "login_type") == "手机扫码-终端":
  343. credential = login.login_with_qrcode_term()
  344. elif config.get("bilibili", "login_type") == "账号密码登录":
  345. bilibili_username = config.get("bilibili", "username")
  346. bilibili_password = config.get("bilibili", "password")
  347. credential = login.login_with_password(bilibili_username, bilibili_password)
  348. elif config.get("bilibili", "login_type") == "不登录":
  349. credential = None
  350. else:
  351. credential = login.login_with_qrcode()
  352. # 初始化 Bilibili 直播间
  353. room = live.LiveDanmaku(my_handle.get_room_id(), credential=credential)
  354. except Exception as e:
  355. logging.error(traceback.format_exc())
  356. os._exit(0)
  357. """
  358. DANMU_MSG: 用户发送弹幕
  359. SEND_GIFT: 礼物
  360. COMBO_SEND:礼物连击
  361. GUARD_BUY:续费大航海
  362. SUPER_CHAT_MESSAGE:醒目留言(SC)
  363. SUPER_CHAT_MESSAGE_JPN:醒目留言(带日语翻译?)
  364. WELCOME: 老爷进入房间
  365. WELCOME_GUARD: 房管进入房间
  366. NOTICE_MSG: 系统通知(全频道广播之类的)
  367. PREPARING: 直播准备中
  368. LIVE: 直播开始
  369. ROOM_REAL_TIME_MESSAGE_UPDATE: 粉丝数等更新
  370. ENTRY_EFFECT: 进场特效
  371. ROOM_RANK: 房间排名更新
  372. INTERACT_WORD: 用户进入直播间
  373. ACTIVITY_BANNER_UPDATE_V2: 好像是房间名旁边那个xx小时榜
  374. 本模块自定义事件:
  375. VIEW: 直播间人气更新
  376. ALL: 所有事件
  377. DISCONNECT: 断开连接(传入连接状态码参数)
  378. TIMEOUT: 心跳响应超时
  379. VERIFICATION_SUCCESSFUL: 认证成功
  380. """
  381. @room.on('DANMU_MSG')
  382. async def _(event):
  383. """
  384. 处理直播间弹幕事件
  385. :param event: 弹幕事件数据
  386. """
  387. global global_idle_time
  388. # 闲时计数清零
  389. global_idle_time = 0
  390. content = event["data"]["info"][1] # 获取弹幕内容
  391. username = event["data"]["info"][2][1] # 获取发送弹幕的用户昵称
  392. logging.info(f"[{username}]: {content}")
  393. data = {
  394. "platform": "哔哩哔哩",
  395. "username": username,
  396. "content": content
  397. }
  398. my_handle.process_data(data, "comment")
  399. @room.on('COMBO_SEND')
  400. async def _(event):
  401. """
  402. 处理直播间礼物连击事件
  403. :param event: 礼物连击事件数据
  404. """
  405. gift_name = event["data"]["data"]["gift_name"]
  406. username = event["data"]["data"]["uname"]
  407. # 礼物数量
  408. combo_num = event["data"]["data"]["combo_num"]
  409. # 总金额
  410. combo_total_coin = event["data"]["data"]["combo_total_coin"]
  411. logging.info(f"用户:{username} 赠送 {combo_num} 个 {gift_name},总计 {combo_total_coin}电池")
  412. data = {
  413. "platform": "哔哩哔哩",
  414. "gift_name": gift_name,
  415. "username": username,
  416. "num": combo_num,
  417. "unit_price": combo_total_coin / combo_num / 1000,
  418. "total_price": combo_total_coin / 1000
  419. }
  420. my_handle.process_data(data, "gift")
  421. @room.on('SEND_GIFT')
  422. async def _(event):
  423. """
  424. 处理直播间礼物事件
  425. :param event: 礼物事件数据
  426. """
  427. # print(event)
  428. gift_name = event["data"]["data"]["giftName"]
  429. username = event["data"]["data"]["uname"]
  430. # 礼物数量
  431. num = event["data"]["data"]["num"]
  432. # 总金额
  433. combo_total_coin = event["data"]["data"]["combo_total_coin"]
  434. # 单个礼物金额
  435. discount_price = event["data"]["data"]["discount_price"]
  436. logging.info(f"用户:{username} 赠送 {num} 个 {gift_name},单价 {discount_price}电池,总计 {combo_total_coin}电池")
  437. data = {
  438. "platform": "哔哩哔哩",
  439. "gift_name": gift_name,
  440. "username": username,
  441. "num": num,
  442. "unit_price": discount_price / 1000,
  443. "total_price": combo_total_coin / 1000
  444. }
  445. my_handle.process_data(data, "gift")
  446. @room.on('GUARD_BUY')
  447. async def _(event):
  448. """
  449. 处理直播间续费大航海事件
  450. :param event: 续费大航海事件数据
  451. """
  452. logging.info(event)
  453. @room.on('SUPER_CHAT_MESSAGE')
  454. async def _(event):
  455. """
  456. 处理直播间醒目留言(SC)事件
  457. :param event: 醒目留言(SC)事件数据
  458. """
  459. message = event["data"]["data"]["message"]
  460. uname = event["data"]["data"]["user_info"]["uname"]
  461. price = event["data"]["data"]["price"]
  462. logging.info(f"用户:{uname} 发送 {price}元 SC:{message}")
  463. data = {
  464. "platform": "哔哩哔哩",
  465. "gift_name": "SC",
  466. "username": uname,
  467. "num": 1,
  468. "unit_price": price,
  469. "total_price": price,
  470. "content": message
  471. }
  472. my_handle.process_data(data, "gift")
  473. my_handle.process_data(data, "comment")
  474. @room.on('INTERACT_WORD')
  475. async def _(event):
  476. """
  477. 处理直播间用户进入直播间事件
  478. :param event: 用户进入直播间事件数据
  479. """
  480. global last_username_list
  481. username = event["data"]["data"]["uname"]
  482. logging.info(f"用户:{username} 进入直播间")
  483. # 添加用户名到最新的用户名列表
  484. add_username_to_last_username_list(username)
  485. data = {
  486. "platform": "哔哩哔哩",
  487. "username": username,
  488. "content": "进入直播间"
  489. }
  490. my_handle.process_data(data, "entrance")
  491. # @room.on('WELCOME')
  492. # async def _(event):
  493. # """
  494. # 处理直播间老爷进入房间事件
  495. # :param event: 老爷进入房间事件数据
  496. # """
  497. # print(event)
  498. # @room.on('WELCOME_GUARD')
  499. # async def _(event):
  500. # """
  501. # 处理直播间房管进入房间事件
  502. # :param event: 房管进入房间事件数据
  503. # """
  504. # print(event)
  505. try:
  506. # 启动 Bilibili 直播间连接
  507. sync(room.connect())
  508. except KeyboardInterrupt:
  509. logging.warning('程序被强行退出')
  510. finally:
  511. logging.warning('关闭连接...可能是直播间号配置有误或者其他原因导致的')
  512. os._exit(0)
  513. elif config.get("platform") == "bilibili2":
  514. try:
  515. import blivedm
  516. import blivedm.models.web as web_models
  517. import blivedm.models.open_live as open_models
  518. # 直播间ID的取值看直播间URL
  519. TEST_ROOM_IDS = [my_handle.get_room_id()]
  520. if config.get("bilibili", "login_type") == "cookie":
  521. bilibili_cookie = config.get("bilibili", "cookie")
  522. SESSDATA = common.parse_cookie_data(bilibili_cookie, "SESSDATA")
  523. elif config.get("bilibili", "login_type") == "open_live":
  524. # 在开放平台申请的开发者密钥 https://open-live.bilibili.com/open-manage
  525. ACCESS_KEY_ID = config.get("bilibili", "open_live", "ACCESS_KEY_ID")
  526. ACCESS_KEY_SECRET = config.get("bilibili", "open_live", "ACCESS_KEY_SECRET")
  527. # 在开放平台创建的项目ID
  528. APP_ID = config.get("bilibili", "open_live", "APP_ID")
  529. # 主播身份码 直播中心获取
  530. ROOM_OWNER_AUTH_CODE = config.get("bilibili", "open_live", "ROOM_OWNER_AUTH_CODE")
  531. except Exception as e:
  532. logging.error(traceback.format_exc())
  533. async def main_func():
  534. global session
  535. if config.get("bilibili", "login_type") == "open_live":
  536. await run_single_client2()
  537. else:
  538. try:
  539. init_session()
  540. await run_single_client()
  541. await run_multi_clients()
  542. finally:
  543. await session.close()
  544. def init_session():
  545. global session, SESSDATA
  546. cookies = http.cookies.SimpleCookie()
  547. cookies['SESSDATA'] = SESSDATA
  548. cookies['SESSDATA']['domain'] = 'bilibili.com'
  549. # logging.info(f"SESSDATA={SESSDATA}")
  550. session = aiohttp.ClientSession()
  551. session.cookie_jar.update_cookies(cookies)
  552. async def run_single_client():
  553. """
  554. 演示监听一个直播间
  555. """
  556. global session
  557. room_id = random.choice(TEST_ROOM_IDS)
  558. client = blivedm.BLiveClient(room_id, session=session)
  559. handler = MyHandler()
  560. client.set_handler(handler)
  561. client.start()
  562. try:
  563. # 演示5秒后停止
  564. await asyncio.sleep(5)
  565. client.stop()
  566. await client.join()
  567. finally:
  568. await client.stop_and_close()
  569. async def run_single_client2():
  570. """
  571. 演示监听一个直播间 开放平台
  572. """
  573. client = blivedm.OpenLiveClient(
  574. access_key_id=ACCESS_KEY_ID,
  575. access_key_secret=ACCESS_KEY_SECRET,
  576. app_id=APP_ID,
  577. room_owner_auth_code=ROOM_OWNER_AUTH_CODE,
  578. )
  579. handler = MyHandler2()
  580. client.set_handler(handler)
  581. client.start()
  582. try:
  583. # 演示70秒后停止
  584. # await asyncio.sleep(70)
  585. # client.stop()
  586. await client.join()
  587. finally:
  588. await client.stop_and_close()
  589. async def run_multi_clients():
  590. """
  591. 演示同时监听多个直播间
  592. """
  593. global session
  594. clients = [blivedm.BLiveClient(room_id, session=session) for room_id in TEST_ROOM_IDS]
  595. handler = MyHandler()
  596. for client in clients:
  597. client.set_handler(handler)
  598. client.start()
  599. try:
  600. await asyncio.gather(*(
  601. client.join() for client in clients
  602. ))
  603. finally:
  604. await asyncio.gather(*(
  605. client.stop_and_close() for client in clients
  606. ))
  607. class MyHandler(blivedm.BaseHandler):
  608. # 演示如何添加自定义回调
  609. _CMD_CALLBACK_DICT = blivedm.BaseHandler._CMD_CALLBACK_DICT.copy()
  610. # 入场消息回调
  611. def __interact_word_callback(self, client: blivedm.BLiveClient, command: dict):
  612. # logging.info(f"[{client.room_id}] INTERACT_WORD: self_type={type(self).__name__}, room_id={client.room_id},"
  613. # f" uname={command['data']['uname']}")
  614. global last_username_list
  615. username = command['data']['uname']
  616. logging.info(f"用户:{username} 进入直播间")
  617. # 添加用户名到最新的用户名列表
  618. add_username_to_last_username_list(username)
  619. data = {
  620. "platform": "哔哩哔哩2",
  621. "username": username,
  622. "content": "进入直播间"
  623. }
  624. my_handle.process_data(data, "entrance")
  625. _CMD_CALLBACK_DICT['INTERACT_WORD'] = __interact_word_callback # noqa
  626. def _on_heartbeat(self, client: blivedm.BLiveClient, message: web_models.HeartbeatMessage):
  627. logging.debug(f'[{client.room_id}] 心跳')
  628. def _on_danmaku(self, client: blivedm.BLiveClient, message: web_models.DanmakuMessage):
  629. global global_idle_time
  630. # 闲时计数清零
  631. global_idle_time = 0
  632. # logging.info(f'[{client.room_id}] {message.uname}:{message.msg}')
  633. content = message.msg # 获取弹幕内容
  634. username = message.uname # 获取发送弹幕的用户昵称
  635. logging.info(f"[{username}]: {content}")
  636. data = {
  637. "platform": "哔哩哔哩2",
  638. "username": username,
  639. "content": content
  640. }
  641. my_handle.process_data(data, "comment")
  642. def _on_gift(self, client: blivedm.BLiveClient, message: web_models.GiftMessage):
  643. # logging.info(f'[{client.room_id}] {message.uname} 赠送{message.gift_name}x{message.num}'
  644. # f' ({message.coin_type}瓜子x{message.total_coin})')
  645. gift_name = message.gift_name
  646. username = message.uname
  647. # 礼物数量
  648. combo_num = message.num
  649. # 总金额
  650. combo_total_coin = message.total_coin
  651. logging.info(f"用户:{username} 赠送 {combo_num} 个 {gift_name},总计 {combo_total_coin}电池")
  652. data = {
  653. "platform": "哔哩哔哩2",
  654. "gift_name": gift_name,
  655. "username": username,
  656. "num": combo_num,
  657. "unit_price": combo_total_coin / combo_num / 1000,
  658. "total_price": combo_total_coin / 1000
  659. }
  660. my_handle.process_data(data, "gift")
  661. def _on_buy_guard(self, client: blivedm.BLiveClient, message: web_models.GuardBuyMessage):
  662. logging.info(f'[{client.room_id}] {message.username} 购买{message.gift_name}')
  663. def _on_super_chat(self, client: blivedm.BLiveClient, message: web_models.SuperChatMessage):
  664. # logging.info(f'[{client.room_id}] 醒目留言 ¥{message.price} {message.uname}:{message.message}')
  665. message = message.message
  666. uname = message.uname
  667. price = message.price
  668. logging.info(f"用户:{uname} 发送 {price}元 SC:{message}")
  669. data = {
  670. "platform": "哔哩哔哩2",
  671. "gift_name": "SC",
  672. "username": uname,
  673. "num": 1,
  674. "unit_price": price,
  675. "total_price": price,
  676. "content": message
  677. }
  678. my_handle.process_data(data, "gift")
  679. my_handle.process_data(data, "comment")
  680. class MyHandler2(blivedm.BaseHandler):
  681. def _on_heartbeat(self, client: blivedm.BLiveClient, message: web_models.HeartbeatMessage):
  682. logging.debug(f'[{client.room_id}] 心跳')
  683. def _on_open_live_danmaku(self, client: blivedm.OpenLiveClient, message: open_models.DanmakuMessage):
  684. global global_idle_time
  685. # 闲时计数清零
  686. global_idle_time = 0
  687. # logging.info(f'[{client.room_id}] {message.uname}:{message.msg}')
  688. content = message.msg # 获取弹幕内容
  689. username = message.uname # 获取发送弹幕的用户昵称
  690. logging.info(f"[{username}]: {content}")
  691. data = {
  692. "platform": "哔哩哔哩2",
  693. "username": username,
  694. "content": content
  695. }
  696. my_handle.process_data(data, "comment")
  697. def _on_open_live_gift(self, client: blivedm.OpenLiveClient, message: open_models.GiftMessage):
  698. gift_name = message.gift_name
  699. username = message.uname
  700. # 礼物数量
  701. combo_num = message.gift_num
  702. # 总金额
  703. combo_total_coin = message.price * message.gift_num
  704. logging.info(f"用户:{username} 赠送 {combo_num} 个 {gift_name},总计 {combo_total_coin}电池")
  705. data = {
  706. "platform": "哔哩哔哩2",
  707. "gift_name": gift_name,
  708. "username": username,
  709. "num": combo_num,
  710. "unit_price": combo_total_coin / combo_num / 1000,
  711. "total_price": combo_total_coin / 1000
  712. }
  713. my_handle.process_data(data, "gift")
  714. def _on_open_live_buy_guard(self, client: blivedm.OpenLiveClient, message: open_models.GuardBuyMessage):
  715. logging.info(f'[{client.room_id}] {message.user_info.uname} 购买 大航海等级={message.guard_level}')
  716. def _on_open_live_super_chat(
  717. self, client: blivedm.OpenLiveClient, message: open_models.SuperChatMessage
  718. ):
  719. print(f'[{message.room_id}] 醒目留言 ¥{message.rmb} {message.uname}:{message.message}')
  720. message = message.message
  721. uname = message.uname
  722. price = message.rmb
  723. logging.info(f"用户:{uname} 发送 {price}元 SC:{message}")
  724. data = {
  725. "platform": "哔哩哔哩2",
  726. "gift_name": "SC",
  727. "username": uname,
  728. "num": 1,
  729. "unit_price": price,
  730. "total_price": price,
  731. "content": message
  732. }
  733. my_handle.process_data(data, "gift")
  734. my_handle.process_data(data, "comment")
  735. def _on_open_live_super_chat_delete(
  736. self, client: blivedm.OpenLiveClient, message: open_models.SuperChatDeleteMessage
  737. ):
  738. logging.info(f'[直播间 {message.room_id}] 删除醒目留言 message_ids={message.message_ids}')
  739. def _on_open_live_like(self, client: blivedm.OpenLiveClient, message: open_models.LikeMessage):
  740. logging.info(f'用户:{message.uname} 点了个赞')
  741. asyncio.run(main_func())
  742. elif config.get("platform") == "douyu":
  743. import websockets
  744. async def on_message(websocket, path):
  745. global last_liveroom_data, last_username_list
  746. global global_idle_time
  747. async for message in websocket:
  748. # print(f"收到消息: {message}")
  749. # await websocket.send("服务器收到了你的消息: " + message)
  750. try:
  751. data_json = json.loads(message)
  752. # logging.debug(data_json)
  753. if data_json["type"] == "comment":
  754. # logging.info(data_json)
  755. # 闲时计数清零
  756. global_idle_time = 0
  757. username = data_json["username"]
  758. content = data_json["content"]
  759. logging.info(f'[📧直播间弹幕消息] [{username}]:{content}')
  760. data = {
  761. "platform": "斗鱼",
  762. "username": username,
  763. "content": content
  764. }
  765. my_handle.process_data(data, "comment")
  766. # 添加用户名到最新的用户名列表
  767. add_username_to_last_username_list(username)
  768. except Exception as e:
  769. logging.error(e)
  770. logging.error("数据解析错误!")
  771. continue
  772. async def ws_server():
  773. ws_url = "127.0.0.1"
  774. ws_port = 5000
  775. server = await websockets.serve(on_message, ws_url, ws_port)
  776. logging.info(f"WebSocket 服务器已在 {ws_url}:{ws_port} 启动")
  777. await server.wait_closed()
  778. asyncio.run(ws_server())
  779. elif config.get("platform") == "dy":
  780. import websocket
  781. def on_message(ws, message):
  782. global last_liveroom_data, last_username_list, config, config_path
  783. global global_idle_time
  784. message_json = json.loads(message)
  785. # logging.debug(message_json)
  786. if "Type" in message_json:
  787. type = message_json["Type"]
  788. data_json = json.loads(message_json["Data"])
  789. if type == 1:
  790. # 闲时计数清零
  791. global_idle_time = 0
  792. username = data_json["User"]["Nickname"]
  793. content = data_json["Content"]
  794. logging.info(f'[📧直播间弹幕消息] [{username}]:{content}')
  795. data = {
  796. "platform": "抖音",
  797. "username": username,
  798. "content": content
  799. }
  800. my_handle.process_data(data, "comment")
  801. pass
  802. elif type == 2:
  803. username = data_json["User"]["Nickname"]
  804. count = data_json["Count"]
  805. logging.info(f'[👍直播间点赞消息] {username} 点了{count}赞')
  806. elif type == 3:
  807. username = data_json["User"]["Nickname"]
  808. logging.info(f'[🚹🚺直播间成员加入消息] 欢迎 {username} 进入直播间')
  809. data = {
  810. "platform": "抖音",
  811. "username": username,
  812. "content": "进入直播间"
  813. }
  814. # 添加用户名到最新的用户名列表
  815. add_username_to_last_username_list(username)
  816. my_handle.process_data(data, "entrance")
  817. elif type == 4:
  818. username = data_json["User"]["Nickname"]
  819. logging.info(f'[➕直播间关注消息] 感谢 {data_json["User"]["Nickname"]} 的关注')
  820. data = {
  821. "platform": "抖音",
  822. "username": username
  823. }
  824. my_handle.process_data(data, "follow")
  825. pass
  826. elif type == 5:
  827. gift_name = data_json["GiftName"]
  828. username = data_json["User"]["Nickname"]
  829. # 礼物数量
  830. num = data_json["GiftCount"]
  831. # 礼物重复数量
  832. repeat_count = data_json["RepeatCount"]
  833. try:
  834. # 暂时是写死的
  835. data_path = "data/抖音礼物价格表.json"
  836. # 读取JSON文件
  837. with open(data_path, "r", encoding="utf-8") as file:
  838. # 解析JSON数据
  839. data_json = json.load(file)
  840. if gift_name in data_json:
  841. # 单个礼物金额 需要自己维护礼物价值表
  842. discount_price = data_json[gift_name]
  843. else:
  844. logging.warning(f"数据文件:{data_path} 中,没有 {gift_name} 对应的价值,请手动补充数据")
  845. discount_price = 1
  846. except Exception as e:
  847. logging.error(traceback.format_exc())
  848. discount_price = 1
  849. # 总金额
  850. combo_total_coin = repeat_count * discount_price
  851. logging.info(f'[🎁直播间礼物消息] 用户:{username} 赠送 {num} 个 {gift_name},单价 {discount_price}抖币,总计 {combo_total_coin}抖币')
  852. data = {
  853. "platform": "抖音",
  854. "gift_name": gift_name,
  855. "username": username,
  856. "num": num,
  857. "unit_price": discount_price / 10,
  858. "total_price": combo_total_coin / 10
  859. }
  860. my_handle.process_data(data, "gift")
  861. elif type == 6:
  862. logging.info(f'[直播间数据] {data_json["Content"]}')
  863. # {'OnlineUserCount': 50, 'TotalUserCount': 22003, 'TotalUserCountStr': '2.2万', 'OnlineUserCountStr': '50',
  864. # 'MsgId': 7260517442466662207, 'User': None, 'Content': '当前直播间人数 50,累计直播间人数 2.2万', 'RoomId': 7260415920948906807}
  865. # print(f"data_json={data_json}")
  866. last_liveroom_data = data_json
  867. # 当前在线人数
  868. OnlineUserCount = data_json["OnlineUserCount"]
  869. try:
  870. # 是否开启了动态配置功能
  871. if config.get("trends_config", "enable"):
  872. for path_config in config.get("trends_config", "path"):
  873. online_num_min = int(path_config["online_num"].split("-")[0])
  874. online_num_max = int(path_config["online_num"].split("-")[1])
  875. # 判断在线人数是否在此范围内
  876. if OnlineUserCount >= online_num_min and OnlineUserCount <= online_num_max:
  877. logging.debug(f"当前配置文件:{path_config['path']}")
  878. # 如果配置文件相同,则跳过
  879. if config_path == path_config["path"]:
  880. break
  881. config_path = path_config["path"]
  882. config = Config(config_path)
  883. my_handle.reload_config(config_path)
  884. logging.info(f"切换配置文件:{config_path}")
  885. break
  886. except Exception as e:
  887. logging.error(traceback.format_exc())
  888. pass
  889. elif type == 8:
  890. logging.info(f'[分享直播间] 感谢 {data_json["User"]["Nickname"]} 分享了直播间')
  891. pass
  892. def on_error(ws, error):
  893. logging.error("Error:", error)
  894. def on_close(ws):
  895. logging.debug("WebSocket connection closed")
  896. def on_open(ws):
  897. logging.debug("WebSocket connection established")
  898. try:
  899. # WebSocket连接URL
  900. ws_url = "ws://127.0.0.1:8888"
  901. logging.info(f"监听地址:{ws_url}")
  902. # 不设置日志等级
  903. websocket.enableTrace(False)
  904. # 创建WebSocket连接
  905. ws = websocket.WebSocketApp(ws_url,
  906. on_message=on_message,
  907. on_error=on_error,
  908. on_close=on_close,
  909. on_open=on_open)
  910. # 运行WebSocket连接
  911. ws.run_forever()
  912. except KeyboardInterrupt:
  913. logging.warning('程序被强行退出')
  914. finally:
  915. logging.info('关闭连接...可能是直播间不存在或下播或网络问题')
  916. os._exit(0)
  917. elif config.get("platform") == "ks":
  918. from playwright.sync_api import sync_playwright
  919. from google.protobuf.json_format import MessageToDict
  920. from configparser import ConfigParser
  921. import kuaishou_pb2
  922. class kslive(object):
  923. def __init__(self):
  924. global config, common, my_handle
  925. self.path = os.path.abspath('')
  926. self.chrome_path = r"\firefox-1419\firefox\firefox.exe"
  927. self.ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0'
  928. self.uri = 'https://live.kuaishou.com/u/'
  929. self.context = None
  930. self.browser = None
  931. self.page = None
  932. try:
  933. self.live_ids = config.get("room_display_id")
  934. self.thread = 2
  935. # 没什么用的手机号配置,也就方便登录
  936. self.phone = "123"
  937. except Exception as e:
  938. logging.error(traceback.format_exc())
  939. logging.error("请检查配置文件")
  940. exit()
  941. def find_file(self, find_path, file_type) -> list:
  942. """
  943. 寻找文件
  944. :param find_path: 子路径
  945. :param file_type: 文件类型
  946. :return:
  947. """
  948. path = self.path + "\\" + find_path
  949. data_list = []
  950. for root, dirs, files in os.walk(path):
  951. if root != path:
  952. break
  953. for file in files:
  954. file_path = os.path.join(root, file)
  955. if file_path.find(file_type) != -1:
  956. data_list.append(file_path)
  957. return data_list
  958. def main(self, lid, semaphore):
  959. if not os.path.exists(self.path + "\\cookie"):
  960. os.makedirs(self.path + "\\cookie")
  961. cookie_path=self.path + "\\cookie\\" + self.phone + ".json"
  962. # if not os.path.exists(cookie_path):
  963. # with open(cookie_path, 'w') as file:
  964. # file.write('{"a":"a"}')
  965. # logging.info(f"'{cookie_path}' 创建成功")
  966. # else:
  967. # logging.info(f"'{cookie_path}' 已存在,无需创建")
  968. with semaphore:
  969. thread_name = threading.current_thread().name.split("-")[0]
  970. with sync_playwright() as p:
  971. self.browser = p.firefox.launch(headless=False)
  972. # executable_path=self.path + self.chrome_path
  973. cookie_list = self.find_file("cookie", "json")
  974. if not os.path.exists(cookie_path):
  975. self.context = self.browser.new_context(storage_state=None, user_agent=self.ua)
  976. else:
  977. self.context = self.browser.new_context(storage_state=cookie_list[0], user_agent=self.ua)
  978. self.page = self.context.new_page()
  979. self.page.add_init_script("Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});")
  980. self.page.goto("https://live.kuaishou.com/")
  981. element = self.page.get_attribute('.no-login', "style")
  982. if not element:
  983. self.page.locator('.login').click()
  984. self.page.locator('li.tab-panel:nth-child(2) > h4:nth-child(1)').click()
  985. self.page.locator(
  986. 'div.normal-login-item:nth-child(1) > div:nth-child(1) > input:nth-child(1)').fill(
  987. self.phone)
  988. try:
  989. self.page.wait_for_selector("#app > section > div.header-placeholder > header > div.header-main > "
  990. "div.right-part > div.user-info > div.tooltip-trigger > span",
  991. timeout=1000 * 60 * 2)
  992. if not os.path.exists(self.path + "\\cookie"):
  993. os.makedirs(self.path + "\\cookie")
  994. self.context.storage_state(path=cookie_path)
  995. # 检测是否开播
  996. selector = "html body div#app div.live-room div.detail div.player " \
  997. "div.kwai-player.kwai-player-container.kwai-player-rotation-0 " \
  998. "div.kwai-player-container-video div.kwai-player-plugins div.center-state div.state " \
  999. "div.no-live-detail div.desc p.tip" # 检测正在直播时下播的选择器
  1000. try:
  1001. msg = self.page.locator(selector).text_content(timeout=3000)
  1002. logging.info("当前%s" % thread_name + "," + msg)
  1003. self.context.close()
  1004. self.browser.close()
  1005. except Exception as e:
  1006. logging.info("当前%s,[%s]正在直播" % (thread_name, lid))
  1007. self.page.goto(self.uri + lid)
  1008. self.page.on("websocket", self.web_sockets)
  1009. self.page.wait_for_selector(selector, timeout=86400000)
  1010. logging.error("当前%s,[%s]的直播结束了" % (thread_name, lid))
  1011. self.context.close()
  1012. self.browser.close()
  1013. except Exception:
  1014. logging.info("登录失败")
  1015. self.context.close()
  1016. self.browser.close()
  1017. def web_sockets(self, web_socket):
  1018. logging.info("web_sockets...")
  1019. urls = web_socket.url
  1020. logging.info(urls)
  1021. if '/websocket' in urls:
  1022. web_socket.on("close", self.websocket_close)
  1023. web_socket.on("framereceived", self.handler)
  1024. def websocket_close(self):
  1025. self.context.close()
  1026. self.browser.close()
  1027. def handler(self, websocket):
  1028. global global_idle_time
  1029. Message = kuaishou_pb2.SocketMessage()
  1030. Message.ParseFromString(websocket)
  1031. if Message.payloadType == 310:
  1032. SCWebFeedPUsh = kuaishou_pb2.SCWebFeedPush()
  1033. SCWebFeedPUsh.ParseFromString(Message.payload)
  1034. obj = MessageToDict(SCWebFeedPUsh, preserving_proto_field_name=True)
  1035. logging.debug(obj)
  1036. if obj.get('commentFeeds', ''):
  1037. msg_list = obj.get('commentFeeds', '')
  1038. for i in msg_list:
  1039. # 闲时计数清零
  1040. global_idle_time = 0
  1041. username = i['user']['userName']
  1042. pid = i['user']['principalId']
  1043. content = i['content']
  1044. logging.info(f"[📧直播间弹幕消息] [{username}]:{content}")
  1045. data = {
  1046. "platform": "快手",
  1047. "username": username,
  1048. "content": content
  1049. }
  1050. my_handle.process_data(data, "comment")
  1051. if obj.get('giftFeeds', ''):
  1052. msg_list = obj.get('giftFeeds', '')
  1053. for i in msg_list:
  1054. username = i['user']['userName']
  1055. # pid = i['user']['principalId']
  1056. giftId = i['giftId']
  1057. comboCount = i['comboCount']
  1058. logging.info(f"[🎁直播间礼物消息] 用户:{username} 赠送礼物Id={giftId} 连击数={comboCount}")
  1059. if obj.get('likeFeeds', ''):
  1060. msg_list = obj.get('likeFeeds', '')
  1061. for i in msg_list:
  1062. username = i['user']['userName']
  1063. pid = i['user']['principalId']
  1064. logging.info(f"{username}")
  1065. class run(kslive):
  1066. def __init__(self):
  1067. super().__init__()
  1068. self.ids_list = self.live_ids.split(",")
  1069. def run_live(self):
  1070. """
  1071. 主程序入口
  1072. :return:
  1073. """
  1074. t_list = []
  1075. # 允许的最大线程数
  1076. if self.thread < 1:
  1077. self.thread = 1
  1078. elif self.thread > 8:
  1079. self.thread = 8
  1080. logging.info("线程最大允许8,线程数最好设置cpu核心数")
  1081. semaphore = threading.Semaphore(self.thread)
  1082. # 用于记录数量
  1083. n = 0
  1084. if not self.live_ids:
  1085. logging.info("请导入网页直播id,多个以','间隔")
  1086. return
  1087. for i in self.ids_list:
  1088. n += 1
  1089. t = threading.Thread(target=kslive().main, args=(i, semaphore), name=f"线程:{n}-{i}")
  1090. t.start()
  1091. t_list.append(t)
  1092. for i in t_list:
  1093. i.join()
  1094. run().run_live()
  1095. elif config.get("platform") == "talk":
  1096. import keyboard
  1097. import pyaudio
  1098. import wave
  1099. import numpy as np
  1100. import speech_recognition as sr
  1101. from aip import AipSpeech
  1102. import signal
  1103. # 冷却时间 0.3 秒
  1104. cooldown = 0.3
  1105. last_pressed = 0
  1106. stop_do_listen_and_comment_thread_event = threading.Event()
  1107. # signal.signal(signal.SIGINT, exit_handler)
  1108. # signal.signal(signal.SIGTERM, exit_handler)
  1109. # 录音功能(录音时间过短进入openai的语音转文字会报错,请一定注意)
  1110. def record_audio():
  1111. pressdown_num = 0
  1112. CHUNK = 1024
  1113. FORMAT = pyaudio.paInt16
  1114. CHANNELS = 1
  1115. RATE = 44100
  1116. WAVE_OUTPUT_FILENAME = "out/record.wav"
  1117. p = pyaudio.PyAudio()
  1118. stream = p.open(format=FORMAT,
  1119. channels=CHANNELS,
  1120. rate=RATE,
  1121. input=True,
  1122. frames_per_buffer=CHUNK)
  1123. frames = []
  1124. print("Recording...")
  1125. flag = 0
  1126. while 1:
  1127. while keyboard.is_pressed('RIGHT_SHIFT'):
  1128. flag = 1
  1129. data = stream.read(CHUNK)
  1130. frames.append(data)
  1131. pressdown_num = pressdown_num + 1
  1132. if flag:
  1133. break
  1134. print("Stopped recording.")
  1135. stream.stop_stream()
  1136. stream.close()
  1137. p.terminate()
  1138. wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
  1139. wf.setnchannels(CHANNELS)
  1140. wf.setsampwidth(p.get_sample_size(FORMAT))
  1141. wf.setframerate(RATE)
  1142. wf.writeframes(b''.join(frames))
  1143. wf.close()
  1144. if pressdown_num >= 5: # 粗糙的处理手段
  1145. return 1
  1146. else:
  1147. print("杂鱼杂鱼,好短好短(录音时间过短,按右shift重新录制)")
  1148. return 0
  1149. # THRESHOLD 设置音量阈值,默认值800.0,根据实际情况调整 silence_threshold 设置沉默阈值,根据实际情况调整
  1150. def audio_listen(volume_threshold=800.0, silence_threshold=15):
  1151. audio = pyaudio.PyAudio()
  1152. # 设置音频参数
  1153. FORMAT = pyaudio.paInt16
  1154. CHANNELS = 1
  1155. RATE = 16000
  1156. CHUNK = 1024
  1157. stream = audio.open(
  1158. format=FORMAT,
  1159. channels=CHANNELS,
  1160. rate=RATE,
  1161. input=True,
  1162. frames_per_buffer=CHUNK,
  1163. input_device_index=int(config.get("talk", "device_index"))
  1164. )
  1165. frames = [] # 存储录制的音频帧
  1166. is_speaking = False # 是否在说话
  1167. silent_count = 0 # 沉默计数
  1168. speaking_flag = False #录入标志位 不重要
  1169. while True:
  1170. # 读取音频数据
  1171. data = stream.read(CHUNK)
  1172. audio_data = np.frombuffer(data, dtype=np.short)
  1173. max_dB = np.max(audio_data)
  1174. # print(max_dB)
  1175. if max_dB > volume_threshold:
  1176. is_speaking = True
  1177. silent_count = 0
  1178. elif is_speaking is True:
  1179. silent_count += 1
  1180. if is_speaking is True:
  1181. frames.append(data)
  1182. if speaking_flag is False:
  1183. logging.info("[录入中……]")
  1184. speaking_flag = True
  1185. if silent_count >= silence_threshold:
  1186. break
  1187. logging.info("[语音录入完成]")
  1188. # 将音频保存为WAV文件
  1189. '''with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
  1190. wf.setnchannels(CHANNELS)
  1191. wf.setsampwidth(pyaudio.get_sample_size(FORMAT))
  1192. wf.setframerate(RATE)
  1193. wf.writeframes(b''.join(frames))'''
  1194. return frames
  1195. # 执行录音、识别&提交
  1196. def do_listen_and_comment(status=True):
  1197. global stop_do_listen_and_comment_thread_event
  1198. while True:
  1199. # 检查是否收到停止事件
  1200. if stop_do_listen_and_comment_thread_event.is_set():
  1201. logging.info(f'停止录音~')
  1202. break
  1203. config = Config(config_path)
  1204. # 根据接入的语音识别类型执行
  1205. if "baidu" == config.get("talk", "type"):
  1206. # 设置音频参数
  1207. FORMAT = pyaudio.paInt16
  1208. CHANNELS = 1
  1209. RATE = 16000
  1210. audio_out_path = config.get("play_audio", "out_path")
  1211. if not os.path.isabs(audio_out_path):
  1212. if not audio_out_path.startswith('./'):
  1213. audio_out_path = './' + audio_out_path
  1214. file_name = 'baidu_' + common.get_bj_time(4) + '.wav'
  1215. WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name)
  1216. # WAVE_OUTPUT_FILENAME = './out/baidu_' + common.get_bj_time(4) + '.wav'
  1217. frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold"))
  1218. # 将音频保存为WAV文件
  1219. with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
  1220. wf.setnchannels(CHANNELS)
  1221. wf.setsampwidth(pyaudio.get_sample_size(FORMAT))
  1222. wf.setframerate(RATE)
  1223. wf.writeframes(b''.join(frames))
  1224. # 读取音频文件
  1225. with open(WAVE_OUTPUT_FILENAME, 'rb') as fp:
  1226. audio = fp.read()
  1227. # 初始化 AipSpeech 对象
  1228. baidu_client = AipSpeech(config.get("talk", "baidu", "app_id"), config.get("talk", "baidu", "api_key"), config.get("talk", "baidu", "secret_key"))
  1229. # 识别音频文件
  1230. res = baidu_client.asr(audio, 'wav', 16000, {
  1231. 'dev_pid': 1536,
  1232. })
  1233. if res['err_no'] == 0:
  1234. content = res['result'][0]
  1235. # 输出识别结果
  1236. logging.info("识别结果:" + content)
  1237. username = config.get("talk", "username")
  1238. data = {
  1239. "platform": "本地聊天",
  1240. "username": username,
  1241. "content": content
  1242. }
  1243. my_handle.process_data(data, "talk")
  1244. else:
  1245. logging.error(f"百度接口报错:{res}")
  1246. elif "google" == config.get("talk", "type"):
  1247. # 创建Recognizer对象
  1248. r = sr.Recognizer()
  1249. try:
  1250. # 打开麦克风进行录音
  1251. with sr.Microphone() as source:
  1252. logging.info(f'录音中...')
  1253. # 从麦克风获取音频数据
  1254. audio = r.listen(source)
  1255. logging.info("成功录制")
  1256. # 进行谷歌实时语音识别 en-US zh-CN ja-JP
  1257. content = r.recognize_google(audio, language=config.get("talk", "google", "tgt_lang"))
  1258. # 输出识别结果
  1259. # logging.info("识别结果:" + content)
  1260. username = config.get("talk", "username")
  1261. data = {
  1262. "platform": "本地聊天",
  1263. "username": username,
  1264. "content": content
  1265. }
  1266. my_handle.process_data(data, "talk")
  1267. except sr.UnknownValueError:
  1268. logging.warning("无法识别输入的语音")
  1269. except sr.RequestError as e:
  1270. logging.error("请求出错:" + str(e))
  1271. if not status:
  1272. return
  1273. def on_key_press(event):
  1274. global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event
  1275. # if event.name in ['z', 'Z', 'c', 'C'] and keyboard.is_pressed('ctrl'):
  1276. # print("退出程序")
  1277. # os._exit(0)
  1278. # 按键CD
  1279. current_time = time.time()
  1280. if current_time - last_pressed < cooldown:
  1281. return
  1282. """
  1283. 触发按键部分的判断
  1284. """
  1285. trigger_key_lower = None
  1286. stop_trigger_key_lower = None
  1287. # trigger_key是字母, 整个小写
  1288. if trigger_key.isalpha():
  1289. trigger_key_lower = trigger_key.lower()
  1290. # stop_trigger_key是字母, 整个小写
  1291. if stop_trigger_key.isalpha():
  1292. stop_trigger_key_lower = stop_trigger_key.lower()
  1293. if trigger_key_lower:
  1294. if event.name == trigger_key or event.name == trigger_key_lower:
  1295. logging.info(f'检测到单击键盘 {event.name},即将开始录音~')
  1296. elif event.name == stop_trigger_key or event.name == stop_trigger_key_lower:
  1297. logging.info(f'检测到单击键盘 {event.name},即将停止录音~')
  1298. stop_do_listen_and_comment_thread_event.set()
  1299. return
  1300. else:
  1301. return
  1302. else:
  1303. if event.name == trigger_key:
  1304. logging.info(f'检测到单击键盘 {event.name},即将开始录音~')
  1305. elif event.name == stop_trigger_key:
  1306. logging.info(f'检测到单击键盘 {event.name},即将停止录音~')
  1307. stop_do_listen_and_comment_thread_event.set()
  1308. return
  1309. else:
  1310. return
  1311. # 是否启用连续对话模式
  1312. if config.get("talk", "continuous_talk"):
  1313. stop_do_listen_and_comment_thread_event.clear()
  1314. do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(True,))
  1315. do_listen_and_comment_thread.start()
  1316. else:
  1317. stop_do_listen_and_comment_thread_event.clear()
  1318. do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(False,))
  1319. do_listen_and_comment_thread.start()
  1320. # 按键监听
  1321. def key_listener():
  1322. # 注册按键按下事件的回调函数
  1323. keyboard.on_press(on_key_press)
  1324. try:
  1325. # 进入监听状态,等待按键按下
  1326. keyboard.wait()
  1327. except KeyboardInterrupt:
  1328. os._exit(0)
  1329. # 从配置文件中读取触发键的字符串配置
  1330. trigger_key = config.get("talk", "trigger_key")
  1331. stop_trigger_key = config.get("talk", "stop_trigger_key")
  1332. logging.info(f'单击键盘 {trigger_key} 按键进行录音喵~ 由于其他任务还要启动,如果按键没有反应,请等待一段时间')
  1333. # 创建并启动按键监听线程
  1334. thread = threading.Thread(target=key_listener)
  1335. thread.start()
  1336. elif config.get("platform") == "twitch":
  1337. import socks
  1338. from emoji import demojize
  1339. try:
  1340. server = 'irc.chat.twitch.tv'
  1341. port = 6667
  1342. nickname = '主人'
  1343. try:
  1344. channel = '#' + config.get("room_display_id") # 要从中检索消息的频道,注意#必须携带在头部 The channel you want to retrieve messages from
  1345. token = config.get("twitch", "token") # 访问 https://twitchapps.com/tmi/ 获取
  1346. user = config.get("twitch", "user") # 你的Twitch用户名 Your Twitch username
  1347. # 代理服务器的地址和端口
  1348. proxy_server = config.get("twitch", "proxy_server")
  1349. proxy_port = int(config.get("twitch", "proxy_port"))
  1350. except Exception as e:
  1351. logging.error("获取Twitch配置失败!\n{0}".format(e))
  1352. # 配置代理服务器
  1353. socks.set_default_proxy(socks.HTTP, proxy_server, proxy_port)
  1354. # 创建socket对象
  1355. sock = socks.socksocket()
  1356. try:
  1357. sock.connect((server, port))
  1358. logging.info("成功连接 Twitch IRC server")
  1359. except Exception as e:
  1360. logging.error(f"连接 Twitch IRC server 失败: {e}")
  1361. sock.send(f"PASS {token}\n".encode('utf-8'))
  1362. sock.send(f"NICK {nickname}\n".encode('utf-8'))
  1363. sock.send(f"JOIN {channel}\n".encode('utf-8'))
  1364. regex = r":(\w+)!\w+@\w+\.tmi\.twitch\.tv PRIVMSG #\w+ :(.+)"
  1365. # 重连次数
  1366. retry_count = 0
  1367. while True:
  1368. try:
  1369. resp = sock.recv(2048).decode('utf-8')
  1370. # 输出所有接收到的内容,包括PING/PONG
  1371. # logging.info(resp)
  1372. if resp.startswith('PING'):
  1373. sock.send("PONG\n".encode('utf-8'))
  1374. elif not user in resp:
  1375. # 闲时计数清零
  1376. global_idle_time = 0
  1377. resp = demojize(resp)
  1378. logging.debug(resp)
  1379. match = re.match(regex, resp)
  1380. username = match.group(1)
  1381. content = match.group(2)
  1382. content = content.rstrip()
  1383. logging.info(f"[{username}]: {content}")
  1384. data = {
  1385. "platform": "twitch",
  1386. "username": username,
  1387. "content": content
  1388. }
  1389. my_handle.process_data(data, "comment")
  1390. except AttributeError as e:
  1391. logging.error(f"捕获到异常: {e}")
  1392. logging.error("发生异常,重新连接socket")
  1393. if retry_count >= 3:
  1394. logging.error(f"多次重连失败,程序结束!")
  1395. return
  1396. retry_count += 1
  1397. logging.error(f"重试次数: {retry_count}")
  1398. # 在这里添加重新连接socket的代码
  1399. # 例如,你可能想要关闭旧的socket连接,然后重新创建一个新的socket连接
  1400. sock.close()
  1401. # 创建socket对象
  1402. sock = socks.socksocket()
  1403. try:
  1404. sock.connect((server, port))
  1405. logging.info("成功连接 Twitch IRC server")
  1406. except Exception as e:
  1407. logging.error(f"连接 Twitch IRC server 失败: {e}")
  1408. sock.send(f"PASS {token}\n".encode('utf-8'))
  1409. sock.send(f"NICK {nickname}\n".encode('utf-8'))
  1410. sock.send(f"JOIN {channel}\n".encode('utf-8'))
  1411. except Exception as e:
  1412. logging.error("Error receiving chat: {0}".format(e))
  1413. except Exception as e:
  1414. logging.error(traceback.format_exc())
  1415. elif config.get("platform") == "youtube":
  1416. import pytchat
  1417. try:
  1418. try:
  1419. video_id = config.get("room_display_id")
  1420. except Exception as e:
  1421. logging.error("获取直播间号失败!\n{0}".format(e))
  1422. live = pytchat.create(video_id=video_id)
  1423. while live.is_alive():
  1424. try:
  1425. for c in live.get().sync_items():
  1426. # 过滤表情包
  1427. chat_raw = re.sub(r':[^\s]+:', '', c.message)
  1428. chat_raw = chat_raw.replace('#', '')
  1429. if chat_raw != '':
  1430. # 闲时计数清零
  1431. global_idle_time = 0
  1432. # chat_author makes the chat look like this: "Nightbot: Hello". So the assistant can respond to the user's name
  1433. # chat = '[' + c.author.name + ']: ' + chat_raw
  1434. # logging.info(chat)
  1435. content = chat_raw # 获取弹幕内容
  1436. username = c.author.name # 获取发送弹幕的用户昵称
  1437. logging.info(f"[{username}]: {content}")
  1438. data = {
  1439. "platform": "YouTube",
  1440. "username": username,
  1441. "content": content
  1442. }
  1443. my_handle.process_data(data, "comment")
  1444. # time.sleep(1)
  1445. except Exception as e:
  1446. logging.error("Error receiving chat: {0}".format(e))
  1447. except KeyboardInterrupt:
  1448. logging.warning('程序被强行退出')
  1449. finally:
  1450. logging.warning('关闭连接...')
  1451. os._exit(0)
  1452. while not sub_thread_exit_events[0].is_set():
  1453. # 等待事件被设置或超时,每次检查之间暂停1秒
  1454. sub_thread_exit_events[0].wait(1)
  1455. # 关闭所有子线程
  1456. for event in sub_thread_exit_events:
  1457. event.set()
  1458. for t in sub_threads:
  1459. t.join()
  1460. logging.info("start_server子线程退出")
  1461. # 退出程序
  1462. def exit_handler(signum, frame):
  1463. logging.info("收到信号:", signum)
  1464. if __name__ == '__main__':
  1465. os.environ['GEVENT_SUPPORT'] = 'True'
  1466. port = 8082
  1467. password = "中文的密码,怕了吧!"
  1468. app = Flask(__name__, static_folder='static')
  1469. CORS(app) # 允许跨域请求
  1470. socketio = SocketIO(app, cors_allowed_origins="*")
  1471. @app.route('/static/<path:filename>')
  1472. def static_files(filename):
  1473. return send_from_directory(app.static_folder, filename)
  1474. sub_thread_exit_events = [threading.Event() for _ in range(4)] # 为每个子线程创建退出事件
  1475. """
  1476. 通用函数
  1477. """
  1478. def restart_application():
  1479. """
  1480. 重启
  1481. """
  1482. try:
  1483. # 获取当前 Python 解释器的可执行文件路径
  1484. python_executable = sys.executable
  1485. # 获取当前脚本的文件路径
  1486. script_file = os.path.abspath(__file__)
  1487. # 重启当前程序
  1488. os.execv(python_executable, ['python', script_file])
  1489. except Exception as e:
  1490. logging.error(traceback.format_exc())
  1491. return {"code": -1, "msg": f"重启失败!{e}"}
  1492. # 创建一个函数,用于运行外部程序
  1493. def run_external_program(config_path):
  1494. global running_flag, running_process
  1495. if running_flag:
  1496. return {"code": 1, "msg": "运行中,请勿重复运行"}
  1497. try:
  1498. running_flag = True
  1499. thread = threading.Thread(target=start_server, args=(config_path, sub_thread_exit_events,))
  1500. thread.start()
  1501. # thread.join()
  1502. logging.info("程序开始运行")
  1503. return {"code": 200, "msg": "程序开始运行"}
  1504. except Exception as e:
  1505. logging.error(traceback.format_exc())
  1506. running_flag = False
  1507. return {"code": -1, "msg": f"运行失败!{e}"}
  1508. # 定义一个函数,用于停止正在运行的程序
  1509. def stop_external_program():
  1510. global running_flag, running_process
  1511. if running_flag:
  1512. try:
  1513. # 通知子线程退出
  1514. sub_thread_exit_events[0].set()
  1515. running_flag = False
  1516. logging.info("程序已停止")
  1517. return {"code": 200, "msg": "停止成功"}
  1518. except Exception as e:
  1519. logging.error(traceback.format_exc())
  1520. return {"code": -1, "msg": f"停止失败!{e}"}
  1521. # 恢复出厂配置
  1522. def factory(src_path, dst_path):
  1523. try:
  1524. with open(src_path, 'r', encoding="utf-8") as source:
  1525. with open(dst_path, 'w', encoding="utf-8") as destination:
  1526. destination.write(source.read())
  1527. logging.info("恢复出厂配置成功!")
  1528. return {"code": 200, "msg": "恢复出厂配置成功!"}
  1529. except Exception as e:
  1530. logging.error(traceback.format_exc())
  1531. return {"code": -1, "msg": f"恢复出厂配置失败!\n{e}"}
  1532. # def check_password(data_json, ip):
  1533. # try:
  1534. # if data_json["password"] == password:
  1535. # return True
  1536. # else:
  1537. # return False
  1538. # except Exception as e:
  1539. # logging.error(f"[{ip}] 密码校验失败!{e}")
  1540. # return False
  1541. """
  1542. 配置config
  1543. config_path 配置文件路径(默认相对路径)
  1544. data 传入的json将被写入配置文件
  1545. data_json = {
  1546. "config_path": "config.json",
  1547. "data": {
  1548. "key": "value"
  1549. }
  1550. }
  1551. return:
  1552. {"code": 200, "msg": "成功"}
  1553. {"code": -1, "msg": "失败"}
  1554. """
  1555. @app.route('/set_config', methods=['POST'])
  1556. def set_config():
  1557. """
  1558. {
  1559. "config_path": "config.json",
  1560. "data": {
  1561. "platform": "bilibili"
  1562. }
  1563. }
  1564. """
  1565. try:
  1566. data_json = request.get_json()
  1567. logging.info(f'收到数据:{data_json}')
  1568. # 打开JSON文件
  1569. with open(data_json['config_path'], 'r+', encoding='utf-8') as file:
  1570. # 读取文件内容
  1571. data = json.load(file)
  1572. # 遍历 data_json 并更新或添加到 data
  1573. for key, value in data_json['data'].items():
  1574. data[key] = value
  1575. # 将文件指针移动到文件开头
  1576. file.seek(0)
  1577. # 将修改后的数据写回文件
  1578. json.dump(data, file, ensure_ascii=False, indent=2)
  1579. # 截断文件
  1580. file.truncate()
  1581. logging.info(f'配置更新成功!')
  1582. return jsonify({"code": 200, "msg": "配置更新成功!"})
  1583. except Exception as e:
  1584. logging.error(traceback.format_exc())
  1585. return jsonify({"code": -1, "msg": f"配置更新失败!{e}"})
  1586. """
  1587. 系统命令
  1588. type 命令类型(run/stop/restart/factory)
  1589. data 传入的json
  1590. data_json = {
  1591. "type": "命令名",
  1592. "data": {
  1593. "key": "value"
  1594. }
  1595. }
  1596. return:
  1597. {"code": 200, "msg": "成功"}
  1598. {"code": -1, "msg": "失败"}
  1599. """
  1600. @app.route('/sys_cmd', methods=['POST'])
  1601. def sys_cmd():
  1602. try:
  1603. data_json = request.get_json()
  1604. logging.info(f'收到数据:{data_json}')
  1605. logging.info(f"开始执行 {data_json['type']}命令...")
  1606. resp_json = {}
  1607. if data_json['type'] == 'run':
  1608. """
  1609. {
  1610. "type": "run",
  1611. "data": {
  1612. "config_path": "config.json"
  1613. }
  1614. }
  1615. """
  1616. # 运行
  1617. resp_json = run_external_program(data_json['data']['config_path'])
  1618. elif data_json['type'] =='stop':
  1619. """
  1620. {
  1621. "type": "stop",
  1622. "data": {
  1623. "config_path": "config.json"
  1624. }
  1625. }
  1626. """
  1627. # 停止
  1628. resp_json = stop_external_program()
  1629. elif data_json['type'] =='restart':
  1630. """
  1631. {
  1632. "type": "factory",
  1633. "data": {
  1634. "config_path": "config.json"
  1635. }
  1636. }
  1637. """
  1638. # 重启
  1639. resp_json = restart_application()
  1640. elif data_json['type'] =='factory':
  1641. """
  1642. {
  1643. "type": "factory",
  1644. "data": {
  1645. "src_path": "config.json.bak",
  1646. "dst_path": "config.json"
  1647. }
  1648. }
  1649. """
  1650. # 恢复出厂
  1651. resp_json = factory(data_json['data']['src_path'], data_json['data']['dst_path'])
  1652. return jsonify(resp_json)
  1653. except Exception as e:
  1654. logging.error(traceback.format_exc())
  1655. return jsonify({"code": -1, "msg": f"{data_json['type']}执行失败!{e}"})
  1656. """
  1657. 发送数据
  1658. type 数据类型(comment/gift/entrance/reread/tuning/...)
  1659. data 传入的json,根据数据类型自行适配
  1660. data_json = {
  1661. "type": "数据类型",
  1662. "data": {
  1663. "key": "value"
  1664. }
  1665. }
  1666. return:
  1667. {"code": 200, "msg": "成功"}
  1668. {"code": -1, "msg": "失败"}
  1669. """
  1670. @app.route('/send', methods=['POST'])
  1671. def send():
  1672. global my_handle, config
  1673. try:
  1674. try:
  1675. data_json = request.get_json()
  1676. logging.info(f"send收到数据:{data_json}")
  1677. if my_handle is None:
  1678. return jsonify({"code": -1, "msg": f"系统还没运行,请先运行后再发送数据!"})
  1679. if data_json["type"] == "reread":
  1680. """
  1681. {
  1682. "type": "reread",
  1683. "data": {
  1684. "platform": "哔哩哔哩",
  1685. "username": "用户名",
  1686. "content": "弹幕内容"
  1687. }
  1688. }
  1689. """
  1690. my_handle.reread_handle(data_json['data'])
  1691. elif data_json["type"] == "tuning":
  1692. """
  1693. {
  1694. "type": "tuning",
  1695. "data": {
  1696. "platform": "聊天模式",
  1697. "username": "用户名",
  1698. "content": "弹幕内容"
  1699. }
  1700. }
  1701. """
  1702. my_handle.tuning_handle(data_json['data'])
  1703. elif data_json["type"] == "comment":
  1704. """
  1705. {
  1706. "type": "comment",
  1707. "data": {
  1708. "platform": "哔哩哔哩",
  1709. "username": "用户名",
  1710. "content": "弹幕内容"
  1711. }
  1712. }
  1713. """
  1714. my_handle.process_data(data_json['data'], "comment")
  1715. elif data_json["type"] == "gift":
  1716. """
  1717. {
  1718. "type": "gift",
  1719. "data": {
  1720. "platform": "哔哩哔哩",
  1721. "gift_name": "礼物名",
  1722. "username": "用户名",
  1723. "num": 礼物数量,
  1724. "unit_price": 礼物单价,
  1725. "total_price": 礼物总价,
  1726. "content": "弹幕内容"
  1727. }
  1728. }
  1729. """
  1730. my_handle.process_data(data_json['data'], "gift")
  1731. elif data_json["type"] == "entrance":
  1732. """
  1733. {
  1734. "type": "entrance",
  1735. "data": {
  1736. "platform": "哔哩哔哩",
  1737. "username": "用户名",
  1738. "content": "入场信息"
  1739. }
  1740. }
  1741. """
  1742. my_handle.process_data(data_json['data'], "entrance")
  1743. return jsonify({"code": 200, "msg": "发送数据成功!"})
  1744. except Exception as e:
  1745. logging.error(traceback.format_exc())
  1746. return jsonify({"code": -1, "msg": f"发送数据失败!{e}"})
  1747. except Exception as e:
  1748. logging.error(traceback.format_exc())
  1749. return jsonify({"code": -1, "msg": f"发送数据失败!{e}"})
  1750. url = f'http://localhost:{port}/static/index.html'
  1751. webbrowser.open(url)
  1752. logging.info(f"浏览器访问地址:{url}")
  1753. socketio.run(app, host='0.0.0.0', port=port, debug=False)