From d9159884a8bbdc77b5ff09f77e048acccfed4483 Mon Sep 17 00:00:00 2001 From: ikaros <327209194@qq.com> Date: Sat, 24 Feb 2024 21:01:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=90=84=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=EF=BC=8C=E5=87=8F=E4=BD=8E=E5=86=97=E4=BD=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bilibili.py | 620 ---------------------- bilibili2.py | 1089 --------------------------------------- douyu.py | 429 --------------- dy.py | 953 ---------------------------------- dy_old.py | 475 ----------------- ks.py | 561 -------------------- ks_old.py | 320 ------------ requirements_common.txt | 2 +- talk.py | 753 --------------------------- tiktok.py | 910 -------------------------------- twitch.py | 496 ------------------ wxlive.py | 443 ---------------- youtube.py | 430 ---------------- 13 files changed, 1 insertion(+), 7480 deletions(-) delete mode 100644 bilibili.py delete mode 100644 bilibili2.py delete mode 100644 douyu.py delete mode 100644 dy.py delete mode 100644 dy_old.py delete mode 100644 ks.py delete mode 100644 ks_old.py delete mode 100644 talk.py delete mode 100644 tiktok.py delete mode 100644 twitch.py delete mode 100644 wxlive.py delete mode 100644 youtube.py diff --git a/bilibili.py b/bilibili.py deleted file mode 100644 index eaa89755..00000000 --- a/bilibili.py +++ /dev/null @@ -1,620 +0,0 @@ -import logging, os -import threading -import schedule -import random -import asyncio -import traceback -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -# 导入所需的库 -from bilibili_api import Credential, live, sync, login - -from utils.common import Common -from utils.config import Config -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -""" - ___ _ - |_ _| | ____ _ _ __ ___ ___ - | || |/ / _` | '__/ _ \/ __| - | || < (_| | | | (_) \__ \ - |___|_|\_\__,_|_| \___/|___/ - -""" - -config = None -common = None -my_handle = None -# last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - -# 点火起飞 -def start_server(): - global config, common, my_handle, last_username_list - - config_path = "config.json" - - common = Common() - config = Config(config_path) - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 获取 httpx 库的日志记录器 - httpx_logger = logging.getLogger("httpx") - # 设置 httpx 日志记录器的级别为 WARNING - httpx_logger.setLevel(logging.WARNING) - - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - # 定时任务 - def schedule_task(index): - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "哔哩哔哩", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - global config - - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # print(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "哔哩哔哩", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "哔哩哔哩", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - try: - if config.get("bilibili", "login_type") == "cookie": - logging.info("b站登录后F12抓网络包获取cookie,强烈建议使用小号!有封号风险") - logging.info("b站登录后,F12控制台,输入 window.localStorage.ac_time_value 回车获取(如果没有,请重新登录)") - - bilibili_cookie = config.get("bilibili", "cookie") - bilibili_ac_time_value = config.get("bilibili", "ac_time_value") - if bilibili_ac_time_value == "": - bilibili_ac_time_value = None - - # print(f'SESSDATA={common.parse_cookie_data(bilibili_cookie, "SESSDATA")}') - # print(f'bili_jct={common.parse_cookie_data(bilibili_cookie, "bili_jct")}') - # print(f'buvid3={common.parse_cookie_data(bilibili_cookie, "buvid3")}') - # print(f'DedeUserID={common.parse_cookie_data(bilibili_cookie, "DedeUserID")}') - - # 生成一个 Credential 对象 - credential = Credential( - sessdata=common.parse_cookie_data(bilibili_cookie, "SESSDATA"), - bili_jct=common.parse_cookie_data(bilibili_cookie, "bili_jct"), - buvid3=common.parse_cookie_data(bilibili_cookie, "buvid3"), - dedeuserid=common.parse_cookie_data(bilibili_cookie, "DedeUserID"), - ac_time_value=bilibili_ac_time_value - ) - elif config.get("bilibili", "login_type") == "手机扫码": - credential = login.login_with_qrcode() - elif config.get("bilibili", "login_type") == "手机扫码-终端": - credential = login.login_with_qrcode_term() - elif config.get("bilibili", "login_type") == "账号密码登录": - bilibili_username = config.get("bilibili", "username") - bilibili_password = config.get("bilibili", "password") - - credential = login.login_with_password(bilibili_username, bilibili_password) - elif config.get("bilibili", "login_type") == "不登录": - credential = None - else: - credential = login.login_with_qrcode() - - # 初始化 Bilibili 直播间 - room = live.LiveDanmaku(my_handle.get_room_id(), credential=credential) - except Exception as e: - logging.error(traceback.format_exc()) - my_handle.abnormal_alarm_handle("platform") - # os._exit(0) - - """ - DANMU_MSG: 用户发送弹幕 - SEND_GIFT: 礼物 - COMBO_SEND:礼物连击 - GUARD_BUY:续费大航海 - SUPER_CHAT_MESSAGE:醒目留言(SC) - SUPER_CHAT_MESSAGE_JPN:醒目留言(带日语翻译?) - WELCOME: 老爷进入房间 - WELCOME_GUARD: 房管进入房间 - NOTICE_MSG: 系统通知(全频道广播之类的) - PREPARING: 直播准备中 - LIVE: 直播开始 - ROOM_REAL_TIME_MESSAGE_UPDATE: 粉丝数等更新 - ENTRY_EFFECT: 进场特效 - ROOM_RANK: 房间排名更新 - INTERACT_WORD: 用户进入直播间 - ACTIVITY_BANNER_UPDATE_V2: 好像是房间名旁边那个xx小时榜 - 本模块自定义事件: - VIEW: 直播间人气更新 - ALL: 所有事件 - DISCONNECT: 断开连接(传入连接状态码参数) - TIMEOUT: 心跳响应超时 - VERIFICATION_SUCCESSFUL: 认证成功 - """ - - @room.on('DANMU_MSG') - async def _(event): - """ - 处理直播间弹幕事件 - :param event: 弹幕事件数据 - """ - global global_idle_time - - # 闲时计数清零 - global_idle_time = 0 - - content = event["data"]["info"][1] # 获取弹幕内容 - user_name = event["data"]["info"][2][1] # 获取发送弹幕的用户昵称 - - logging.info(f"[{user_name}]: {content}") - - data = { - "platform": "哔哩哔哩", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - @room.on('COMBO_SEND') - async def _(event): - """ - 处理直播间礼物连击事件 - :param event: 礼物连击事件数据 - """ - - gift_name = event["data"]["data"]["gift_name"] - user_name = event["data"]["data"]["uname"] - # 礼物数量 - combo_num = event["data"]["data"]["combo_num"] - # 总金额 - combo_total_coin = event["data"]["data"]["combo_total_coin"] - - logging.info(f"用户:{user_name} 赠送 {combo_num} 个 {gift_name},总计 {combo_total_coin}电池") - - data = { - "platform": "哔哩哔哩", - "gift_name": gift_name, - "username": user_name, - "num": combo_num, - "unit_price": combo_total_coin / combo_num / 1000, - "total_price": combo_total_coin / 1000 - } - - my_handle.process_data(data, "gift") - - @room.on('SEND_GIFT') - async def _(event): - """ - 处理直播间礼物事件 - :param event: 礼物事件数据 - """ - - # print(event) - - gift_name = event["data"]["data"]["giftName"] - user_name = event["data"]["data"]["uname"] - # 礼物数量 - num = event["data"]["data"]["num"] - # 总金额 - combo_total_coin = event["data"]["data"]["combo_total_coin"] - # 单个礼物金额 - discount_price = event["data"]["data"]["discount_price"] - - logging.info(f"用户:{user_name} 赠送 {num} 个 {gift_name},单价 {discount_price}电池,总计 {combo_total_coin}电池") - - data = { - "platform": "哔哩哔哩", - "gift_name": gift_name, - "username": user_name, - "num": num, - "unit_price": discount_price / 1000, - "total_price": combo_total_coin / 1000 - } - - my_handle.process_data(data, "gift") - - @room.on('GUARD_BUY') - async def _(event): - """ - 处理直播间续费大航海事件 - :param event: 续费大航海事件数据 - """ - - logging.info(event) - - @room.on('SUPER_CHAT_MESSAGE') - async def _(event): - """ - 处理直播间醒目留言(SC)事件 - :param event: 醒目留言(SC)事件数据 - """ - message = event["data"]["data"]["message"] - uname = event["data"]["data"]["user_info"]["uname"] - price = event["data"]["data"]["price"] - - logging.info(f"用户:{uname} 发送 {price}元 SC:{message}") - - data = { - "platform": "哔哩哔哩", - "gift_name": "SC", - "username": uname, - "num": 1, - "unit_price": price, - "total_price": price, - "content": message - } - - my_handle.process_data(data, "gift") - - my_handle.process_data(data, "comment") - - - @room.on('INTERACT_WORD') - async def _(event): - """ - 处理直播间用户进入直播间事件 - :param event: 用户进入直播间事件数据 - """ - global last_username_list - - user_name = event["data"]["data"]["uname"] - - logging.info(f"用户:{user_name} 进入直播间") - - # 添加用户名到最新的用户名列表 - add_username_to_last_username_list(user_name) - - data = { - "platform": "哔哩哔哩", - "username": user_name, - "content": "进入直播间" - } - - my_handle.process_data(data, "entrance") - - # @room.on('WELCOME') - # async def _(event): - # """ - # 处理直播间老爷进入房间事件 - # :param event: 老爷进入房间事件数据 - # """ - - # print(event) - - # @room.on('WELCOME_GUARD') - # async def _(event): - # """ - # 处理直播间房管进入房间事件 - # :param event: 房管进入房间事件数据 - # """ - - # print(event) - - - try: - # 启动 Bilibili 直播间连接 - sync(room.connect()) - except KeyboardInterrupt: - logging.warning('程序被强行退出') - finally: - logging.warning('关闭连接...可能是直播间号配置有误或者其他原因导致的') - os._exit(0) - - -if __name__ == '__main__': - start_server() diff --git a/bilibili2.py b/bilibili2.py deleted file mode 100644 index 18e6b106..00000000 --- a/bilibili2.py +++ /dev/null @@ -1,1089 +0,0 @@ -import logging, os -import threading -import schedule -import random -import asyncio -import traceback -import copy - -from functools import partial - -import http.cookies -from typing import * - -import aiohttp - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -import blivedm -import blivedm.models.web as web_models -import blivedm.models.open_live as open_models - -# 按键监听语音聊天板块 -import keyboard -import pyaudio -import wave -import numpy as np -import speech_recognition as sr -from aip import AipSpeech -import signal -import time - -from utils.common import Common -from utils.config import Config -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -""" - ___ _ - |_ _| | ____ _ _ __ ___ ___ - | || |/ / _` | '__/ _ \/ __| - | || < (_| | | | (_) \__ \ - |___|_|\_\__,_|_| \___/|___/ - -""" - -config = None -common = None -my_handle = None -# last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -# 点火起飞 -def start_server(): - global config, common, my_handle, last_username_list, SESSDATA - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - - # 按键监听相关 - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = threading.Event() - # 冷却时间 0.5 秒 - cooldown = 0.5 - last_pressed = 0 - - # 配置文件路径 - config_path = "config.json" - - common = Common() - config = Config(config_path) - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 获取 httpx 库的日志记录器 - httpx_logger = logging.getLogger("httpx") - # 设置 httpx 日志记录器的级别为 WARNING - httpx_logger.setLevel(logging.WARNING) - - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - """ - 按键监听板块 - """ - # 录音功能(录音时间过短进入openai的语音转文字会报错,请一定注意) - def record_audio(): - pressdown_num = 0 - CHUNK = 1024 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 44100 - WAVE_OUTPUT_FILENAME = "out/record.wav" - p = pyaudio.PyAudio() - stream = p.open(format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) - frames = [] - print("Recording...") - flag = 0 - while 1: - while keyboard.is_pressed('RIGHT_SHIFT'): - flag = 1 - data = stream.read(CHUNK) - frames.append(data) - pressdown_num = pressdown_num + 1 - if flag: - break - print("Stopped recording.") - stream.stop_stream() - stream.close() - p.terminate() - wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') - wf.setnchannels(CHANNELS) - wf.setsampwidth(p.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - wf.close() - if pressdown_num >= 5: # 粗糙的处理手段 - return 1 - else: - print("杂鱼杂鱼,好短好短(录音时间过短,按右shift重新录制)") - return 0 - - - # THRESHOLD 设置音量阈值,默认值800.0,根据实际情况调整 silence_threshold 设置沉默阈值,根据实际情况调整 - def audio_listen(volume_threshold=800.0, silence_threshold=15): - audio = pyaudio.PyAudio() - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 16000 - CHUNK = 1024 - - stream = audio.open( - format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK, - input_device_index=int(config.get("talk", "device_index")) - ) - - frames = [] # 存储录制的音频帧 - - is_speaking = False # 是否在说话 - silent_count = 0 # 沉默计数 - speaking_flag = False #录入标志位 不重要 - - while True: - # 读取音频数据 - data = stream.read(CHUNK) - audio_data = np.frombuffer(data, dtype=np.short) - max_dB = np.max(audio_data) - # print(max_dB) - if max_dB > volume_threshold: - is_speaking = True - silent_count = 0 - elif is_speaking is True: - silent_count += 1 - - if is_speaking is True: - frames.append(data) - if speaking_flag is False: - logging.info("[录入中……]") - speaking_flag = True - - if silent_count >= silence_threshold: - break - - logging.info("[语音录入完成]") - - # 将音频保存为WAV文件 - '''with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames))''' - return frames - - - # 执行录音、识别&提交 - def do_listen_and_comment(status=True): - global stop_do_listen_and_comment_thread_event - - config = Config(config_path) - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - while True: - try: - # 检查是否收到停止事件 - if stop_do_listen_and_comment_thread_event.is_set(): - logging.info(f'停止录音~') - break - - config = Config(config_path) - - # 根据接入的语音识别类型执行 - if "baidu" == config.get("talk", "type"): - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'baidu_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/baidu_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # 读取音频文件 - with open(WAVE_OUTPUT_FILENAME, 'rb') as fp: - audio = fp.read() - - # 初始化 AipSpeech 对象 - baidu_client = AipSpeech(config.get("talk", "baidu", "app_id"), config.get("talk", "baidu", "api_key"), config.get("talk", "baidu", "secret_key")) - - # 识别音频文件 - res = baidu_client.asr(audio, 'wav', 16000, { - 'dev_pid': 1536, - }) - if res['err_no'] == 0: - content = res['result'][0] - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - else: - logging.error(f"百度接口报错:{res}") - elif "google" == config.get("talk", "type"): - # 创建Recognizer对象 - r = sr.Recognizer() - - try: - # 打开麦克风进行录音 - with sr.Microphone() as source: - logging.info(f'录音中...') - # 从麦克风获取音频数据 - audio = r.listen(source) - logging.info("成功录制") - - # 进行谷歌实时语音识别 en-US zh-CN ja-JP - content = r.recognize_google(audio, language=config.get("talk", "google", "tgt_lang")) - - # 输出识别结果 - # logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - except sr.UnknownValueError: - logging.warning("无法识别输入的语音") - except sr.RequestError as e: - logging.error("请求出错:" + str(e)) - elif "faster_whisper" == config.get("talk", "type"): - from faster_whisper import WhisperModel - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'faster_whisper_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/faster_whisper_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # Run on GPU with FP16 - model = WhisperModel(model_size_or_path=config.get("talk", "faster_whisper", "model_size"), \ - device=config.get("talk", "faster_whisper", "device"), \ - compute_type=config.get("talk", "faster_whisper", "compute_type"), \ - download_root=config.get("talk", "faster_whisper", "download_root")) - - segments, info = model.transcribe(WAVE_OUTPUT_FILENAME, beam_size=config.get("talk", "faster_whisper", "beam_size")) - - logging.debug("识别语言为:'%s',概率:%f" % (info.language, info.language_probability)) - - content = "" - for segment in segments: - logging.info("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text)) - content += segment.text + "。" - - if content == "": - return - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - - if not status: - return - except Exception as e: - logging.error(traceback.format_exc()) - - - def on_key_press(event): - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - # if event.name in ['z', 'Z', 'c', 'C'] and keyboard.is_pressed('ctrl'): - # print("退出程序") - - # os._exit(0) - - # 按键CD - current_time = time.time() - if current_time - last_pressed < cooldown: - return - - - """ - 触发按键部分的判断 - """ - trigger_key_lower = None - stop_trigger_key_lower = None - - # trigger_key是字母, 整个小写 - if trigger_key.isalpha(): - trigger_key_lower = trigger_key.lower() - - # stop_trigger_key是字母, 整个小写 - if stop_trigger_key.isalpha(): - stop_trigger_key_lower = stop_trigger_key.lower() - - if trigger_key_lower: - if event.name == trigger_key or event.name == trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key or event.name == stop_trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - else: - if event.name == trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - - # 是否启用连续对话模式 - if config.get("talk", "continuous_talk"): - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(True,)) - do_listen_and_comment_thread.start() - else: - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(False,)) - do_listen_and_comment_thread.start() - - - # 按键监听 - def key_listener(): - # 注册按键按下事件的回调函数 - keyboard.on_press(on_key_press) - - try: - # 进入监听状态,等待按键按下 - keyboard.wait() - except KeyboardInterrupt: - os._exit(0) - - - # 从配置文件中读取触发键的字符串配置 - trigger_key = config.get("talk", "trigger_key") - stop_trigger_key = config.get("talk", "stop_trigger_key") - - if config.get("talk", "key_listener_enable"): - logging.info(f'单击键盘 {trigger_key} 按键进行录音喵~ 由于其他任务还要启动,如果按键没有反应,请等待一段时间') - - # 创建并启动按键监听线程 - thread = threading.Thread(target=key_listener) - thread.start() - - - - # 定时任务 - def schedule_task(index): - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "哔哩哔哩", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - global config - - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # logging.info(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - logging.debug(f"copywriting_file_path_list={copywriting_file_path_list}") - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - logging.debug(f'copywriting_file_content={copywriting_file_content},content={data_json["content"]}') - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - logging.debug("切换到文案触发模式") - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "哔哩哔哩2", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or (not config.get("idle_time_task", "comment", "enable")): - logging.debug("切换到本地音频模式") - - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - logging.debug(f"local_audio_path={local_audio_path}") - - # 发送给处理函数 - data = { - "platform": "哔哩哔哩2", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - - - # 直播间ID的取值看直播间URL - TEST_ROOM_IDS = [my_handle.get_room_id()] - - try: - if config.get("bilibili", "login_type") == "cookie": - bilibili_cookie = config.get("bilibili", "cookie") - SESSDATA = common.parse_cookie_data(bilibili_cookie, "SESSDATA") - elif config.get("bilibili", "login_type") == "open_live": - # 在开放平台申请的开发者密钥 https://open-live.bilibili.com/open-manage - ACCESS_KEY_ID = config.get("bilibili", "open_live", "ACCESS_KEY_ID") - ACCESS_KEY_SECRET = config.get("bilibili", "open_live", "ACCESS_KEY_SECRET") - # 在开放平台创建的项目ID - APP_ID = config.get("bilibili", "open_live", "APP_ID") - # 主播身份码 直播中心获取 - ROOM_OWNER_AUTH_CODE = config.get("bilibili", "open_live", "ROOM_OWNER_AUTH_CODE") - - except Exception as e: - logging.error(traceback.format_exc()) - my_handle.abnormal_alarm_handle("platform") - - async def main_func(): - global session - - if config.get("bilibili", "login_type") == "open_live": - await run_single_client2() - else: - try: - init_session() - - await run_single_client() - await run_multi_clients() - finally: - await session.close() - - - def init_session(): - global session, SESSDATA - - cookies = http.cookies.SimpleCookie() - cookies['SESSDATA'] = SESSDATA - cookies['SESSDATA']['domain'] = 'bilibili.com' - - # logging.info(f"SESSDATA={SESSDATA}") - - session = aiohttp.ClientSession() - session.cookie_jar.update_cookies(cookies) - - - async def run_single_client(): - """ - 演示监听一个直播间 - """ - global session - - room_id = random.choice(TEST_ROOM_IDS) - client = blivedm.BLiveClient(room_id, session=session) - handler = MyHandler() - client.set_handler(handler) - - client.start() - try: - # 演示5秒后停止 - await asyncio.sleep(5) - client.stop() - - await client.join() - finally: - await client.stop_and_close() - - async def run_single_client2(): - """ - 演示监听一个直播间 开放平台 - """ - client = blivedm.OpenLiveClient( - access_key_id=ACCESS_KEY_ID, - access_key_secret=ACCESS_KEY_SECRET, - app_id=APP_ID, - room_owner_auth_code=ROOM_OWNER_AUTH_CODE, - ) - handler = MyHandler2() - client.set_handler(handler) - - client.start() - try: - # 演示70秒后停止 - # await asyncio.sleep(70) - # client.stop() - - await client.join() - finally: - await client.stop_and_close() - - async def run_multi_clients(): - """ - 演示同时监听多个直播间 - """ - global session - - clients = [blivedm.BLiveClient(room_id, session=session) for room_id in TEST_ROOM_IDS] - handler = MyHandler() - for client in clients: - client.set_handler(handler) - client.start() - - try: - await asyncio.gather(*( - client.join() for client in clients - )) - finally: - await asyncio.gather(*( - client.stop_and_close() for client in clients - )) - - - class MyHandler(blivedm.BaseHandler): - # 演示如何添加自定义回调 - _CMD_CALLBACK_DICT = blivedm.BaseHandler._CMD_CALLBACK_DICT.copy() - - # 入场消息回调 - def __interact_word_callback(self, client: blivedm.BLiveClient, command: dict): - # logging.info(f"[{client.room_id}] INTERACT_WORD: self_type={type(self).__name__}, room_id={client.room_id}," - # f" uname={command['data']['uname']}") - - global last_username_list - - user_name = command['data']['uname'] - - logging.info(f"用户:{user_name} 进入直播间") - - # 添加用户名到最新的用户名列表 - add_username_to_last_username_list(user_name) - - data = { - "platform": "哔哩哔哩2", - "username": user_name, - "content": "进入直播间" - } - - my_handle.process_data(data, "entrance") - - _CMD_CALLBACK_DICT['INTERACT_WORD'] = __interact_word_callback # noqa - - def _on_heartbeat(self, client: blivedm.BLiveClient, message: web_models.HeartbeatMessage): - logging.debug(f'[{client.room_id}] 心跳') - - def _on_danmaku(self, client: blivedm.BLiveClient, message: web_models.DanmakuMessage): - global global_idle_time - - # 闲时计数清零 - global_idle_time = 0 - - # logging.info(f'[{client.room_id}] {message.uname}:{message.msg}') - content = message.msg # 获取弹幕内容 - user_name = message.uname # 获取发送弹幕的用户昵称 - - logging.info(f"[{user_name}]: {content}") - - data = { - "platform": "哔哩哔哩2", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - def _on_gift(self, client: blivedm.BLiveClient, message: web_models.GiftMessage): - # logging.info(f'[{client.room_id}] {message.uname} 赠送{message.gift_name}x{message.num}' - # f' ({message.coin_type}瓜子x{message.total_coin})') - - gift_name = message.gift_name - user_name = message.uname - # 礼物数量 - combo_num = message.num - # 总金额 - combo_total_coin = message.total_coin - - logging.info(f"用户:{user_name} 赠送 {combo_num} 个 {gift_name},总计 {combo_total_coin}电池") - - data = { - "platform": "哔哩哔哩2", - "gift_name": gift_name, - "username": user_name, - "num": combo_num, - "unit_price": combo_total_coin / combo_num / 1000, - "total_price": combo_total_coin / 1000 - } - - my_handle.process_data(data, "gift") - - def _on_buy_guard(self, client: blivedm.BLiveClient, message: web_models.GuardBuyMessage): - logging.info(f'[{client.room_id}] {message.username} 购买{message.gift_name}') - - def _on_super_chat(self, client: blivedm.BLiveClient, message: web_models.SuperChatMessage): - # logging.info(f'[{client.room_id}] 醒目留言 ¥{message.price} {message.uname}:{message.message}') - - message = message.message - uname = message.uname - price = message.price - - logging.info(f"用户:{uname} 发送 {price}元 SC:{message}") - - data = { - "platform": "哔哩哔哩2", - "gift_name": "SC", - "username": uname, - "num": 1, - "unit_price": price, - "total_price": price, - "content": message - } - - my_handle.process_data(data, "gift") - - my_handle.process_data(data, "comment") - - class MyHandler2(blivedm.BaseHandler): - def _on_heartbeat(self, client: blivedm.BLiveClient, message: web_models.HeartbeatMessage): - logging.debug(f'[{client.room_id}] 心跳') - - def _on_open_live_danmaku(self, client: blivedm.OpenLiveClient, message: open_models.DanmakuMessage): - global global_idle_time - - # 闲时计数清零 - global_idle_time = 0 - - # logging.info(f'[{client.room_id}] {message.uname}:{message.msg}') - content = message.msg # 获取弹幕内容 - user_name = message.uname # 获取发送弹幕的用户昵称 - - logging.info(f"[{user_name}]: {content}") - - data = { - "platform": "哔哩哔哩2", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - def _on_open_live_gift(self, client: blivedm.OpenLiveClient, message: open_models.GiftMessage): - gift_name = message.gift_name - user_name = message.uname - # 礼物数量 - combo_num = message.gift_num - # 总金额 - combo_total_coin = message.price * message.gift_num - - logging.info(f"用户:{user_name} 赠送 {combo_num} 个 {gift_name},总计 {combo_total_coin}电池") - - data = { - "platform": "哔哩哔哩2", - "gift_name": gift_name, - "username": user_name, - "num": combo_num, - "unit_price": combo_total_coin / combo_num / 1000, - "total_price": combo_total_coin / 1000 - } - - my_handle.process_data(data, "gift") - - - def _on_open_live_buy_guard(self, client: blivedm.OpenLiveClient, message: open_models.GuardBuyMessage): - logging.info(f'[{client.room_id}] {message.user_info.uname} 购买 大航海等级={message.guard_level}') - - def _on_open_live_super_chat( - self, client: blivedm.OpenLiveClient, message: open_models.SuperChatMessage - ): - print(f'[{message.room_id}] 醒目留言 ¥{message.rmb} {message.uname}:{message.message}') - - message = message.message - uname = message.uname - price = message.rmb - - logging.info(f"用户:{uname} 发送 {price}元 SC:{message}") - - data = { - "platform": "哔哩哔哩2", - "gift_name": "SC", - "username": uname, - "num": 1, - "unit_price": price, - "total_price": price, - "content": message - } - - my_handle.process_data(data, "gift") - - my_handle.process_data(data, "comment") - - def _on_open_live_super_chat_delete( - self, client: blivedm.OpenLiveClient, message: open_models.SuperChatDeleteMessage - ): - logging.info(f'[直播间 {message.room_id}] 删除醒目留言 message_ids={message.message_ids}') - - def _on_open_live_like(self, client: blivedm.OpenLiveClient, message: open_models.LikeMessage): - logging.info(f'用户:{message.uname} 点了个赞') - - - - asyncio.run(main_func()) - -# 退出程序 -def exit_handler(signum, frame): - print("Received signal:", signum) - - -if __name__ == '__main__': - # 这里填一个已登录账号的cookie。不填cookie也可以连接,但是收到弹幕的用户名会打码,UID会变成0 - SESSDATA = '' - - session: Optional[aiohttp.ClientSession] = None - - # 按键监听相关 - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = None - - signal.signal(signal.SIGINT, exit_handler) - signal.signal(signal.SIGTERM, exit_handler) - - start_server() diff --git a/douyu.py b/douyu.py deleted file mode 100644 index 9e1b9d9f..00000000 --- a/douyu.py +++ /dev/null @@ -1,429 +0,0 @@ -import asyncio -import websockets -import json, logging, os -import time -import threading -import schedule -import random -import traceback -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -from utils.common import Common -from utils.logger import Configure_logger -from utils.my_handle import My_handle -from utils.config import Config - - -config = None -common = None -my_handle = None -last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -def start_server(): - global config, common, my_handle, last_liveroom_data, last_username_list - - config_path = "config.json" - - config = Config(config_path) - common = Common() - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 最新的直播间数据 - last_liveroom_data = { - 'OnlineUserCount': 0, - 'TotalUserCount': 0, - 'TotalUserCountStr': '0', - 'OnlineUserCountStr': '0', - 'MsgId': 0, - 'User': None, - 'Content': '当前直播间人数 0,累计直播间人数 0', - 'RoomId': 0 - } - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - # 定时任务 - def schedule_task(index): - global config, common, my_handle, last_liveroom_data, last_username_list - - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "斗鱼", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # print(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(e) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "斗鱼", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "斗鱼", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - async def on_message(websocket, path): - global last_liveroom_data, last_username_list - global global_idle_time - - async for message in websocket: - # print(f"收到消息: {message}") - # await websocket.send("服务器收到了你的消息: " + message) - - try: - data_json = json.loads(message) - # logging.debug(data_json) - if data_json["type"] == "comment": - # logging.info(data_json) - # 闲时计数清零 - global_idle_time = 0 - - user_name = data_json["username"] - content = data_json["content"] - - logging.info(f'[📧直播间弹幕消息] [{user_name}]:{content}') - - data = { - "platform": "斗鱼", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - # 添加用户名到最新的用户名列表 - add_username_to_last_username_list(user_name) - - except Exception as e: - logging.error(traceback.format_exc()) - logging.error("数据解析错误!") - my_handle.abnormal_alarm_handle("platform") - continue - - - async def ws_server(): - ws_url = "127.0.0.1" - ws_port = 5000 - server = await websockets.serve(on_message, ws_url, ws_port) - logging.info(f"WebSocket 服务器已在 {ws_url}:{ws_port} 启动") - await server.wait_closed() - - - asyncio.run(ws_server()) - - -if __name__ == '__main__': - start_server() \ No newline at end of file diff --git a/dy.py b/dy.py deleted file mode 100644 index 8f7a1545..00000000 --- a/dy.py +++ /dev/null @@ -1,953 +0,0 @@ -import websocket -import json, logging, os -import time -import threading -import schedule -import random -import asyncio -import traceback -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -# 按键监听语音聊天板块 -import keyboard -import pyaudio -import wave -import numpy as np -import speech_recognition as sr -from aip import AipSpeech -import signal -import time - -from utils.common import Common -from utils.logger import Configure_logger -from utils.my_handle import My_handle -from utils.config import Config - - -config = None -config_path = None -common = None -my_handle = None -last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -def start_server(): - global config, common, my_handle, last_liveroom_data, last_username_list, config_path - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - - # 按键监听相关 - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = threading.Event() - # 冷却时间 0.5 秒 - cooldown = 0.5 - last_pressed = 0 - - config_path = "config.json" - - config = Config(config_path) - common = Common() - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 最新的直播间数据 - last_liveroom_data = { - 'OnlineUserCount': 0, - 'TotalUserCount': 0, - 'TotalUserCountStr': '0', - 'OnlineUserCountStr': '0', - 'MsgId': 0, - 'User': None, - 'Content': '当前直播间人数 0,累计直播间人数 0', - 'RoomId': 0 - } - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - """ - 按键监听板块 - """ - # 录音功能(录音时间过短进入openai的语音转文字会报错,请一定注意) - def record_audio(): - pressdown_num = 0 - CHUNK = 1024 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 44100 - WAVE_OUTPUT_FILENAME = "out/record.wav" - p = pyaudio.PyAudio() - stream = p.open(format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) - frames = [] - print("Recording...") - flag = 0 - while 1: - while keyboard.is_pressed('RIGHT_SHIFT'): - flag = 1 - data = stream.read(CHUNK) - frames.append(data) - pressdown_num = pressdown_num + 1 - if flag: - break - print("Stopped recording.") - stream.stop_stream() - stream.close() - p.terminate() - wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') - wf.setnchannels(CHANNELS) - wf.setsampwidth(p.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - wf.close() - if pressdown_num >= 5: # 粗糙的处理手段 - return 1 - else: - print("杂鱼杂鱼,好短好短(录音时间过短,按右shift重新录制)") - return 0 - - - # THRESHOLD 设置音量阈值,默认值800.0,根据实际情况调整 silence_threshold 设置沉默阈值,根据实际情况调整 - def audio_listen(volume_threshold=800.0, silence_threshold=15): - audio = pyaudio.PyAudio() - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 16000 - CHUNK = 1024 - - stream = audio.open( - format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK, - input_device_index=int(config.get("talk", "device_index")) - ) - - frames = [] # 存储录制的音频帧 - - is_speaking = False # 是否在说话 - silent_count = 0 # 沉默计数 - speaking_flag = False #录入标志位 不重要 - - while True: - # 读取音频数据 - data = stream.read(CHUNK) - audio_data = np.frombuffer(data, dtype=np.short) - max_dB = np.max(audio_data) - # print(max_dB) - if max_dB > volume_threshold: - is_speaking = True - silent_count = 0 - elif is_speaking is True: - silent_count += 1 - - if is_speaking is True: - frames.append(data) - if speaking_flag is False: - logging.info("[录入中……]") - speaking_flag = True - - if silent_count >= silence_threshold: - break - - logging.info("[语音录入完成]") - - # 将音频保存为WAV文件 - '''with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames))''' - return frames - - - # 执行录音、识别&提交 - def do_listen_and_comment(status=True): - global stop_do_listen_and_comment_thread_event - - config = Config(config_path) - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - while True: - try: - # 检查是否收到停止事件 - if stop_do_listen_and_comment_thread_event.is_set(): - logging.info(f'停止录音~') - break - - config = Config(config_path) - - # 根据接入的语音识别类型执行 - if "baidu" == config.get("talk", "type"): - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'baidu_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/baidu_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # 读取音频文件 - with open(WAVE_OUTPUT_FILENAME, 'rb') as fp: - audio = fp.read() - - # 初始化 AipSpeech 对象 - baidu_client = AipSpeech(config.get("talk", "baidu", "app_id"), config.get("talk", "baidu", "api_key"), config.get("talk", "baidu", "secret_key")) - - # 识别音频文件 - res = baidu_client.asr(audio, 'wav', 16000, { - 'dev_pid': 1536, - }) - if res['err_no'] == 0: - content = res['result'][0] - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - else: - logging.error(f"百度接口报错:{res}") - elif "google" == config.get("talk", "type"): - # 创建Recognizer对象 - r = sr.Recognizer() - - try: - # 打开麦克风进行录音 - with sr.Microphone() as source: - logging.info(f'录音中...') - # 从麦克风获取音频数据 - audio = r.listen(source) - logging.info("成功录制") - - # 进行谷歌实时语音识别 en-US zh-CN ja-JP - content = r.recognize_google(audio, language=config.get("talk", "google", "tgt_lang")) - - # 输出识别结果 - # logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - except sr.UnknownValueError: - logging.warning("无法识别输入的语音") - except sr.RequestError as e: - logging.error("请求出错:" + str(e)) - elif "faster_whisper" == config.get("talk", "type"): - from faster_whisper import WhisperModel - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'faster_whisper_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/faster_whisper_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # Run on GPU with FP16 - model = WhisperModel(model_size_or_path=config.get("talk", "faster_whisper", "model_size"), \ - device=config.get("talk", "faster_whisper", "device"), \ - compute_type=config.get("talk", "faster_whisper", "compute_type"), \ - download_root=config.get("talk", "faster_whisper", "download_root")) - - segments, info = model.transcribe(WAVE_OUTPUT_FILENAME, beam_size=config.get("talk", "faster_whisper", "beam_size")) - - logging.debug("识别语言为:'%s',概率:%f" % (info.language, info.language_probability)) - - content = "" - for segment in segments: - logging.info("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text)) - content += segment.text + "。" - - if content == "": - return - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - - if not status: - return - except Exception as e: - logging.error(traceback.format_exc()) - - - def on_key_press(event): - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - # if event.name in ['z', 'Z', 'c', 'C'] and keyboard.is_pressed('ctrl'): - # print("退出程序") - - # os._exit(0) - - # 按键CD - current_time = time.time() - if current_time - last_pressed < cooldown: - return - - - """ - 触发按键部分的判断 - """ - trigger_key_lower = None - stop_trigger_key_lower = None - - # trigger_key是字母, 整个小写 - if trigger_key.isalpha(): - trigger_key_lower = trigger_key.lower() - - # stop_trigger_key是字母, 整个小写 - if stop_trigger_key.isalpha(): - stop_trigger_key_lower = stop_trigger_key.lower() - - if trigger_key_lower: - if event.name == trigger_key or event.name == trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key or event.name == stop_trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - else: - if event.name == trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - - # 是否启用连续对话模式 - if config.get("talk", "continuous_talk"): - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(True,)) - do_listen_and_comment_thread.start() - else: - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(False,)) - do_listen_and_comment_thread.start() - - - # 按键监听 - def key_listener(): - # 注册按键按下事件的回调函数 - keyboard.on_press(on_key_press) - - try: - # 进入监听状态,等待按键按下 - keyboard.wait() - except KeyboardInterrupt: - os._exit(0) - - - # 从配置文件中读取触发键的字符串配置 - trigger_key = config.get("talk", "trigger_key") - stop_trigger_key = config.get("talk", "stop_trigger_key") - - if config.get("talk", "key_listener_enable"): - logging.info(f'单击键盘 {trigger_key} 按键进行录音喵~ 由于其他任务还要启动,如果按键没有反应,请等待一段时间') - - # 创建并启动按键监听线程 - thread = threading.Thread(target=key_listener) - thread.start() - - - # 定时任务 - def schedule_task(index): - global config, common, my_handle, last_liveroom_data, last_username_list - - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': last_liveroom_data["OnlineUserCount"], - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "抖音", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # print(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "抖音", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "抖音", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - # if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - def on_message(ws, message): - global last_liveroom_data, last_username_list, config, config_path - global global_idle_time - - message_json = json.loads(message) - # logging.debug(message_json) - if "Type" in message_json: - type = message_json["Type"] - data_json = json.loads(message_json["Data"]) - - if type == 1: - # 闲时计数清零 - global_idle_time = 0 - - user_name = data_json["User"]["Nickname"] - content = data_json["Content"] - - logging.info(f'[📧直播间弹幕消息] [{user_name}]:{content}') - - data = { - "platform": "抖音", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - pass - - elif type == 2: - user_name = data_json["User"]["Nickname"] - count = data_json["Count"] - - logging.info(f'[👍直播间点赞消息] {user_name} 点了{count}赞') - - elif type == 3: - user_name = data_json["User"]["Nickname"] - - logging.info(f'[🚹🚺直播间成员加入消息] 欢迎 {user_name} 进入直播间') - - data = { - "platform": "抖音", - "username": user_name, - "content": "进入直播间" - } - - # 添加用户名到最新的用户名列表 - add_username_to_last_username_list(user_name) - - my_handle.process_data(data, "entrance") - - elif type == 4: - user_name = data_json["User"]["Nickname"] - - logging.info(f'[➕直播间关注消息] 感谢 {data_json["User"]["Nickname"]} 的关注') - - data = { - "platform": "抖音", - "username": user_name - } - - my_handle.process_data(data, "follow") - - pass - - elif type == 5: - gift_name = data_json["GiftName"] - user_name = data_json["User"]["Nickname"] - # 礼物数量 - num = data_json["GiftCount"] - # 礼物重复数量 - repeat_count = data_json["RepeatCount"] - - try: - # 暂时是写死的 - data_path = "data/抖音礼物价格表.json" - - # 读取JSON文件 - with open(data_path, "r", encoding="utf-8") as file: - # 解析JSON数据 - data_json = json.load(file) - - if gift_name in data_json: - # 单个礼物金额 需要自己维护礼物价值表 - discount_price = data_json[gift_name] - else: - logging.warning(f"数据文件:{data_path} 中,没有 {gift_name} 对应的价值,请手动补充数据") - discount_price = 1 - except Exception as e: - logging.error(traceback.format_exc()) - discount_price = 1 - - - # 总金额 - combo_total_coin = repeat_count * discount_price - - logging.info(f'[🎁直播间礼物消息] 用户:{user_name} 赠送 {num} 个 {gift_name},单价 {discount_price}抖币,总计 {combo_total_coin}抖币') - - data = { - "platform": "抖音", - "gift_name": gift_name, - "username": user_name, - "num": num, - "unit_price": discount_price / 10, - "total_price": combo_total_coin / 10 - } - - my_handle.process_data(data, "gift") - - elif type == 6: - logging.info(f'[直播间数据] {data_json["Content"]}') - # {'OnlineUserCount': 50, 'TotalUserCount': 22003, 'TotalUserCountStr': '2.2万', 'OnlineUserCountStr': '50', - # 'MsgId': 7260517442466662207, 'User': None, 'Content': '当前直播间人数 50,累计直播间人数 2.2万', 'RoomId': 7260415920948906807} - # print(f"data_json={data_json}") - - last_liveroom_data = data_json - - # 当前在线人数 - OnlineUserCount = data_json["OnlineUserCount"] - - try: - # 是否开启了动态配置功能 - if config.get("trends_config", "enable"): - for path_config in config.get("trends_config", "path"): - online_num_min = int(path_config["online_num"].split("-")[0]) - online_num_max = int(path_config["online_num"].split("-")[1]) - - # 判断在线人数是否在此范围内 - if OnlineUserCount >= online_num_min and OnlineUserCount <= online_num_max: - logging.debug(f"当前配置文件:{path_config['path']}") - # 如果配置文件相同,则跳过 - if config_path == path_config["path"]: - break - - config_path = path_config["path"] - config = Config(config_path) - - my_handle.reload_config(config_path) - - logging.info(f"切换配置文件:{config_path}") - - break - except Exception as e: - logging.error(traceback.format_exc()) - - pass - - elif type == 8: - logging.info(f'[分享直播间] 感谢 {data_json["User"]["Nickname"]} 分享了直播间') - - pass - - def on_error(ws, error): - logging.error("Error:", error) - - - def on_close(ws): - logging.debug("WebSocket connection closed") - - def on_open(ws): - logging.debug("WebSocket connection established") - - - - try: - # WebSocket连接URL - ws_url = "ws://127.0.0.1:8888" - - logging.info(f"监听地址:{ws_url}") - - # 不设置日志等级 - websocket.enableTrace(False) - # 创建WebSocket连接 - ws = websocket.WebSocketApp(ws_url, - on_message=on_message, - on_error=on_error, - on_close=on_close, - on_open=on_open) - - # 运行WebSocket连接 - ws.run_forever() - except KeyboardInterrupt: - logging.warning('程序被强行退出') - finally: - logging.warning('关闭ws连接...请确认您是否启动了抖音弹幕监听程序,ws服务正常运行!\n监听程序启动成功后,请重新运行程序进行对接使用!') - # os._exit(0) - - # 等待子线程结束 - schedule_thread.join() - - -# 退出程序 -def exit_handler(signum, frame): - print("Received signal:", signum) - - -if __name__ == '__main__': - # 按键监听相关 - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = None - - signal.signal(signal.SIGINT, exit_handler) - signal.signal(signal.SIGTERM, exit_handler) - - start_server() - \ No newline at end of file diff --git a/dy_old.py b/dy_old.py deleted file mode 100644 index 1e1e9ec2..00000000 --- a/dy_old.py +++ /dev/null @@ -1,475 +0,0 @@ -import _thread -import binascii -import gzip -import json -import logging -import re -import time -import requests -import websocket -import urllib -from protobuf_inspector.types import StandardParser -from google.protobuf import json_format -from dy_pb2 import PushFrame -from dy_pb2 import Response -from dy_pb2 import MatchAgainstScoreMessage -from dy_pb2 import LikeMessage -from dy_pb2 import MemberMessage -from dy_pb2 import GiftMessage -from dy_pb2 import ChatMessage -from dy_pb2 import SocialMessage -from dy_pb2 import RoomUserSeqMessage -from dy_pb2 import UpdateFanTicketMessage -from dy_pb2 import CommonTextMessage - -# 导入所需的库 -import json, re - -from utils.common import Common -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -common = Common() -# 日志文件路径 -file_path = "./log/log-" + common.get_bj_time(1) + ".txt" -Configure_logger(file_path) - -my_handle = My_handle("config.json") -if my_handle is None: - print("程序初始化失败!") - exit(0) - - -liveRoomId = None -ttwid = None -roomStore = None -liveRoomTitle = None -proxy = None - -def onMessage(ws: websocket.WebSocketApp, message: bytes): - # print("Received: %s" % message) - - message = json.loads(message) - - if message["type"] == "message": - for msg in message["message_list"]: - # 用户进入 - if msg["type"] == "MemberMessage": - username = msg["name"] - - logging.info(f'[🚹🚺直播间成员加入消息] 欢迎 {username} 进入直播间') - - data = { - "username": username, - "content": "进入直播间" - } - - my_handle.process_data(data, "entrance") - # 用户送礼 - elif msg["type"] == "GiftMessage": - username = msg["name"] - giftCount = msg["giftCount"] - giftName = msg["giftName"] - try: - # 暂时是写死的 - data_path = "data/抖音礼物价格表.json" - - # 读取JSON文件 - with open(data_path, "r", encoding="utf-8") as file: - # 解析JSON数据 - data_json = json.load(file) - - if giftName in data_json: - # 单个礼物金额 需要自己维护礼物价值表 - discount_price = data_json[giftName] - else: - logging.warning(f"数据文件:{data_path} 中,没有 {giftName} 对应的价值,请手动补充数据") - discount_price = 1 - except Exception as e: - logging.error(e) - discount_price = 1 - - - # 总金额 - combo_total_coin = giftCount * discount_price - - logging.info(f'[🎁直播间礼物消息] 用户:{username} 赠送 {giftCount} 个 {giftName},单价 {discount_price}抖币,总计 {combo_total_coin}抖币') - - data = { - "gift_name": giftName, - "username": username, - "num": giftCount, - "unit_price": discount_price / 10, - "total_price": combo_total_coin / 10 - } - - my_handle.process_data(data, "gift") - # 用户发言 - elif msg["type"] == "ChatMessage": - username = msg["name"] - content = msg["content"] - - logging.info(f'[📧直播间弹幕消息] [{username}]:{content}') - - # print(data) - - data = { - "username": username, - "content": content - } - - my_handle.process_data(data, "comment") - # 用户点赞 - elif msg["type"] == "LikeMessage": - username = msg["name"] - count = msg["count"] - - logging.info(f"用户:{username} 点了{count}赞") - # 用户关注或者分享 - elif msg["type"] == "SocialMessage": - username = msg["name"] - - logging.info(f"用户关注或者分享:{username}") - # 直播间在线人数 - elif msg["type"] == "RoomUserSeqMessage": - total = msg["total"] - - logging.info(f"直播间在线人数:{total}") - # 直播结束 - elif msg["type"] == "ControlMessage": - pass - - -def onMessage_old(ws: websocket.WebSocketApp, message: bytes): - wssPackage = PushFrame() - wssPackage.ParseFromString(message) - logId = wssPackage.logId - decompressed = gzip.decompress(wssPackage.payload) - payloadPackage = Response() - payloadPackage.ParseFromString(decompressed) - # 发送ack包 - if payloadPackage.needAck: - sendAck(ws, logId, payloadPackage.internalExt) - for msg in payloadPackage.messagesList: - if msg.method == 'WebcastMatchAgainstScoreMessage': - unPackMatchAgainstScoreMessage(msg.payload) - continue - - if msg.method == 'WebcastLikeMessage': - unPackWebcastLikeMessage(msg.payload) - continue - - if msg.method == 'WebcastMemberMessage': - unPackWebcastMemberMessage(msg.payload) - continue - if msg.method == 'WebcastGiftMessage': - unPackWebcastGiftMessage(msg.payload) - continue - if msg.method == 'WebcastChatMessage': - data = unPackWebcastChatMessage(msg.payload) - - # print(data) - - content = data['content'] - user_name = data['user']['nickName'] - - data = { - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - continue - - if msg.method == 'WebcastSocialMessage': - unPackWebcastSocialMessage(msg.payload) - continue - - if msg.method == 'WebcastRoomUserSeqMessage': - unPackWebcastRoomUserSeqMessage(msg.payload) - continue - - if msg.method == 'WebcastUpdateFanTicketMessage': - unPackWebcastUpdateFanTicketMessage(msg.payload) - continue - - if msg.method == 'WebcastCommonTextMessage': - unPackWebcastCommonTextMessage(msg.payload) - continue - - logging.info('[onMessage] [⌛️方法' + msg.method + '等待解析~] [房间Id:' + liveRoomId + ']') - - -def unPackWebcastCommonTextMessage(data): - commonTextMessage = CommonTextMessage() - commonTextMessage.ParseFromString(data) - data = json_format.MessageToDict(commonTextMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackWebcastCommonTextMessage] [] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -def unPackWebcastUpdateFanTicketMessage(data): - updateFanTicketMessage = UpdateFanTicketMessage() - updateFanTicketMessage.ParseFromString(data) - data = json_format.MessageToDict(updateFanTicketMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackWebcastUpdateFanTicketMessage] [] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -def unPackWebcastRoomUserSeqMessage(data): - roomUserSeqMessage = RoomUserSeqMessage() - roomUserSeqMessage.ParseFromString(data) - data = json_format.MessageToDict(roomUserSeqMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - logging.info('[unPackWebcastRoomUserSeqMessage] [] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -def unPackWebcastSocialMessage(data): - socialMessage = SocialMessage() - socialMessage.ParseFromString(data) - data = json_format.MessageToDict(socialMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackWebcastSocialMessage] [➕直播间关注消息] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -# 普通消息 -def unPackWebcastChatMessage(data): - chatMessage = ChatMessage() - chatMessage.ParseFromString(data) - data = json_format.MessageToDict(chatMessage, preserving_proto_field_name=True) - logging.info('[unPackWebcastChatMessage] [📧直播间弹幕消息] [房间Id:' + liveRoomId + '] | ' + data['content']) - # logging.info('[unPackWebcastChatMessage] [📧直播间弹幕消息] [房间Id:' + liveRoomId + '] | ' + json.dumps(data)) - return data - - -# 礼物消息 -def unPackWebcastGiftMessage(data): - giftMessage = GiftMessage() - giftMessage.ParseFromString(data) - data = json_format.MessageToDict(giftMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackWebcastGiftMessage] [🎁直播间礼物消息] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -# xx成员进入直播间消息 -def unPackWebcastMemberMessage(data): - memberMessage = MemberMessage() - memberMessage.ParseFromString(data) - data = json_format.MessageToDict(memberMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackWebcastMemberMessage] [🚹🚺直播间成员加入消息] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -# 点赞 -def unPackWebcastLikeMessage(data): - likeMessage = LikeMessage() - likeMessage.ParseFromString(data) - data = json_format.MessageToDict(likeMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackWebcastLikeMessage] [👍直播间点赞消息] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -# 解析WebcastMatchAgainstScoreMessage消息包体 -def unPackMatchAgainstScoreMessage(data): - matchAgainstScoreMessage = MatchAgainstScoreMessage() - matchAgainstScoreMessage.ParseFromString(data) - data = json_format.MessageToDict(matchAgainstScoreMessage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - # logging.info('[unPackMatchAgainstScoreMessage] [🤷不知道是啥的消息] [房间Id:' + liveRoomId + '] | ' + log) - return data - - -# 发送Ack请求 -def sendAck(ws, logId, internalExt): - obj = PushFrame() - obj.payloadType = 'ack' - obj.logId = logId - obj.payloadType = internalExt - data = obj.SerializeToString() - ws.send(data, websocket.ABNF.OPCODE_BINARY) - # logging.info('[sendAck] [🌟发送Ack] [房间Id:' + liveRoomId + '] ====> 房间🏖标题【' + liveRoomTitle + '】') - - -def onError(ws, error): - logging.error('[onError] [webSocket Error事件] [房间Id:' + liveRoomId + ']') - - -def onClose(ws, a, b): - logging.info('[onClose] [webSocket Close事件] [房间Id:' + liveRoomId + ']') - - -def onOpen(ws): - ws.send('{"url":"https://live.douyin.com/358102408285","proxyIp":""}') - # _thread.start_new_thread(ping, (ws,)) - logging.info('[onOpen] [webSocket Open事件] [房间Id:' + liveRoomId + ']') - - -# 发送ping心跳包 -def ping(ws): - while True: - obj = PushFrame() - obj.payloadType = 'hb' - data = obj.SerializeToString() - ws.send(data, websocket.ABNF.OPCODE_BINARY) - logging.info('[ping] [💗发送ping心跳] [房间Id:' + liveRoomId + '] ====> 房间🏖标题【' + liveRoomTitle + '】') - time.sleep(10) - - -def wssServerStart(roomId): - global liveRoomId - liveRoomId = roomId - websocket.enableTrace(False) - webSocketUrl = 'wss://webcast3-ws-web-lq.douyin.com/webcast/im/push/v2/?app_name=douyin_web&\ - version_code=180800&webcast_sdk_version=1.3.0&update_version_code=1.3.0&compress=gzip&\ - internal_ext=internal_src:dim|wss_push_room_id:'+liveRoomId+'|wss_push_did:7188358506633528844|\ - dim_log_id:20230521093022204E5B327EF20D5CDFC6|fetch_time:1684632622323|seq:1|wss_info:0-1684632622323-0-0|\ - wrds_kvs:WebcastRoomRankMessage-1684632106402346965_WebcastRoomStatsMessage-1684632616357153318&\ - cursor=t-1684632622323_r-1_d-1_u-1_h-1&host=https://live.douyin.com&aid=6383&live_id=1&did_rule=3&\ - debug=false&maxCacheMessageNumber=20&endpoint=live_pc&support_wrds=1&im_path=/webcast/im/fetch/&\ - user_unique_id=7188358506633528844&device_platform=web&cookie_enabled=true&screen_width=1440&screen_height=900&\ - browser_language=zh&browser_platform=MacIntel&browser_name=Mozilla&\ - browser_version=5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20\ - Chrome/113.0.0.0%20Safari/537.36&browser_online=true&tz_name=Asia/Shanghai&identity=audience&room_id='+liveRoomId+'&\ - heartbeatDuration=0&signature=00000000' - h = { - 'cookie': 'ttwid='+ttwid + \ - "__ac_nonce=063c22e0b00ef2187712a; __ac_signature=_02B4Z6wo00f01UZsilwAAIDC0rrRUTr27elGTI7AADJcqjRe1hQOZ" - ".K4Q4FmO9daZqxwlf.5k4RomG9GZZgMXO3CvtvKJiy9txGxo4HWdJhcwlVYhBiKSGhWCrULSjQEPlsLZ8LgciLILIZO8f; " - "%7Cd796439337e51fc553cfb64d79ef412b84b3eef18e9e19ebaa1287a829b90db8; douyin.com; " - "passport_csrf_token=955106f18f323068c601a6918c27747d; " - "passport_csrf_token_default=955106f18f323068c601a6918c27747d; " - "s_v_web_id=verify_lcvfzyv4_BZabwp7p_r58g_4G5z_8KHs_lDuazCJCZ9uB; " - "passport_assist_user" - "=Cj2FisA1wsU1M6ZAOUfBxA5tt48uEnnpcfN8dtT3oivxC3hjFxm0iQwLvZkXjoCnZas0ias8qelCHfDmIZJ5GkgKPEal_vGBVAKPt3vk_68vkfwzRBgsf8APM87d_iaEiBj6_B9X0Gj7vjvTHA_1A0fF9m3QYysg3NS7vbf-ihDlxaYNGImv1lQiAQNaHWg4; n_mh=bUxOCHzZv5szT8QIHfxmgnjoVlZQ1kn7aM-ZW6ndDKY; sso_uid_tt=e1e48ca5f945784a59c3174e16176a61; sso_uid_tt_ss=e1e48ca5f945784a59c3174e16176a61; toutiao_sso_user=fba93355178fb80aafc02d05bfcd0a5c; toutiao_sso_user_ss=fba93355178fb80aafc02d05bfcd0a5c; sid_ucp_sso_v1=1.0.0-KDM0OWJmNzI2ZDQ1NjFmODJkMTY4Y2Q2MDAzMTIzY2FkMTc1YWUwYmQKHQik8Iy7gQMQ7tyIngYY7zEgDDCYxPfbBTgGQPQHGgJsZiIgZmJhOTMzNTUxNzhmYjgwYWFmYzAyZDA1YmZjZDBhNWM; ssid_ucp_sso_v1=1.0.0-KDM0OWJmNzI2ZDQ1NjFmODJkMTY4Y2Q2MDAzMTIzY2FkMTc1YWUwYmQKHQik8Iy7gQMQ7tyIngYY7zEgDDCYxPfbBTgGQPQHGgJsZiIgZmJhOTMzNTUxNzhmYjgwYWFmYzAyZDA1YmZjZDBhNWM; odin_tt=8547fef56c59daf11b0c15312d499833a2be72189dd6aad256dc07c950b8880c52ad37e717a99de93cc971ad3d80d88ffc1b4e6a10be0fd4f05d46ba29e91ac5; passport_auth_status=6929cca1c9119873a9d7fa528ccd5f37%2C; passport_auth_status_ss=6929cca1c9119873a9d7fa528ccd5f37%2C; uid_tt=8c0899f7528059e847f7eddad4210e5c; uid_tt_ss=8c0899f7528059e847f7eddad4210e5c; sid_tt=6c5a7be3caf51c935494a12b4f1c5c13; sessionid=6c5a7be3caf51c935494a12b4f1c5c13; sessionid_ss=6c5a7be3caf51c935494a12b4f1c5c13; LOGIN_STATUS=1; store-region=cn-hn; store-region-src=uid; sid_guard=6c5a7be3caf51c935494a12b4f1c5c13%7C1673670258%7C5183996%7CWed%2C+15-Mar-2023+04%3A24%3A14+GMT; sid_ucp_v1=1.0.0-KGZmOThhNTZlMThiOTEzMTI2MWI0MDIxZGM5MDUyNjVmZmVkMzVhMTgKFwik8Iy7gQMQ8tyIngYY7zEgDDgGQPQHGgJsZiIgNmM1YTdiZTNjYWY1MWM5MzU0OTRhMTJiNGYxYzVjMTM; ssid_ucp_v1=1.0.0-KGZmOThhNTZlMThiOTEzMTI2MWI0MDIxZGM5MDUyNjVmZmVkMzVhMTgKFwik8Iy7gQMQ8tyIngYY7zEgDDgGQPQHGgJsZiIgNmM1YTdiZTNjYWY1MWM5MzU0OTRhMTJiNGYxYzVjMTM; csrf_session_id=f8b3bae210355efd72d7b596b8d59786; tt_scid=yrq-tjQaXEaPz4kTP9WpqNwOnqVy13KMI.snGi6wnZp8P1hduQmhbtqsTOyIhz.O0bd1; download_guide=%223%2F20230114%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1674275671220%2C%22type%22%3A1%7D; strategyABtestKey=%221673670871.396%22; FOLLOW_NUMBER_YELLOW_POINT_INFO=%22MS4wLjABAAAAlc42G0Nktrm_CVQMfASabMRnI_6GlL2br1i7a8yqTEI%2F1673712000000%2F0%2F1673670871480%2F0%22; home_can_add_dy_2_desktop=%221%22; passport_fe_beating_status=true; msToken=veZvf1xerEu8qj_1IHeP6Tce5JyHlTLZf_YVSJCj5moDg72Q0821Vcqo4ylUrBP75wlGO2xO22kVG9fEsZvWcx6L6nUPhL6LFf6TtpXznJMEA7riXCgfInQgv70a3Go=; msToken=6j75IbD0uth58uE0p2svIf3-bGlzkqLnJ34RfGkr8Ooxgos99KHfw-4HzlJ26GI6C3uTwDUUIthFI0wUocJy2ZAMkpyPrUgGFgcshHRO3vw_kBc9yiZ-4EbErjwp8FI=", - "origin": "https://www.douyin.com", - "referer": "https://www.douyin.com/", - # "sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"", - # "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "\"macOS\"", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", - "x-secsdk-csrf-token": "000100000001a4af956d5ea10b016b19ba5c287191937c2093fc6b603acc6ee5bed3a6354333173a12ca9498806f", - 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', - } - # 创建一个长连接 - ws = websocket.WebSocketApp( - webSocketUrl, on_message=onMessage, on_error=onError, on_close=onClose, - on_open=onOpen, - header=h - ) - ws.run_forever() - - -def parseLiveRoomUrl(url): - print(f"直播间地址:{url}") - - h = { - 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', - 'cookie': '__ac_nonce=0638733a400869171be51', - } - res = requests.get(url=url, headers=h, proxies=proxy) - global ttwid, roomStore, liveRoomId, liveRoomTitle - data = res.cookies.get_dict() - # print(f"data={data}") - ttwid = data['ttwid'] - res = res.text - res = re.search(r'', res) - res = res.group(1) - res = urllib.parse.unquote(res, encoding='utf-8', errors='replace') - res = json.loads(res) - # print(f"res={res}") - roomStore = res['app']['initialState']['roomStore'] - liveRoomId = roomStore['roomInfo']['roomId'] - liveRoomTitle = roomStore['roomInfo']['room']['title'] - liveAnchorNickname = roomStore['roomInfo']['anchor']['nickname'] - - print(f"直播间标题:{liveRoomTitle}\n播主:{liveAnchorNickname}\n直播间ID:{liveRoomId}") - - wssServerStart(liveRoomId) - - -def test(): - url = "https://www.douyin.com/aweme/v1/web/comment/follow/user/?device_platform=webapp&aid=6383&channel=channel_pc_web&pc_client_type=1&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1440&screen_height=900&browser_language=zh&browser_platform=MacIntel&browser_name=Chrome&browser_version=109.0.0.0&browser_online=true&engine_name=Blink&engine_version=109.0.0.0&os_name=Mac+OS&os_version=10.15.7&cpu_core_num=8&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7188358506633528844&msToken=6j75IbD0uth58uE0p2svIf3-bGlzkqLnJ34RfGkr8Ooxgos99KHfw-4HzlJ26GI6C3uTwDUUIthFI0wUocJy2ZAMkpyPrUgGFgcshHRO3vw_kBc9yiZ-4EbErjwp8FI=&X-Bogus=DFSzswVYdOs9-MIISZ-yup7TlqtU" - h = { - "accept": "application/json, text/plain, */*", - # "accept-encoding": "gzip, deflate, br", - # "accept-language": "zh,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6", - # "content-length": "26", - "content-type": "application/x-www-form-urlencoded; charset=UTF-8", - "cookie": "__ac_nonce=063c22e0b00ef2187712a; __ac_signature=_02B4Z6wo00f01UZsilwAAIDC0rrRUTr27elGTI7AADJcqjRe1hQOZ" - ".K4Q4FmO9daZqxwlf.5k4RomG9GZZgMXO3CvtvKJiy9txGxo4HWdJhcwlVYhBiKSGhWCrULSjQEPlsLZ8LgciLILIZO8f; " - "ttwid=1%7Cw5daZw41FpoZMR-XIJc-J_4yVhjVtBUBWUPO-v6khXo%7C1673670155" - "%7Cd796439337e51fc553cfb64d79ef412b84b3eef18e9e19ebaa1287a829b90db8; douyin.com; " - "passport_csrf_token=955106f18f323068c601a6918c27747d; " - "passport_csrf_token_default=955106f18f323068c601a6918c27747d; " - "s_v_web_id=verify_lcvfzyv4_BZabwp7p_r58g_4G5z_8KHs_lDuazCJCZ9uB; " - "passport_assist_user" - "=Cj2FisA1wsU1M6ZAOUfBxA5tt48uEnnpcfN8dtT3oivxC3hjFxm0iQwLvZkXjoCnZas0ias8qelCHfDmIZJ5GkgKPEal_vGBVAKPt3vk_68vkfwzRBgsf8APM87d_iaEiBj6_B9X0Gj7vjvTHA_1A0fF9m3QYysg3NS7vbf-ihDlxaYNGImv1lQiAQNaHWg4; n_mh=bUxOCHzZv5szT8QIHfxmgnjoVlZQ1kn7aM-ZW6ndDKY; sso_uid_tt=e1e48ca5f945784a59c3174e16176a61; sso_uid_tt_ss=e1e48ca5f945784a59c3174e16176a61; toutiao_sso_user=fba93355178fb80aafc02d05bfcd0a5c; toutiao_sso_user_ss=fba93355178fb80aafc02d05bfcd0a5c; sid_ucp_sso_v1=1.0.0-KDM0OWJmNzI2ZDQ1NjFmODJkMTY4Y2Q2MDAzMTIzY2FkMTc1YWUwYmQKHQik8Iy7gQMQ7tyIngYY7zEgDDCYxPfbBTgGQPQHGgJsZiIgZmJhOTMzNTUxNzhmYjgwYWFmYzAyZDA1YmZjZDBhNWM; ssid_ucp_sso_v1=1.0.0-KDM0OWJmNzI2ZDQ1NjFmODJkMTY4Y2Q2MDAzMTIzY2FkMTc1YWUwYmQKHQik8Iy7gQMQ7tyIngYY7zEgDDCYxPfbBTgGQPQHGgJsZiIgZmJhOTMzNTUxNzhmYjgwYWFmYzAyZDA1YmZjZDBhNWM; odin_tt=8547fef56c59daf11b0c15312d499833a2be72189dd6aad256dc07c950b8880c52ad37e717a99de93cc971ad3d80d88ffc1b4e6a10be0fd4f05d46ba29e91ac5; passport_auth_status=6929cca1c9119873a9d7fa528ccd5f37%2C; passport_auth_status_ss=6929cca1c9119873a9d7fa528ccd5f37%2C; uid_tt=8c0899f7528059e847f7eddad4210e5c; uid_tt_ss=8c0899f7528059e847f7eddad4210e5c; sid_tt=6c5a7be3caf51c935494a12b4f1c5c13; sessionid=6c5a7be3caf51c935494a12b4f1c5c13; sessionid_ss=6c5a7be3caf51c935494a12b4f1c5c13; LOGIN_STATUS=1; store-region=cn-hn; store-region-src=uid; sid_guard=6c5a7be3caf51c935494a12b4f1c5c13%7C1673670258%7C5183996%7CWed%2C+15-Mar-2023+04%3A24%3A14+GMT; sid_ucp_v1=1.0.0-KGZmOThhNTZlMThiOTEzMTI2MWI0MDIxZGM5MDUyNjVmZmVkMzVhMTgKFwik8Iy7gQMQ8tyIngYY7zEgDDgGQPQHGgJsZiIgNmM1YTdiZTNjYWY1MWM5MzU0OTRhMTJiNGYxYzVjMTM; ssid_ucp_v1=1.0.0-KGZmOThhNTZlMThiOTEzMTI2MWI0MDIxZGM5MDUyNjVmZmVkMzVhMTgKFwik8Iy7gQMQ8tyIngYY7zEgDDgGQPQHGgJsZiIgNmM1YTdiZTNjYWY1MWM5MzU0OTRhMTJiNGYxYzVjMTM; csrf_session_id=f8b3bae210355efd72d7b596b8d59786; tt_scid=yrq-tjQaXEaPz4kTP9WpqNwOnqVy13KMI.snGi6wnZp8P1hduQmhbtqsTOyIhz.O0bd1; download_guide=%223%2F20230114%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1674275671220%2C%22type%22%3A1%7D; strategyABtestKey=%221673670871.396%22; FOLLOW_NUMBER_YELLOW_POINT_INFO=%22MS4wLjABAAAAlc42G0Nktrm_CVQMfASabMRnI_6GlL2br1i7a8yqTEI%2F1673712000000%2F0%2F1673670871480%2F0%22; home_can_add_dy_2_desktop=%221%22; passport_fe_beating_status=true; msToken=veZvf1xerEu8qj_1IHeP6Tce5JyHlTLZf_YVSJCj5moDg72Q0821Vcqo4ylUrBP75wlGO2xO22kVG9fEsZvWcx6L6nUPhL6LFf6TtpXznJMEA7riXCgfInQgv70a3Go=; msToken=6j75IbD0uth58uE0p2svIf3-bGlzkqLnJ34RfGkr8Ooxgos99KHfw-4HzlJ26GI6C3uTwDUUIthFI0wUocJy2ZAMkpyPrUgGFgcshHRO3vw_kBc9yiZ-4EbErjwp8FI=", - "origin": "https://www.douyin.com", - "referer": "https://www.douyin.com/", - # "sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"", - # "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "\"macOS\"", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36", - "x-secsdk-csrf-token": "000100000001a4af956d5ea10b016b19ba5c287191937c2093fc6b603acc6ee5bed3a6354333173a12ca9498806f" - } - data = "type=1&user_id=93865343726" - # res = requests.post(url=url, data=data, headers=h, proxies=proxy) - # print(res) - res = requests.post(url=url, data=data, headers=h, proxies=proxy).json() - print(res) - - -# 十六进制字符串转protobuf格式 (用于快手网页websocket调试分析包体结构) -def hexStrToProtobuf(hexStr): - # 示例数据 - # hexStr = '08d40210011aeb250a8e010a81010a0b66666731343535323939391206e68595e799bd1a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31322f32312f424d6a41794d6a45784d5449794d5441304d6a68664d6a67784d4445354e7a67344d5638795832686b4d6a6335587a49324d513d3d5f732e6a7067180120012a04323132300aa8010a9e010a0b79697869616f77753636361209e79fa5e5b08fe6ada61a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31302f30322f31392f424d6a41794d6a45774d4449784f544d304e4442664e6a497a4d4455324d445977587a4a66614751304e444a664d546b335f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a033430330ab6010aac010a0c4c31333130373432373635301212e9bb8ee699a8f09f8c8af09f8c8af09f8c8a1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31392f31332f424d6a41794d6a45784d546b784d7a4d784d5452664d5441344e5467794d5449334d5638795832686b4e444935587a45304d513d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a033335390ac2010ab8010a104757515053414148445244444145775a121ee69492e4b880e58fa3e8a28be6989fe6989fe98081e7bb99e58c97e699a81a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31322f32312f424d6a41794d6a45784d5449794d5445774d6a4e664d5445314d7a59354d4451324e6c38795832686b4e7a46664f5449355f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a033130330a91010a88010a0f337870323538746a6d6376337a62751209e58699e7949ce8af971a6a68747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f32302f31372f424d6a41794d6a45784d6a41784e7a4d784e5442664d7a45784d7a4d784f5445784e6c38795832686b4d545530587a49344d513d3d5f732e6a706718012a0233340aac010aa3010a0979756875616e306b64120ce88a8be594a4e1b587e1b69c1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30362f31312f30312f424d6a41794d6a41324d5445774d5451334e5452664d5441774d6a51324f4445344f4638795832686b4e444535587a457a4e773d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0233330a8a010a81010a0c6868686832303033313032391205e888aac2b71a6a68747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30332f32322f424d6a41794d6a45784d444d794d6a41784d546c664d6a4d7a4d7a45794d4455304d3138795832686b4e544133587a4d314d513d3d5f732e6a706718012a0233310aab010aa2010a0f3378686d3568677a6e657963666b6b1209e890a8e5bf853536391a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032302f30372f32342f30382f424d6a41794d4441334d6a51774f4449774d4442664d5467344e5455314d5449784e6c38795832686b4d6a4d79587a673d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0232330a93010a8a010a0f33787569663363746b6e6d38773271120be790aae790aa37313432321a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31382f31322f424d6a41794d6a45784d5467784d6a45784e4446664d7a45794f5441354f446b304e3138795832686b4e7a6730587a49344e773d3d5f732e6a706718012a0232320a94010a8b010a0f337862677a6a627034777570763567120cefbc87e7bbade99b86efbc821a6a68747470733a2f2f70352e612e7978696d67732e636f6d2f75686561642f41422f323032322f30382f33302f31352f424d6a41794d6a41344d7a41784e5451334e4452664d5441324d6a51334d6a41324e3138795832686b4f545533587a63314e413d3d5f732e6a706718012a0232320aa5010a9c010a0979796473696f73313212054c696b652e1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31382f31322f424d6a41794d6a45784d5467784d6a51324e4456664d6a4d354d4459774d5455344e5638785832686b4d546731587a51354d773d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0232310a90010a85010a0a48657969676530353230120be4bba5e6ad8c20f09f8e801a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31332f31302f424d6a41794d6a45784d544d784d4455354d6a4e664d544d324e6a41304e7a41774d5638785832686b4d6a6779587a51794f413d3d5f732e6a7067180120012a0231320aa8010a9f010a0f33786b623464793435706a797570711206e684a6e6829f1a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30342f32322f424d6a41794d6a45784d4451794d6a41774d445a664e4445794f446b324d545931587a4a66614751324e4456664f446b355f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0231310aa5010a9c010a08776a353431383830120ae88b8fe791bee699a82f1a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30332f30302f424d6a41794d6a45784d444d774d4449344e546c664d6a41354e6a45794e544d31587a4666614751304d7a6c664e444d785f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0231310abc010ab3010a0f33786334746a7a376e7875636561791216e7a9bfe5b1b1e794b2efbc88696b756ee59ba2efbc891a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30382f32362f31312f424d6a41794d6a41344d6a59784d5451334d4442664d6a4d314d7a41314d4449774d3138795832686b4e444d79587a4d7a4e673d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0231300a8c010a84010a0957535162616f353230120fe5b08f20e5a88120e5b09120e380821a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31332f31392f424d6a41794d6a45784d544d784f5445354d5446664f5455314e4449304f445578587a4a666147517a4e5452664e4449785f732e6a706718012a01330a8f010a87010a0f337834646178626768746a78716979120ce7a78be8be9ee1b587e1b69c1a6668747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31342f31312f424d6a41794d6a45784d5451784d544d7a4d544a664e7a55354d7a63774d446b31587a4a66614751784f544e664e5459315f732e6a706718012a01330a8d010a85010a0f3378723937706975747768326d7a321206e791bee4b8811a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032312f30342f32332f32312f424d6a41794d5441304d6a4d794d5445304e544a664d5463794d7a55304f4463304d4638795832686b4f546378587a59354e513d3d5f732e6a706718012a01330ab0010aa8010a0d6c713431383835343138386868120de585b3e4ba8ee58a8920e5bcb71a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30352f32332f424d6a41794d6a45784d4455794d7a4d334e544a664d54517a4f44497a4e6a67784d6c38795832686b4d544578587a67324f413d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a01330a8b010a83010a0e4c544431353933313731353337371209e4bba5e6a4bfe383bb1a6668747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30372f31392f424d6a41794d6a45784d4463784f544d314d545a664d5441314d5463784e4459334e6c38795832686b4f444579587a55315f732e6a706718012a01330a89010a83010a0b64796c3230303830363130120ce5b08fe5b08fe5a79ce99c961a6668747470733a2f2f70342e612e7978696d67732e636f6d2f75686561642f41422f323032322f31302f31362f31312f424d6a41794d6a45774d5459784d5455354e5442664f4449304e7a517a4e545535587a4a66614751334d7a42664e5445335f732e6a70672a01320ab7010ab1010a0f3378727a33667a69737438717334611218e5bf98e5b79de38088e5b7b2e69c89e58584e5bc9fe380891a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31342f31312f424d6a41794d6a45784d5451784d54557a4d6a42664f4441304e444d794f545579587a4666614751304d7a52664d7a4d785f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320a90010a8a010a0e796f6e6773686974756f7a68616e1210e5b08fe58b87e5a3ab2de99988e8b68a1a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f30352f32382f31332f424d6a41794d6a41314d6a67784d7a51354d445a664e7a59304f44517a4d7a6b35587a4a66614751794d6a56664e544d7a5f732e6a70672a01320a8d010a87010a0f3378746d706b6167627536766e7139120ce5ad90e792a9e1b587e1b69c1a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31302f33312f31312f424d6a41794d6a45774d7a45784d544d784e4468664e6a45324d4445304d444d30587a4a66614751314e446c664d54497a5f732e6a70672a01320a89010a83010a0f4c4a483532304c4a31333134656d6f1204f09f88b71a6a68747470733a2f2f70352e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31342f31382f424d6a41794d6a45784d5451784f4441314d4442664d6a67354d4441774f5455314e6c38795832686b4d6a6730587a63794e513d3d5f732e6a70672a01320aa5010a9d010a0f33786967326675743533373872626b1204456e6d681a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f32332f31312f424d6a41794d6a45784d6a4d784d544d784e446c664d6a59324e7a517a4d4455334f5638785832686b4d7a4132587a6b315f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a01320ab1010aab010a0f33786d39353268396a393473787a731212e5ad90e792a9efbc88e5b08fe58fb7efbc891a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30372f30302f424d6a41794d6a45784d4463774d4451794e4452664d6a517a4d5463314d6a49354e5638795832686b4e5464664e7a49785f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320aa9010aa3010a0f3378397370326436703272727866391206e585b1e5928c1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30372f32382f31322f424d6a41794d6a41334d6a67784d6a55314d7a42664d6a4d354e6a4d774e4455304d3138795832686b4e6a6b35587a45774e413d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320aa5010a9f010a0b4c5a5032303133313479611206e6b3bde4b8801a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30372f31332f31362f424d6a41794d6a41334d544d784e6a49304e5456664d546b324e4445794f4463334f5638795832686b4f545179587a597a4d673d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320a9f010a99010a0f3378326e37793865656573766b6879121ee58c97e699a8e79a84e4bfa1e699baefbc88e5b7b2e7b4abe7a082efbc891a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31332f31312f424d6a41794d6a45784d544d784d5441344d6a68664d5467344d44497a4f5441354e5638785832686b4f446b79587a45795f732e6a70672a013220a699a0ebca30' - with open('t-proto', 'wb') as w: - w.write(binascii.unhexlify(hexStr)) - - parser = StandardParser() - - print(parser) - with open('t-proto', 'rb') as fh: - output = parser.parse_message(fh, 'message') - print(output) - return output - - -try: - room_id = my_handle.get_room_id() - # parseLiveRoomUrl(f"https://live.douyin.com/{room_id}") - - # WebSocket连接URL - # ws://42.193.254.253:3000/dy - # ws://49.235.82.99:10006 - ws_url = f"ws://49.235.82.99:10006/{room_id}" - - # 创建WebSocket连接 - websocket.enableTrace(True) - ws = websocket.WebSocketApp(ws_url, on_message=onMessage, on_error=onError, on_close=onClose, - on_open=onOpen) - - # 启动定时发送ping的线程 - - # 运行WebSocket连接 - ws.run_forever() -except KeyboardInterrupt: - print('程序被强行退出') -finally: - print('关闭连接...可能是直播间不存在或下播或网络问题') - exit(0) diff --git a/ks.py b/ks.py deleted file mode 100644 index fa4f4df7..00000000 --- a/ks.py +++ /dev/null @@ -1,561 +0,0 @@ -from playwright.sync_api import sync_playwright -import logging, os -import time -import threading -import schedule -import random -import traceback -import asyncio -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -from google.protobuf.json_format import MessageToDict -from configparser import ConfigParser -import kuaishou_pb2 - -from utils.common import Common -from utils.logger import Configure_logger -from utils.my_handle import My_handle -from utils.config import Config - - -config = None -common = None -my_handle = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -class kslive(object): - def __init__(self): - global config, common, my_handle - - self.path = os.path.abspath('') - self.chrome_path = r"\firefox-1419\firefox\firefox.exe" - self.ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0' - self.uri = 'https://live.kuaishou.com/u/' - self.context = None - self.browser = None - self.page = None - - try: - self.live_ids = config.get("room_display_id") - self.thread = 2 - # 没什么用的手机号配置,也就方便登录 - self.phone = "123" - except Exception as e: - logging.error(traceback.format_exc()) - logging.error("请检查配置文件") - my_handle.abnormal_alarm_handle("platform") - exit() - - def find_file(self, find_path, file_type) -> list: - """ - 寻找文件 - :param find_path: 子路径 - :param file_type: 文件类型 - :return: - """ - path = self.path + "\\" + find_path - data_list = [] - for root, dirs, files in os.walk(path): - if root != path: - break - for file in files: - file_path = os.path.join(root, file) - if file_path.find(file_type) != -1: - data_list.append(file_path) - return data_list - - def main(self, lid, semaphore): - if not os.path.exists(self.path + "\\cookie"): - os.makedirs(self.path + "\\cookie") - - cookie_path=self.path + "\\cookie\\" + self.phone + ".json" - # if not os.path.exists(cookie_path): - # with open(cookie_path, 'w') as file: - # file.write('{"a":"a"}') - # logging.info(f"'{cookie_path}' 创建成功") - # else: - # logging.info(f"'{cookie_path}' 已存在,无需创建") - - with semaphore: - thread_name = threading.current_thread().name.split("-")[0] - with sync_playwright() as p: - self.browser = p.firefox.launch(headless=False) - # executable_path=self.path + self.chrome_path - cookie_list = self.find_file("cookie", "json") - - if not os.path.exists(cookie_path): - self.context = self.browser.new_context(storage_state=None, user_agent=self.ua) - else: - self.context = self.browser.new_context(storage_state=cookie_list[0], user_agent=self.ua) - self.page = self.context.new_page() - self.page.add_init_script("Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});") - self.page.goto("https://live.kuaishou.com/") - element = self.page.get_attribute('.no-login', "style") - if not element: - self.page.locator('.login').click() - self.page.locator('li.tab-panel:nth-child(2) > h4:nth-child(1)').click() - self.page.locator( - 'div.normal-login-item:nth-child(1) > div:nth-child(1) > input:nth-child(1)').fill( - self.phone) - try: - self.page.wait_for_selector("#app > section > div.header-placeholder > header > div.header-main > " - "div.right-part > div.user-info > div.tooltip-trigger > span", - timeout=1000 * 60 * 2) - if not os.path.exists(self.path + "\\cookie"): - os.makedirs(self.path + "\\cookie") - self.context.storage_state(path=cookie_path) - # 检测是否开播 - selector = "html body div#app div.live-room div.detail div.player " \ - "div.kwai-player.kwai-player-container.kwai-player-rotation-0 " \ - "div.kwai-player-container-video div.kwai-player-plugins div.center-state div.state " \ - "div.no-live-detail div.desc p.tip" # 检测正在直播时下播的选择器 - try: - msg = self.page.locator(selector).text_content(timeout=3000) - logging.info("当前%s" % thread_name + "," + msg) - self.context.close() - self.browser.close() - - except Exception as e: - logging.info("当前%s,[%s]正在直播" % (thread_name, lid)) - self.page.goto(self.uri + lid) - self.page.on("websocket", self.web_sockets) - self.page.wait_for_selector(selector, timeout=86400000) - logging.error("当前%s,[%s]的直播结束了" % (thread_name, lid)) - self.context.close() - self.browser.close() - - except Exception: - logging.info("登录失败") - self.context.close() - self.browser.close() - - def web_sockets(self, web_socket): - logging.info("web_sockets...") - urls = web_socket.url - logging.info(urls) - if '/websocket' in urls: - web_socket.on("close", self.websocket_close) - web_socket.on("framereceived", self.handler) - - def websocket_close(self): - self.context.close() - self.browser.close() - - def handler(self, websocket): - global global_idle_time - - Message = kuaishou_pb2.SocketMessage() - Message.ParseFromString(websocket) - if Message.payloadType == 310: - SCWebFeedPUsh = kuaishou_pb2.SCWebFeedPush() - SCWebFeedPUsh.ParseFromString(Message.payload) - obj = MessageToDict(SCWebFeedPUsh, preserving_proto_field_name=True) - - logging.debug(obj) - - if obj.get('commentFeeds', ''): - msg_list = obj.get('commentFeeds', '') - for i in msg_list: - # 闲时计数清零 - global_idle_time = 0 - - username = i['user']['userName'] - pid = i['user']['principalId'] - content = i['content'] - logging.info(f"[📧直播间弹幕消息] [{username}]:{content}") - - data = { - "platform": "快手", - "username": username, - "content": content - } - - my_handle.process_data(data, "comment") - if obj.get('giftFeeds', ''): - msg_list = obj.get('giftFeeds', '') - for i in msg_list: - username = i['user']['userName'] - # pid = i['user']['principalId'] - giftId = i['giftId'] - comboCount = i['comboCount'] - logging.info(f"[🎁直播间礼物消息] 用户:{username} 赠送礼物Id={giftId} 连击数={comboCount}") - if obj.get('likeFeeds', ''): - msg_list = obj.get('likeFeeds', '') - for i in msg_list: - username = i['user']['userName'] - pid = i['user']['principalId'] - logging.info(f"{username}") - - -class run(kslive): - def __init__(self): - super().__init__() - self.ids_list = self.live_ids.split(",") - - def run_live(self): - """ - 主程序入口 - :return: - """ - t_list = [] - # 允许的最大线程数 - if self.thread < 1: - self.thread = 1 - elif self.thread > 8: - self.thread = 8 - logging.info("线程最大允许8,线程数最好设置cpu核心数") - - semaphore = threading.Semaphore(self.thread) - # 用于记录数量 - n = 0 - if not self.live_ids: - logging.info("请导入网页直播id,多个以','间隔") - return - - for i in self.ids_list: - n += 1 - t = threading.Thread(target=kslive().main, args=(i, semaphore), name=f"线程:{n}-{i}") - t.start() - t_list.append(t) - for i in t_list: - i.join() - - -def start_server(): - global config, common, my_handle, last_username_list - - config_path = "config.json" - - config = Config(config_path) - common = Common() - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 定时任务 - def schedule_task(index): - global config, common, my_handle, last_username_list - - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - # 'user_num': last_liveroom_data["OnlineUserCount"], - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "快手", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # print(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(e) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "快手", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "快手", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - run().run_live() - - -if __name__ == '__main__': - start_server() - \ No newline at end of file diff --git a/ks_old.py b/ks_old.py deleted file mode 100644 index 9c330e50..00000000 --- a/ks_old.py +++ /dev/null @@ -1,320 +0,0 @@ - -import binascii -import json, os -import logging -import random -import re -import time -import websocket -import requests -import _thread -from protobuf_inspector.types import StandardParser -from google.protobuf import json_format - -from protobuf_inspector.types import StandardParser -from google.protobuf import json_format -from ks_pb2 import CSWebEnterRoom -from ks_pb2 import CSWebHeartbeat -from ks_pb2 import SocketMessage -from ks_pb2 import SCHeartbeatAck -from ks_pb2 import PayloadType -from ks_pb2 import SCWebFeedPush -from ks_pb2 import SCWebLiveWatchingUsers -from ks_pb2 import SCWebEnterRoomAck - -from utils.common import Common -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -common = Common() -# 日志文件路径 -file_path = "./log/log-" + common.get_bj_time(1) + ".txt" -Configure_logger(file_path) - -my_handle = My_handle("config.json") -if my_handle is None: - print("程序初始化失败!") - os._exit(0) - - -liveRoomId = None -ttwid = None -roomStore = None -liveRoomTitle = None -proxy = None - - -class Tool: - apiHost = 'https://live.kuaishou.com/live_graphql' - # 进入房间时需要的token - token = '' - # 网页token - cookie = '' - # websocket地址 - webSocketUrl = '' - # 房间号 - liveRoomId = None - # 直播网页地址 - liveUrl = '' - # 公共请求头 - headers = { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', - } - - # 初始化 - def init(self, liveUrl: str, cookie: str): - self.liveUrl = liveUrl - self.cookie = cookie - self.headers['Referer'] = self.liveUrl - self.headers['cookie'] = self.cookie - - # 获取房间号 - def getLiveRoomId(self): - liveUrl = self.liveUrl.strip('/') - st = liveUrl.split('/') - st = st[len(st) - 1] - res = requests.get(url=liveUrl, headers=self.headers) - # print(res.text) - userTag = '$ROOT_QUERY.webLiveDetail({\"authToken\":\"\",\"principalId\":\"' + st + '\"})' - ss = re.search( - r'_STATE__=(.*?);\(function\(\)\{var s;\(s=document\.currentScript\|\|document\.scripts\[document\.scripts\.length-1]\)\.parentNode\.r', - res.text) - text = ss.group(1) - text = json.loads(text) - self.liveRoomId = text['liveroom']['liveStream']['id'] - if self.liveRoomId == '': - raise RuntimeError('liveRoomId获取失败') - return self.liveRoomId - - # 获取直播websocket信息 - def getWebSocketInfo(self, liveRoomId): - res = requests.get("https://live.kuaishou.com/live_api/liveroom/websocketinfo?liveStreamId="+liveRoomId, headers=self.headers).json() - return res - - # 启动websocket服务 - def wssServerStart(self): - rid = self.getLiveRoomId() - wssInfo = self.getWebSocketInfo(rid) - self.token = wssInfo['data']['token'] - self.webSocketUrl = wssInfo['data']['websocketUrls'][0] - websocket.enableTrace(False) - # 创建一个长连接 - ws = websocket.WebSocketApp( - self.webSocketUrl, on_message=self.onMessage, on_error=self.onError, on_close=self.onClose, - on_open=self.onOpen - ) - ws.run_forever() - - def onMessage(self, ws: websocket.WebSocketApp, message: bytes): - wssPackage = SocketMessage() - wssPackage.ParseFromString(message) - - if wssPackage.payloadType == PayloadType.SC_ENTER_ROOM_ACK: - self.parseEnterRoomAckPack(wssPackage.payload) - return - - if wssPackage.payloadType == PayloadType.SC_HEARTBEAT_ACK: - self.parseHeartBeatPack(wssPackage.payload) - return - - if wssPackage.payloadType == PayloadType.SC_FEED_PUSH: - datas = self.parseFeedPushPack(wssPackage.payload) - - if "commentFeeds" not in datas: - return - - for data in datas['commentFeeds']: - content = data['content'] - user_name = data['user']['userName'] - - print(f"[{user_name}]: {content}") - - data = { - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - return - - if wssPackage.payloadType == PayloadType.SC_LIVE_WATCHING_LIST: - self.parseSCWebLiveWatchingUsers(wssPackage.payload) - return - - data = json_format.MessageToDict(wssPackage, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - logging.warn('[onMessage] [无法解析的数据包⚠️]' + log) - - def parseEnterRoomAckPack(self, message: bytes): - scWebEnterRoomAck = SCWebEnterRoomAck() - scWebEnterRoomAck.ParseFromString(message) - data = json_format.MessageToDict(scWebEnterRoomAck, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - logging.info('[parseEnterRoomAckPack] [进入房间成功ACK应答👌] [RoomId:' + self.liveRoomId + '] | ' + log) - return data - - # 进入直播间的用户 - def parseSCWebLiveWatchingUsers(self, message: bytes): - scWebLiveWatchingUsers = SCWebLiveWatchingUsers() - scWebLiveWatchingUsers.ParseFromString(message) - data = json_format.MessageToDict(scWebLiveWatchingUsers, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - logging.info('[parseSCWebLiveWatchingUsers] [不知道是啥的数据包🤷] [RoomId:' + self.liveRoomId + '] | ' + log) - return data - - # 直播间弹幕信息 - def parseFeedPushPack(self, message: bytes): - scWebFeedPush = SCWebFeedPush() - scWebFeedPush.ParseFromString(message) - data = json_format.MessageToDict(scWebFeedPush, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - logging.info('[parseFeedPushPack] [直播间弹幕🐎消息] [RoomId:' + self.liveRoomId + '] | ' + log) - return data - - def parseHeartBeatPack(self, message: bytes): - heartAckMsg = SCHeartbeatAck() - heartAckMsg.ParseFromString(message) - data = json_format.MessageToDict(heartAckMsg, preserving_proto_field_name=True) - log = json.dumps(data, ensure_ascii=False) - logging.info('[parseHeartBeatPack] [心跳❤️响应] [RoomId:' + self.liveRoomId + '] | ' + log) - return data - - def onError(self, ws, error): - logging.error('[Error] [websocket异常]') - - def onClose(self, ws): - logging.info('[Close] [websocket已关闭]') - - def onOpen(self, ws): - data = self.connectData() - logging.info('[onOpen] [建立wss连接]') - ws.send(data, websocket.ABNF.OPCODE_BINARY) - _thread.start_new_thread(self.keepHeartBeat, (ws,)) - - def connectData(self): - obj = CSWebEnterRoom() - obj.payloadType = 200 - obj.payload.token = self.token - obj.payload.liveStreamId = self.liveRoomId - obj.payload.pageId = self.getPageId() # pageId - data = obj.SerializeToString() # 序列化成二进制字符串 - return data - - # 封装心跳包 - def heartbeatData(self): - obj = CSWebHeartbeat() - obj.payloadType = 1 - obj.payload.timestamp = int(time.time() * 1000) - return obj.SerializeToString() - - # 发送心跳包 - def keepHeartBeat(self, ws: websocket.WebSocketApp): - while True: - # 20秒发一次心跳包 - time.sleep(20) - payload = self.heartbeatData() - logging.info("[keepHeartBeat] [发送心跳]") - ws.send(payload, websocket.ABNF.OPCODE_BINARY) - - def getPageId(self): - # js 中获取到该值的组成字符串 - charset = "-_zyxwvutsrqponmlkjihgfedcba9876543210ZYXWVUTSRQPONMLKJIHGFEDCBA" - pageId = "" - for _ in range(0, 16): - pageId += random.choice(charset) - pageId += "_" - pageId += str(int(time.time() * 1000)) - return pageId - - # content内容 liveStreamId房间号 color字幕颜色 - def sendMsg(self, content: str, liveStreamId=None, color=None): - variables = { - 'color': color, - 'content': content, - 'liveStreamId': liveStreamId - } - query = 'mutation SendLiveComment($liveStreamId: String, $content: String, $color: String) {\n sendLiveComment(liveStreamId: $liveStreamId, content: $content, color: $color) {\n result\n __typename\n }\n}\n' - return self.liveGraphql('SendLiveComment', variables, query) - - # 关注用户 principalId用户ID type 1关注 2取消关注 - def follow(self, principalId=None, type=1): - variables = { - 'principalId': principalId, - 'type': type, - } - query = 'mutation UserFollow($principalId: String, $type: Int) {\n webFollow(principalId: $principalId, type: $type) {\n followStatus\n __typename\n }\n}\n' - return self.liveGraphql('UserFollow', variables, query) - - # 获取用户基本信息 principalId用户ID - def getUserCardInfoById(self, principalId): - variables = { - 'principalId': principalId, - 'count': 3, - } - query = 'query UserCardInfoById($principalId: String, $count: Int) {\n userCardInfo(principalId: $principalId, count: $count) {\n id\n originUserId\n avatar\n name\n description\n sex\n constellation\n cityName\n followStatus\n privacy\n feeds {\n eid\n photoId\n thumbnailUrl\n timestamp\n __typename\n }\n counts {\n fan\n follow\n photo\n __typename\n }\n __typename\n }\n}\n' - return self.liveGraphql('UserCardInfoById', variables, query) - - # 获取所有礼物信息 - def getAllGifts(self): - variables = {} - query = 'query AllGifts {\n allGifts\n}\n' - return self.liveGraphql('AllGifts', variables, query) - - # 底层统一请求方法 - def liveGraphql(self, operationName: str, variables, query, headers=None): - if headers is None: - head = self.headers - head['content-type'] = 'application/json' - else: - head = headers - - data = { - 'operationName': operationName, - 'variables': variables, - 'query': query - } - res = requests.post(url=self.apiHost, data=json.dumps(data), headers=head).json() - logging.info('[liveGraphql] [操作返回数据] | ' + json.dumps(res, ensure_ascii=False)) - return res - - # 十六进制字符串转protobuf格式 (用于快手网页websocket调试分析包体结构) - def hexStrToProtobuf(self, hexStr): - # 示例数据 - # hexStr = '08d40210011aeb250a8e010a81010a0b66666731343535323939391206e68595e799bd1a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31322f32312f424d6a41794d6a45784d5449794d5441304d6a68664d6a67784d4445354e7a67344d5638795832686b4d6a6335587a49324d513d3d5f732e6a7067180120012a04323132300aa8010a9e010a0b79697869616f77753636361209e79fa5e5b08fe6ada61a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31302f30322f31392f424d6a41794d6a45774d4449784f544d304e4442664e6a497a4d4455324d445977587a4a66614751304e444a664d546b335f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a033430330ab6010aac010a0c4c31333130373432373635301212e9bb8ee699a8f09f8c8af09f8c8af09f8c8a1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31392f31332f424d6a41794d6a45784d546b784d7a4d784d5452664d5441344e5467794d5449334d5638795832686b4e444935587a45304d513d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a033335390ac2010ab8010a104757515053414148445244444145775a121ee69492e4b880e58fa3e8a28be6989fe6989fe98081e7bb99e58c97e699a81a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31322f32312f424d6a41794d6a45784d5449794d5445774d6a4e664d5445314d7a59354d4451324e6c38795832686b4e7a46664f5449355f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a033130330a91010a88010a0f337870323538746a6d6376337a62751209e58699e7949ce8af971a6a68747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f32302f31372f424d6a41794d6a45784d6a41784e7a4d784e5442664d7a45784d7a4d784f5445784e6c38795832686b4d545530587a49344d513d3d5f732e6a706718012a0233340aac010aa3010a0979756875616e306b64120ce88a8be594a4e1b587e1b69c1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30362f31312f30312f424d6a41794d6a41324d5445774d5451334e5452664d5441774d6a51324f4445344f4638795832686b4e444535587a457a4e773d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0233330a8a010a81010a0c6868686832303033313032391205e888aac2b71a6a68747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30332f32322f424d6a41794d6a45784d444d794d6a41784d546c664d6a4d7a4d7a45794d4455304d3138795832686b4e544133587a4d314d513d3d5f732e6a706718012a0233310aab010aa2010a0f3378686d3568677a6e657963666b6b1209e890a8e5bf853536391a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032302f30372f32342f30382f424d6a41794d4441334d6a51774f4449774d4442664d5467344e5455314d5449784e6c38795832686b4d6a4d79587a673d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0232330a93010a8a010a0f33787569663363746b6e6d38773271120be790aae790aa37313432321a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31382f31322f424d6a41794d6a45784d5467784d6a45784e4446664d7a45794f5441354f446b304e3138795832686b4e7a6730587a49344e773d3d5f732e6a706718012a0232320a94010a8b010a0f337862677a6a627034777570763567120cefbc87e7bbade99b86efbc821a6a68747470733a2f2f70352e612e7978696d67732e636f6d2f75686561642f41422f323032322f30382f33302f31352f424d6a41794d6a41344d7a41784e5451334e4452664d5441324d6a51334d6a41324e3138795832686b4f545533587a63314e413d3d5f732e6a706718012a0232320aa5010a9c010a0979796473696f73313212054c696b652e1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31382f31322f424d6a41794d6a45784d5467784d6a51324e4456664d6a4d354d4459774d5455344e5638785832686b4d546731587a51354d773d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0232310a90010a85010a0a48657969676530353230120be4bba5e6ad8c20f09f8e801a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31332f31302f424d6a41794d6a45784d544d784d4455354d6a4e664d544d324e6a41304e7a41774d5638785832686b4d6a6779587a51794f413d3d5f732e6a7067180120012a0231320aa8010a9f010a0f33786b623464793435706a797570711206e684a6e6829f1a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30342f32322f424d6a41794d6a45784d4451794d6a41774d445a664e4445794f446b324d545931587a4a66614751324e4456664f446b355f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0231310aa5010a9c010a08776a353431383830120ae88b8fe791bee699a82f1a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30332f30302f424d6a41794d6a45784d444d774d4449344e546c664d6a41354e6a45794e544d31587a4666614751304d7a6c664e444d785f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0231310abc010ab3010a0f33786334746a7a376e7875636561791216e7a9bfe5b1b1e794b2efbc88696b756ee59ba2efbc891a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30382f32362f31312f424d6a41794d6a41344d6a59784d5451334d4442664d6a4d314d7a41314d4449774d3138795832686b4e444d79587a4d7a4e673d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a0231300a8c010a84010a0957535162616f353230120fe5b08f20e5a88120e5b09120e380821a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31332f31392f424d6a41794d6a45784d544d784f5445354d5446664f5455314e4449304f445578587a4a666147517a4e5452664e4449785f732e6a706718012a01330a8f010a87010a0f337834646178626768746a78716979120ce7a78be8be9ee1b587e1b69c1a6668747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31342f31312f424d6a41794d6a45784d5451784d544d7a4d544a664e7a55354d7a63774d446b31587a4a66614751784f544e664e5459315f732e6a706718012a01330a8d010a85010a0f3378723937706975747768326d7a321206e791bee4b8811a6a68747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032312f30342f32332f32312f424d6a41794d5441304d6a4d794d5445304e544a664d5463794d7a55304f4463304d4638795832686b4f546378587a59354e513d3d5f732e6a706718012a01330ab0010aa8010a0d6c713431383835343138386868120de585b3e4ba8ee58a8920e5bcb71a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30352f32332f424d6a41794d6a45784d4455794d7a4d334e544a664d54517a4f44497a4e6a67784d6c38795832686b4d544578587a67324f413d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a01330a8b010a83010a0e4c544431353933313731353337371209e4bba5e6a4bfe383bb1a6668747470733a2f2f70332e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30372f31392f424d6a41794d6a45784d4463784f544d314d545a664d5441314d5463784e4459334e6c38795832686b4f444579587a55315f732e6a706718012a01330a89010a83010a0b64796c3230303830363130120ce5b08fe5b08fe5a79ce99c961a6668747470733a2f2f70342e612e7978696d67732e636f6d2f75686561642f41422f323032322f31302f31362f31312f424d6a41794d6a45774d5459784d5455354e5442664f4449304e7a517a4e545535587a4a66614751334d7a42664e5445335f732e6a70672a01320ab7010ab1010a0f3378727a33667a69737438717334611218e5bf98e5b79de38088e5b7b2e69c89e58584e5bc9fe380891a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31342f31312f424d6a41794d6a45784d5451784d54557a4d6a42664f4441304e444d794f545579587a4666614751304d7a52664d7a4d785f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320a90010a8a010a0e796f6e6773686974756f7a68616e1210e5b08fe58b87e5a3ab2de99988e8b68a1a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f30352f32382f31332f424d6a41794d6a41314d6a67784d7a51354d445a664e7a59304f44517a4d7a6b35587a4a66614751794d6a56664e544d7a5f732e6a70672a01320a8d010a87010a0f3378746d706b6167627536766e7139120ce5ad90e792a9e1b587e1b69c1a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31302f33312f31312f424d6a41794d6a45774d7a45784d544d784e4468664e6a45324d4445304d444d30587a4a66614751314e446c664d54497a5f732e6a70672a01320a89010a83010a0f4c4a483532304c4a31333134656d6f1204f09f88b71a6a68747470733a2f2f70352e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31342f31382f424d6a41794d6a45784d5451784f4441314d4442664d6a67354d4441774f5455314e6c38795832686b4d6a6730587a63794e513d3d5f732e6a70672a01320aa5010a9d010a0f33786967326675743533373872626b1204456e6d681a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f32332f31312f424d6a41794d6a45784d6a4d784d544d784e446c664d6a59324e7a517a4d4455334f5638785832686b4d7a4132587a6b315f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e73726318012a01320ab1010aab010a0f33786d39353268396a393473787a731212e5ad90e792a9efbc88e5b08fe58fb7efbc891a830168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f30372f30302f424d6a41794d6a45784d4463774d4451794e4452664d6a517a4d5463314d6a49354e5638795832686b4e5464664e7a49785f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320aa9010aa3010a0f3378397370326436703272727866391206e585b1e5928c1a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30372f32382f31322f424d6a41794d6a41334d6a67784d6a55314d7a42664d6a4d354e6a4d774e4455304d3138795832686b4e6a6b35587a45774e413d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320aa5010a9f010a0b4c5a5032303133313479611206e6b3bde4b8801a870168747470733a2f2f616c69696d672e612e7978696d67732e636f6d2f75686561642f41422f323032322f30372f31332f31362f424d6a41794d6a41334d544d784e6a49304e5456664d546b324e4445794f4463334f5638795832686b4f545179587a597a4d673d3d5f732e6a70674030655f306f5f306c5f3530685f3530775f3835712e7372632a01320a9f010a99010a0f3378326e37793865656573766b6879121ee58c97e699a8e79a84e4bfa1e699baefbc88e5b7b2e7b4abe7a082efbc891a6668747470733a2f2f70312e612e7978696d67732e636f6d2f75686561642f41422f323032322f31312f31332f31312f424d6a41794d6a45784d544d784d5441344d6a68664d5467344d44497a4f5441354e5638785832686b4f446b79587a45795f732e6a70672a013220a699a0ebca30' - with open('t-proto', 'wb') as w: - w.write(binascii.unhexlify(hexStr)) - - parser = StandardParser() - with open('t-proto', 'rb') as fh: - output = parser.parse_message(fh, 'message') - print(output) - return output - - # 把十六进制字符串转成ascii编码格式 (用于快手网页websocket调试分析包体结构) - def unHexLify(self, data: str): - # 示例数据 - # data = 'E5 8C 97 E6 99 A8 E7 9A 84 E4 BF A1 E6 99 BA EF BC 88 E5 B7 B2 E7 B4 AB' - data.replace(' ', '') - data = binascii.unhexlify(data).decode() - print(data) - return data - - -try: - ks = Tool() - # ss='08a04e10828298ced230180520012a1b0a07582d426f67757312105273693578484a6f7859364a2f7636543a02706242a808086410a04e1a0c302e372e332d626574612e342200280330003a0e666562376331343a6d617374657242f102a206ed020a1d303a313a3130333437313539333530383a313033343731353933353038100118a48481d688ddbbbb6322407b226d656e74696f6e5f7573657273223a5b5d2c2261776554797065223a3730302c227269636854657874496e666f73223a5b5d2c2274657874223a2233227d2a150a11733a6d656e74696f6e65645f757365727312002a3b0a13733a636c69656e745f6d6573736167655f6964122465653931663962312d396165662d346566612d623631622d61336230373266383933373330073a81013248626877434843544d544d6c37366372416a6b57784f315a4749686f5a6341646e6337316567324a3268496746504454324e454c4f62354b39477446476a656a496b716657494e564b4458716d56334d4974337335766a554c5a74596d305246304551487a6261627a795548435643503770674a6a6c7a79484c6776364c6d69422465653931663962312d396165662d346566612d623631622d6133623037326638393337334a01305a09646f7579696e5f70637a130a0b73657373696f6e5f6169641204363338337a100a0b73657373696f6e5f6469641201307a150a086170705f6e616d651209646f7579696e5f70637a150a0f7072696f726974795f726567696f6e1202636e7a83010a0a757365725f6167656e7412754d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f3729204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3130382e302e302e30205361666172692f3533372e33367a160a0e636f6f6b69655f656e61626c65641204747275657a160a1062726f777365725f6c616e677561676512027a687a1c0a1062726f777365725f706c6174666f726d12084d6163496e74656c7a170a0c62726f777365725f6e616d6512074d6f7a696c6c617a80010a0f62726f777365725f76657273696f6e126d352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f3729204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3130382e302e302e30205361666172692f3533372e33367a160a0e62726f777365725f6f6e6c696e651204747275657a140a0c73637265656e5f77696474681204313434307a140a0d73637265656e5f68656967687412033930307a0b0a077265666572657212007a1e0a0d74696d657a6f6e655f6e616d65120d417369612f5368616e676861697a0d0a0864657669636549641201307a1c0a057765626964121337313339333931353538393134333933363132900101aa010a646f7579696e5f776562b201077765625f73646b' - # ks.hexStrToProtobuf(ss) - c ='clientid=3; didv=1675056349580; userId=845495460; kuaishou.live.bfb1s=7206d814e5c089a58c910ed8bf52ace5; did=web_a41c846314016ca1a260444bb3c7d66c; client_key=65890b29; kpn=GAME_ZONE; _did=web_117262730815E9F7; kuaishou.live.web_st=ChRrdWFpc2hvdS5saXZlLndlYi5zdBKgAQkoEnsRiD0ovFwIQ828tvYMhmH6rThiUxM-uuTQtXKjmQEry1dCvI5sEsH9SZt9LNWcvJ_kNRPH2AFvS1awpa65z-Jpe3p2nbMvkpraiJV0WkJrvhLrCyb_CTCNPBGoYwUBaDoabrmZLqLJX-txGbrmUDIblQmR-MKwbPb7uQ5MszR2O3jaon_MtIrqnQA7e0IOBVmJT8N_p-lsiclN4NsaEsa__TMaP0jJgfAfW0kccZcKPyIgmgfFxb6YcCH2fKNK5CO2G4OWyK-WxFeXx6Bx8LA1FGcoBTAB; kuaishou.live.web_ph=8d652450751eaf1d2b61edf08b812bb0a41a; userId=845495460; ksliveShowClipTip=true' - room_id = my_handle.get_room_id() - url = 'https://live.kuaishou.com/u/' + room_id - print(f"监听直播间:{url}") - # ks.init(url, c) - # ks.init('https://live.kuaishou.com/u/3x7cff8hm8b9uwi', c) - # 启动快手ws客户端 - ks.wssServerStart() -except KeyboardInterrupt: - print('程序被强行退出') -finally: - print('关闭连接...可能是直播已经结束或网络问题可能是直播间不存在或下播或网络问题') - os._exit(0) \ No newline at end of file diff --git a/requirements_common.txt b/requirements_common.txt index 4f654cb0..2fecbe7b 100644 --- a/requirements_common.txt +++ b/requirements_common.txt @@ -150,7 +150,7 @@ flask_socketio==5.3.6 emoji==2.9.0 google-generativeai==0.3.1 colorlog==6.8.0 -faster_whisper==0.10.0 +faster_whisper==1.0.0 pygtrans==1.5.3 jieba==0.42.1 gradio==4.16.0 diff --git a/talk.py b/talk.py deleted file mode 100644 index 072081c6..00000000 --- a/talk.py +++ /dev/null @@ -1,753 +0,0 @@ -import logging, time -import threading -import sys, os -import random -import schedule -import traceback -import asyncio -import copy -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -import keyboard -import pyaudio -import wave -import numpy as np -import speech_recognition as sr -from aip import AipSpeech -import signal - -from utils.common import Common -from utils.logger import Configure_logger -from utils.config import Config -from utils.my_handle import My_handle - -config = None -common = None -my_handle = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - -def start_server(): - global config, common, my_handle - global thread, do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - thread = None - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = threading.Event() - - # 最新入场的用户名列表 - last_username_list = [""] - - common = Common() - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - config_path = "config.json" - config = Config(config_path) - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - exit(0) - - # 冷却时间 0.5 秒 - cooldown = 0.5 - last_pressed = 0 - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - """ - 按键监听板块 - """ - # 录音功能(录音时间过短进入openai的语音转文字会报错,请一定注意) - def record_audio(): - pressdown_num = 0 - CHUNK = 1024 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 44100 - WAVE_OUTPUT_FILENAME = "out/record.wav" - p = pyaudio.PyAudio() - stream = p.open(format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) - frames = [] - print("Recording...") - flag = 0 - while 1: - while keyboard.is_pressed('RIGHT_SHIFT'): - flag = 1 - data = stream.read(CHUNK) - frames.append(data) - pressdown_num = pressdown_num + 1 - if flag: - break - print("Stopped recording.") - stream.stop_stream() - stream.close() - p.terminate() - wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') - wf.setnchannels(CHANNELS) - wf.setsampwidth(p.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - wf.close() - if pressdown_num >= 5: # 粗糙的处理手段 - return 1 - else: - print("杂鱼杂鱼,好短好短(录音时间过短,按右shift重新录制)") - return 0 - - - # THRESHOLD 设置音量阈值,默认值800.0,根据实际情况调整 silence_threshold 设置沉默阈值,根据实际情况调整 - def audio_listen(volume_threshold=800.0, silence_threshold=15): - audio = pyaudio.PyAudio() - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 16000 - CHUNK = 1024 - - stream = audio.open( - format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK, - input_device_index=int(config.get("talk", "device_index")) - ) - - frames = [] # 存储录制的音频帧 - - is_speaking = False # 是否在说话 - silent_count = 0 # 沉默计数 - speaking_flag = False #录入标志位 不重要 - - while True: - # 读取音频数据 - data = stream.read(CHUNK) - audio_data = np.frombuffer(data, dtype=np.short) - max_dB = np.max(audio_data) - # print(max_dB) - if max_dB > volume_threshold: - is_speaking = True - silent_count = 0 - elif is_speaking is True: - silent_count += 1 - - if is_speaking is True: - frames.append(data) - if speaking_flag is False: - logging.info("[录入中……]") - speaking_flag = True - - if silent_count >= silence_threshold: - break - - logging.info("[语音录入完成]") - - # 将音频保存为WAV文件 - '''with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames))''' - return frames - - - # 执行录音、识别&提交 - def do_listen_and_comment(status=True): - global stop_do_listen_and_comment_thread_event - - config = Config(config_path) - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - while True: - try: - # 检查是否收到停止事件 - if stop_do_listen_and_comment_thread_event.is_set(): - logging.info(f'停止录音~') - break - - config = Config(config_path) - - # 根据接入的语音识别类型执行 - if "baidu" == config.get("talk", "type"): - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'baidu_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/baidu_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # 读取音频文件 - with open(WAVE_OUTPUT_FILENAME, 'rb') as fp: - audio = fp.read() - - # 初始化 AipSpeech 对象 - baidu_client = AipSpeech(config.get("talk", "baidu", "app_id"), config.get("talk", "baidu", "api_key"), config.get("talk", "baidu", "secret_key")) - - # 识别音频文件 - res = baidu_client.asr(audio, 'wav', 16000, { - 'dev_pid': 1536, - }) - if res['err_no'] == 0: - content = res['result'][0] - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - else: - logging.error(f"百度接口报错:{res}") - elif "google" == config.get("talk", "type"): - # 创建Recognizer对象 - r = sr.Recognizer() - - try: - # 打开麦克风进行录音 - with sr.Microphone() as source: - logging.info(f'录音中...') - # 从麦克风获取音频数据 - audio = r.listen(source) - logging.info("成功录制") - - # 进行谷歌实时语音识别 en-US zh-CN ja-JP - content = r.recognize_google(audio, language=config.get("talk", "google", "tgt_lang")) - - # 输出识别结果 - # logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - except sr.UnknownValueError: - logging.warning("无法识别输入的语音") - except sr.RequestError as e: - logging.error("请求出错:" + str(e)) - elif "faster_whisper" == config.get("talk", "type"): - from faster_whisper import WhisperModel - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'faster_whisper_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/faster_whisper_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # Run on GPU with FP16 - model = WhisperModel(model_size_or_path=config.get("talk", "faster_whisper", "model_size"), \ - device=config.get("talk", "faster_whisper", "device"), \ - compute_type=config.get("talk", "faster_whisper", "compute_type"), \ - download_root=config.get("talk", "faster_whisper", "download_root")) - - segments, info = model.transcribe(WAVE_OUTPUT_FILENAME, beam_size=config.get("talk", "faster_whisper", "beam_size")) - - logging.debug("识别语言为:'%s',概率:%f" % (info.language, info.language_probability)) - - content = "" - for segment in segments: - logging.info("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text)) - content += segment.text + "。" - - if content == "": - return - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - - if not status: - return - except Exception as e: - logging.error(traceback.format_exc()) - - - def on_key_press(event): - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - # if event.name in ['z', 'Z', 'c', 'C'] and keyboard.is_pressed('ctrl'): - # print("退出程序") - - # os._exit(0) - - # 按键CD - current_time = time.time() - if current_time - last_pressed < cooldown: - return - - - """ - 触发按键部分的判断 - """ - trigger_key_lower = None - stop_trigger_key_lower = None - - # trigger_key是字母, 整个小写 - if trigger_key.isalpha(): - trigger_key_lower = trigger_key.lower() - - # stop_trigger_key是字母, 整个小写 - if stop_trigger_key.isalpha(): - stop_trigger_key_lower = stop_trigger_key.lower() - - if trigger_key_lower: - if event.name == trigger_key or event.name == trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key or event.name == stop_trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - else: - if event.name == trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - - # 是否启用连续对话模式 - if config.get("talk", "continuous_talk"): - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(True,)) - do_listen_and_comment_thread.start() - else: - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(False,)) - do_listen_and_comment_thread.start() - - - # 按键监听 - def key_listener(): - # 注册按键按下事件的回调函数 - keyboard.on_press(on_key_press) - - try: - # 进入监听状态,等待按键按下 - keyboard.wait() - except KeyboardInterrupt: - os._exit(0) - - - # 从配置文件中读取触发键的字符串配置 - trigger_key = config.get("talk", "trigger_key") - stop_trigger_key = config.get("talk", "stop_trigger_key") - - if config.get("talk", "key_listener_enable"): - logging.info(f'单击键盘 {trigger_key} 按键进行录音喵~ 由于其他任务还要启动,如果按键没有反应,请等待一段时间') - - # 创建并启动按键监听线程 - thread = threading.Thread(target=key_listener) - thread.start() - - # 定时任务 - def schedule_task(index): - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "聊天模式", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - global config - - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # logging.info(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - logging.debug(f"copywriting_file_path_list={copywriting_file_path_list}") - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - logging.debug(f'copywriting_file_content={copywriting_file_content},content={data_json["content"]}') - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - logging.debug("切换到文案触发模式") - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "聊天模式", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or (not config.get("idle_time_task", "comment", "enable")): - logging.debug("切换到本地音频模式") - - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - logging.debug(f"local_audio_path={local_audio_path}") - - # 发送给处理函数 - data = { - "platform": "聊天模式", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - # 起飞 - # audio_listen_google() - - -# 退出程序 -def exit_handler(signum, frame): - print("Received signal:", signum) - - # threading.current_thread().exit() - - # os._exit(0) - - -if __name__ == '__main__': - # 键盘监听线程 - thread = None - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = None - - signal.signal(signal.SIGINT, exit_handler) - signal.signal(signal.SIGTERM, exit_handler) - - start_server() - - thread.join() # 等待子线程退出 - - os._exit(0) diff --git a/tiktok.py b/tiktok.py deleted file mode 100644 index db3b2191..00000000 --- a/tiktok.py +++ /dev/null @@ -1,910 +0,0 @@ -import websocket -import json, logging, os -import time -import threading -import schedule -import random -import asyncio -import traceback -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -from TikTokLive import TikTokLiveClient -from TikTokLive.types.events import CommentEvent, ConnectEvent, DisconnectEvent, JoinEvent, GiftEvent, FollowEvent -from TikTokLive.types.errors import LiveNotFound - -# 按键监听语音聊天板块 -import keyboard -import pyaudio -import wave -import numpy as np -import speech_recognition as sr -from aip import AipSpeech -import signal -import time - -from utils.common import Common -from utils.logger import Configure_logger -from utils.my_handle import My_handle -from utils.config import Config - - -config = None -config_path = None -common = None -my_handle = None -last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -def start_server(): - global config, common, my_handle, last_liveroom_data, last_username_list, config_path, global_idle_time - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - - # 按键监听相关 - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = threading.Event() - # 冷却时间 0.5 秒 - cooldown = 0.5 - last_pressed = 0 - - config_path = "config.json" - - config = Config(config_path) - common = Common() - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 获取 httpx 库的日志记录器 - httpx_logger = logging.getLogger("httpx") - # 设置 httpx 日志记录器的级别为 WARNING - httpx_logger.setLevel(logging.WARNING) - - # 最新的直播间数据 - last_liveroom_data = { - 'OnlineUserCount': 0, - 'TotalUserCount': 0, - 'TotalUserCountStr': '0', - 'OnlineUserCountStr': '0', - 'MsgId': 0, - 'User': None, - 'Content': '当前直播间人数 0,累计直播间人数 0', - 'RoomId': 0 - } - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - """ - 按键监听板块 - """ - # 录音功能(录音时间过短进入openai的语音转文字会报错,请一定注意) - def record_audio(): - pressdown_num = 0 - CHUNK = 1024 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 44100 - WAVE_OUTPUT_FILENAME = "out/record.wav" - p = pyaudio.PyAudio() - stream = p.open(format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK) - frames = [] - logging.info("Recording...") - flag = 0 - while 1: - while keyboard.is_pressed('RIGHT_SHIFT'): - flag = 1 - data = stream.read(CHUNK) - frames.append(data) - pressdown_num = pressdown_num + 1 - if flag: - break - logging.info("Stopped recording.") - stream.stop_stream() - stream.close() - p.terminate() - wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') - wf.setnchannels(CHANNELS) - wf.setsampwidth(p.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - wf.close() - if pressdown_num >= 5: # 粗糙的处理手段 - return 1 - else: - logging.info("杂鱼杂鱼,好短好短(录音时间过短,按右shift重新录制)") - return 0 - - - # THRESHOLD 设置音量阈值,默认值800.0,根据实际情况调整 silence_threshold 设置沉默阈值,根据实际情况调整 - def audio_listen(volume_threshold=800.0, silence_threshold=15): - audio = pyaudio.PyAudio() - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = 1 - RATE = 16000 - CHUNK = 1024 - - stream = audio.open( - format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK, - input_device_index=int(config.get("talk", "device_index")) - ) - - frames = [] # 存储录制的音频帧 - - is_speaking = False # 是否在说话 - silent_count = 0 # 沉默计数 - speaking_flag = False #录入标志位 不重要 - - while True: - # 读取音频数据 - data = stream.read(CHUNK) - audio_data = np.frombuffer(data, dtype=np.short) - max_dB = np.max(audio_data) - # logging.info(max_dB) - if max_dB > volume_threshold: - is_speaking = True - silent_count = 0 - elif is_speaking is True: - silent_count += 1 - - if is_speaking is True: - frames.append(data) - if speaking_flag is False: - logging.info("[录入中……]") - speaking_flag = True - - if silent_count >= silence_threshold: - break - - logging.info("[语音录入完成]") - - # 将音频保存为WAV文件 - '''with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames))''' - return frames - - - # 执行录音、识别&提交 - def do_listen_and_comment(status=True): - global stop_do_listen_and_comment_thread_event - - config = Config(config_path) - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - while True: - try: - # 检查是否收到停止事件 - if stop_do_listen_and_comment_thread_event.is_set(): - logging.info(f'停止录音~') - break - - config = Config(config_path) - - # 根据接入的语音识别类型执行 - if "baidu" == config.get("talk", "type"): - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'baidu_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/baidu_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # 读取音频文件 - with open(WAVE_OUTPUT_FILENAME, 'rb') as fp: - audio = fp.read() - - # 初始化 AipSpeech 对象 - baidu_client = AipSpeech(config.get("talk", "baidu", "app_id"), config.get("talk", "baidu", "api_key"), config.get("talk", "baidu", "secret_key")) - - # 识别音频文件 - res = baidu_client.asr(audio, 'wav', 16000, { - 'dev_pid': 1536, - }) - if res['err_no'] == 0: - content = res['result'][0] - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - else: - logging.error(f"百度接口报错:{res}") - elif "google" == config.get("talk", "type"): - # 创建Recognizer对象 - r = sr.Recognizer() - - try: - # 打开麦克风进行录音 - with sr.Microphone() as source: - logging.info(f'录音中...') - # 从麦克风获取音频数据 - audio = r.listen(source) - logging.info("成功录制") - - # 进行谷歌实时语音识别 en-US zh-CN ja-JP - content = r.recognize_google(audio, language=config.get("talk", "google", "tgt_lang")) - - # 输出识别结果 - # logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - except sr.UnknownValueError: - logging.warning("无法识别输入的语音") - except sr.RequestError as e: - logging.error("请求出错:" + str(e)) - elif "faster_whisper" == config.get("talk", "type"): - from faster_whisper import WhisperModel - - # 设置音频参数 - FORMAT = pyaudio.paInt16 - CHANNELS = config.get("talk", "CHANNELS") - RATE = config.get("talk", "RATE") - - audio_out_path = config.get("play_audio", "out_path") - - if not os.path.isabs(audio_out_path): - if not audio_out_path.startswith('./'): - audio_out_path = './' + audio_out_path - file_name = 'faster_whisper_' + common.get_bj_time(4) + '.wav' - WAVE_OUTPUT_FILENAME = common.get_new_audio_path(audio_out_path, file_name) - # WAVE_OUTPUT_FILENAME = './out/faster_whisper_' + common.get_bj_time(4) + '.wav' - - frames = audio_listen(config.get("talk", "volume_threshold"), config.get("talk", "silence_threshold")) - - # 将音频保存为WAV文件 - with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: - wf.setnchannels(CHANNELS) - wf.setsampwidth(pyaudio.get_sample_size(FORMAT)) - wf.setframerate(RATE) - wf.writeframes(b''.join(frames)) - - # Run on GPU with FP16 - model = WhisperModel(model_size_or_path=config.get("talk", "faster_whisper", "model_size"), \ - device=config.get("talk", "faster_whisper", "device"), \ - compute_type=config.get("talk", "faster_whisper", "compute_type"), \ - download_root=config.get("talk", "faster_whisper", "download_root")) - - segments, info = model.transcribe(WAVE_OUTPUT_FILENAME, beam_size=config.get("talk", "faster_whisper", "beam_size")) - - logging.debug("识别语言为:'%s',概率:%f" % (info.language, info.language_probability)) - - content = "" - for segment in segments: - logging.info("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text)) - content += segment.text + "。" - - if content == "": - return - - # 输出识别结果 - logging.info("识别结果:" + content) - user_name = config.get("talk", "username") - - data = { - "platform": "本地聊天", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "talk") - - if not status: - return - except Exception as e: - logging.error(traceback.format_exc()) - - - def on_key_press(event): - global do_listen_and_comment_thread, stop_do_listen_and_comment_thread_event - - # 是否启用按键监听,不启用的话就不用执行了 - if False == config.get("talk", "key_listener_enable"): - return - - # if event.name in ['z', 'Z', 'c', 'C'] and keyboard.is_pressed('ctrl'): - # logging.info("退出程序") - - # os._exit(0) - - # 按键CD - current_time = time.time() - if current_time - last_pressed < cooldown: - return - - - """ - 触发按键部分的判断 - """ - trigger_key_lower = None - stop_trigger_key_lower = None - - # trigger_key是字母, 整个小写 - if trigger_key.isalpha(): - trigger_key_lower = trigger_key.lower() - - # stop_trigger_key是字母, 整个小写 - if stop_trigger_key.isalpha(): - stop_trigger_key_lower = stop_trigger_key.lower() - - if trigger_key_lower: - if event.name == trigger_key or event.name == trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key or event.name == stop_trigger_key_lower: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - else: - if event.name == trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将开始录音~') - elif event.name == stop_trigger_key: - logging.info(f'检测到单击键盘 {event.name},即将停止录音~') - stop_do_listen_and_comment_thread_event.set() - return - else: - return - - # 是否启用连续对话模式 - if config.get("talk", "continuous_talk"): - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(True,)) - do_listen_and_comment_thread.start() - else: - stop_do_listen_and_comment_thread_event.clear() - do_listen_and_comment_thread = threading.Thread(target=do_listen_and_comment, args=(False,)) - do_listen_and_comment_thread.start() - - - # 按键监听 - def key_listener(): - # 注册按键按下事件的回调函数 - keyboard.on_press(on_key_press) - - try: - # 进入监听状态,等待按键按下 - keyboard.wait() - except KeyboardInterrupt: - os._exit(0) - - - # 从配置文件中读取触发键的字符串配置 - trigger_key = config.get("talk", "trigger_key") - stop_trigger_key = config.get("talk", "stop_trigger_key") - - if config.get("talk", "key_listener_enable"): - logging.info(f'单击键盘 {trigger_key} 按键进行录音喵~ 由于其他任务还要启动,如果按键没有反应,请等待一段时间') - - # 创建并启动按键监听线程 - thread = threading.Thread(target=key_listener) - thread.start() - - - # 定时任务 - def schedule_task(index): - global config, common, my_handle, last_liveroom_data, last_username_list - - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': last_liveroom_data["OnlineUserCount"], - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "tiktok", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # logging.info(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "tiktok", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "tiktok", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - # if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - """ - tiktok - """ - # 比如直播间是 https://www.tiktok.com/@username/live 那么room_id就是 username,其实就是用户唯一ID - room_id = my_handle.get_room_id() - - # 代理软件开启TUN模式进行代理,由于库的ws不走传入的代理参数,只能靠代理软件全代理了 - client: TikTokLiveClient = TikTokLiveClient(unique_id=f"@{room_id}", proxies=None) - - # Define how you want to handle specific events via decorator - @client.on("connect") - async def on_connect(_: ConnectEvent): - logging.info(f"连接到 房间ID:{client.room_id}") - - @client.on("disconnect") - async def on_disconnect(event: DisconnectEvent): - logging.info("断开连接") - - @client.on("join") - async def on_join(event: JoinEvent): - user_name = event.user.nickname - unique_id = event.user.unique_id - - logging.info(f'[🚹🚺直播间成员加入消息] 欢迎 {user_name} 进入直播间') - - data = { - "platform": "tiktok", - "username": user_name, - "content": "进入直播间" - } - - # 添加用户名到最新的用户名列表 - add_username_to_last_username_list(user_name) - - my_handle.process_data(data, "entrance") - - # Notice no decorator? - @client.on("comment") - async def on_comment(event: CommentEvent): - # 闲时计数清零 - global_idle_time = 0 - - user_name = event.user.nickname - content = event.comment - - logging.info(f'[📧直播间弹幕消息] [{user_name}]:{content}') - - data = { - "platform": "tiktok", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - @client.on("gift") - async def on_gift(event: GiftEvent): - """ - This is an example for the "gift" event to show you how to read gift data properly. - - Important Note: - - Gifts of type 1 can have streaks, so we need to check that the streak has ended - If the gift type isn't 1, it can't repeat. Therefore, we can go straight to logging.infoing - - """ - - # Streakable gift & streak is over - if event.gift.streakable and not event.gift.streaking: - # 礼物重复数量 - repeat_count = event.gift.count - - # Non-streakable gift - elif not event.gift.streakable: - # 礼物重复数量 - repeat_count = 1 - - gift_name = event.gift.info.name - user_name = event.user.nickname - # 礼物数量 - num = 1 - - - try: - # 暂时是写死的 - data_path = "data/tiktok礼物价格表.json" - - # 读取JSON文件 - with open(data_path, "r", encoding="utf-8") as file: - # 解析JSON数据 - data_json = json.load(file) - - if gift_name in data_json: - # 单个礼物金额 需要自己维护礼物价值表 - discount_price = data_json[gift_name] - else: - logging.warning(f"数据文件:{data_path} 中,没有 {gift_name} 对应的价值,请手动补充数据") - discount_price = 1 - except Exception as e: - logging.error(traceback.format_exc()) - discount_price = 1 - - - # 总金额 - combo_total_coin = repeat_count * discount_price - - logging.info(f'[🎁直播间礼物消息] 用户:{user_name} 赠送 {num} 个 {gift_name},单价 {discount_price}抖币,总计 {combo_total_coin}抖币') - - data = { - "platform": "tiktok", - "gift_name": gift_name, - "username": user_name, - "num": num, - "unit_price": discount_price / 10, - "total_price": combo_total_coin / 10 - } - - my_handle.process_data(data, "gift") - - @client.on("follow") - async def on_follow(event: FollowEvent): - user_name = event.user.nickname - - logging.info(f'[➕直播间关注消息] 感谢 {user_name} 的关注') - - data = { - "platform": "tiktok", - "username": user_name - } - - my_handle.process_data(data, "follow") - - try: - client.run() - - except LiveNotFound: - logging.info(f"用户ID: @{client.unique_id} 好像不在线捏, 1分钟后重试...") - -# 退出程序 -def exit_handler(signum, frame): - logging.info(f"Received signal:{signum}") - - os._exit(0) - -if __name__ == '__main__': - # 按键监听相关 - do_listen_and_comment_thread = None - stop_do_listen_and_comment_thread_event = None - - signal.signal(signal.SIGINT, exit_handler) - signal.signal(signal.SIGTERM, exit_handler) - - start_server() - \ No newline at end of file diff --git a/twitch.py b/twitch.py deleted file mode 100644 index 508e81e3..00000000 --- a/twitch.py +++ /dev/null @@ -1,496 +0,0 @@ -import socks -from emoji import demojize - -import logging, os -import threading -import schedule -import random -import asyncio -import traceback -import re -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -from utils.common import Common -from utils.config import Config -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -""" - ___ _ - |_ _| | ____ _ _ __ ___ ___ - | || |/ / _` | '__/ _ \/ __| - | || < (_| | | | (_) \__ \ - |___|_|\_\__,_|_| \___/|___/ - -""" - -config = None -common = None -my_handle = None -# last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -# 点火起飞 -def start_server(): - global config, common, my_handle, last_username_list - global global_idle_time - - config_path = "config.json" - - common = Common() - config = Config(config_path) - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 获取 httpx 库的日志记录器 - httpx_logger = logging.getLogger("httpx") - # 设置 httpx 日志记录器的级别为 WARNING - httpx_logger.setLevel(logging.WARNING) - - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - # 定时任务 - def schedule_task(index): - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "YouTube", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - global config - - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # logging.info(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "twitch", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "twitch", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - try: - server = 'irc.chat.twitch.tv' - port = 6667 - nickname = '主人' - - try: - channel = '#' + config.get("room_display_id") # 要从中检索消息的频道,注意#必须携带在头部 The channel you want to retrieve messages from - token = config.get("twitch", "token") # 访问 https://twitchapps.com/tmi/ 获取 - user = config.get("twitch", "user") # 你的Twitch用户名 Your Twitch username - # 代理服务器的地址和端口 - proxy_server = config.get("twitch", "proxy_server") - proxy_port = int(config.get("twitch", "proxy_port")) - except Exception as e: - logging.error(traceback.format_exc()) - logging.error("获取Twitch配置失败!\n{0}".format(e)) - my_handle.abnormal_alarm_handle("platform") - - # 配置代理服务器 - socks.set_default_proxy(socks.HTTP, proxy_server, proxy_port) - - # 创建socket对象 - sock = socks.socksocket() - - try: - sock.connect((server, port)) - logging.info("成功连接 Twitch IRC server") - except Exception as e: - logging.error(traceback.format_exc()) - logging.error(f"连接 Twitch IRC server 失败: {e}") - my_handle.abnormal_alarm_handle("platform") - - - sock.send(f"PASS {token}\n".encode('utf-8')) - sock.send(f"NICK {nickname}\n".encode('utf-8')) - sock.send(f"JOIN {channel}\n".encode('utf-8')) - - regex = r":(\w+)!\w+@\w+\.tmi\.twitch\.tv PRIVMSG #\w+ :(.+)" - - # 重连次数 - retry_count = 0 - - while True: - try: - resp = sock.recv(2048).decode('utf-8') - - # 输出所有接收到的内容,包括PING/PONG - # logging.info(resp) - - if resp.startswith('PING'): - sock.send("PONG\n".encode('utf-8')) - - elif not user in resp: - # 闲时计数清零 - global_idle_time = 0 - - resp = demojize(resp) - - logging.debug(resp) - - match = re.match(regex, resp) - - user_name = match.group(1) - content = match.group(2) - content = content.rstrip() - - logging.info(f"[{user_name}]: {content}") - - data = { - "platform": "twitch", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - except AttributeError as e: - logging.error(traceback.format_exc()) - logging.error(f"捕获到异常: {e}") - logging.error("发生异常,重新连接socket") - my_handle.abnormal_alarm_handle("platform") - - if retry_count >= 3: - logging.error(f"多次重连失败,程序结束!") - return - - retry_count += 1 - logging.error(f"重试次数: {retry_count}") - - # 在这里添加重新连接socket的代码 - # 例如,你可能想要关闭旧的socket连接,然后重新创建一个新的socket连接 - sock.close() - - # 创建socket对象 - sock = socks.socksocket() - - try: - sock.connect((server, port)) - logging.info("成功连接 Twitch IRC server") - except Exception as e: - logging.error(f"连接 Twitch IRC server 失败: {e}") - - sock.send(f"PASS {token}\n".encode('utf-8')) - sock.send(f"NICK {nickname}\n".encode('utf-8')) - sock.send(f"JOIN {channel}\n".encode('utf-8')) - except Exception as e: - logging.error(traceback.format_exc()) - logging.error("Error receiving chat: {0}".format(e)) - my_handle.abnormal_alarm_handle("platform") - except Exception as e: - logging.error(traceback.format_exc()) - my_handle.abnormal_alarm_handle("platform") - - -if __name__ == '__main__': - start_server() diff --git a/wxlive.py b/wxlive.py deleted file mode 100644 index ff2718b7..00000000 --- a/wxlive.py +++ /dev/null @@ -1,443 +0,0 @@ -import logging, os -import threading -import schedule -import random -import asyncio -import traceback -import copy - -from functools import partial - -from typing import * - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -from utils.common import Common -from utils.config import Config -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -config = None -common = None -my_handle = None -# last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - -# 点火起飞 -def start_server(): - global config, common, my_handle, last_username_list - - # 配置文件路径 - config_path = "config.json" - - common = Common() - config = Config(config_path) - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 获取 httpx 库的日志记录器 - httpx_logger = logging.getLogger("httpx") - # 设置 httpx 日志记录器的级别为 WARNING - httpx_logger.setLevel(logging.WARNING) - - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - # 定时任务 - def schedule_task(index): - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "微信视频号", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - global config - - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # logging.info(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - logging.debug(f"copywriting_file_path_list={copywriting_file_path_list}") - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - logging.debug(f'copywriting_file_content={copywriting_file_content},content={data_json["content"]}') - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - logging.debug("切换到文案触发模式") - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "哔哩哔哩2", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or (not config.get("idle_time_task", "comment", "enable")): - logging.debug("切换到本地音频模式") - - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - logging.debug(f"local_audio_path={local_audio_path}") - - # 发送给处理函数 - data = { - "platform": "哔哩哔哩2", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - app = Flask(__name__) - CORS(app) # 允许跨域请求 - - # 用于去重用的列表 - seq_list = [] - - @app.route('/wxlive', methods=['POST']) - def wxlive(): - global my_handle, config, global_idle_time - - try: - # 获取 POST 请求中的数据 - data = request.json - # 这里可以添加代码处理接收到的数据 - logging.debug(data) - - if data['events'][0]['seq'] in seq_list: - return jsonify({"code": 1, "message": "重复数据过滤"}) - - # 如果列表长度达到30,移除最旧的元素 - if len(seq_list) >= 30: - seq_list.pop(0) - - # 添加新元素 - seq_list.append(data['events'][0]['seq']) - - # 弹幕数据 - if data['events'][0]['decoded_type'] == "comment": - # 闲时计数清零 - global_idle_time = 0 - - content = data['events'][0]['content'] # 获取弹幕内容 - user_name = data['events'][0]['nickname'] # 获取发送弹幕的用户昵称 - - logging.info(f"[{user_name}]: {content}") - - data = { - "platform": "微信视频号", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - # 入场数据 - elif data['events'][0]['decoded_type'] == "enter": - user_name = data['events'][0]['nickname'] - - logging.info(f"用户:{user_name} 进入直播间") - - # 添加用户名到最新的用户名列表 - add_username_to_last_username_list(user_name) - - data = { - "platform": "微信视频号", - "username": user_name, - "content": "进入直播间" - } - - my_handle.process_data(data, "entrance") - pass - - # 响应 - return jsonify({"code": 200, "message": "成功接收"}) - except Exception as e: - logging.error(traceback.format_exc()) - my_handle.abnormal_alarm_handle("platform") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - # app.run(host="0.0.0.0", port=8082, debug=True) - - -if __name__ == '__main__': - start_server() - diff --git a/youtube.py b/youtube.py deleted file mode 100644 index 043f65b0..00000000 --- a/youtube.py +++ /dev/null @@ -1,430 +0,0 @@ -import logging, os -import threading -import schedule -import random -import asyncio -import traceback -import re -import copy - -from functools import partial - -from flask import Flask, send_from_directory, render_template, request, jsonify -from flask_cors import CORS - -import pytchat - -from utils.common import Common -from utils.config import Config -from utils.logger import Configure_logger -from utils.my_handle import My_handle - -""" - ___ _ - |_ _| | ____ _ _ __ ___ ___ - | || |/ / _` | '__/ _ \/ __| - | || < (_| | | | (_) \__ \ - |___|_|\_\__,_|_| \___/|___/ - -""" - -config = None -common = None -my_handle = None -# last_liveroom_data = None -last_username_list = None -# 空闲时间计数器 -global_idle_time = 0 - - -# 点火起飞 -def start_server(): - global config, common, my_handle, last_username_list - global global_idle_time - - config_path = "config.json" - - common = Common() - config = Config(config_path) - # 日志文件路径 - log_path = "./log/log-" + common.get_bj_time(1) + ".txt" - Configure_logger(log_path) - - # 获取 httpx 库的日志记录器 - httpx_logger = logging.getLogger("httpx") - # 设置 httpx 日志记录器的级别为 WARNING - httpx_logger.setLevel(logging.WARNING) - - # 最新入场的用户名列表 - last_username_list = [""] - - my_handle = My_handle(config_path) - if my_handle is None: - logging.error("程序初始化失败!") - os._exit(0) - - # HTTP API线程 - def http_api_thread(): - app = Flask(__name__, static_folder='./') - CORS(app) # 允许跨域请求 - - @app.route('/send', methods=['POST']) - def send(): - global my_handle, config - - try: - try: - data_json = request.get_json() - logging.info(f"API收到数据:{data_json}") - - if data_json["type"] == "reread": - my_handle.reread_handle(data_json) - elif data_json["type"] == "comment": - my_handle.process_data(data_json, "comment") - elif data_json["type"] == "tuning": - my_handle.tuning_handle(data_json) - - return jsonify({"code": 200, "message": "发送数据成功!"}) - except Exception as e: - logging.error(f"发送数据失败!{e}") - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - except Exception as e: - return jsonify({"code": -1, "message": f"发送数据失败!{e}"}) - - app.run(host=config.get("api_ip"), port=config.get("api_port"), debug=False) - - # HTTP API线程并启动 - schedule_thread = threading.Thread(target=http_api_thread) - schedule_thread.start() - - # 添加用户名到最新的用户名列表 - def add_username_to_last_username_list(data): - global last_username_list - - # 添加数据到 最新入场的用户名列表 - last_username_list.append(data) - - # 保留最新的3个数据 - last_username_list = last_username_list[-3:] - - - # 定时任务 - def schedule_task(index): - logging.debug("定时任务执行中...") - hour, min = common.get_bj_time(6) - - if 0 <= hour and hour < 6: - time = f"凌晨{hour}点{min}分" - elif 6 <= hour and hour < 9: - time = f"早晨{hour}点{min}分" - elif 9 <= hour and hour < 12: - time = f"上午{hour}点{min}分" - elif hour == 12: - time = f"中午{hour}点{min}分" - elif 13 <= hour and hour < 18: - time = f"下午{hour - 12}点{min}分" - elif 18 <= hour and hour < 20: - time = f"傍晚{hour - 12}点{min}分" - elif 20 <= hour and hour < 24: - time = f"晚上{hour - 12}点{min}分" - - - # 根据对应索引从列表中随机获取一个值 - random_copy = random.choice(config.get("schedule")[index]["copy"]) - - # 假设有多个未知变量,用户可以在此处定义动态变量 - variables = { - 'time': time, - 'user_num': "N", - 'last_username': last_username_list[-1], - } - - # 使用字典进行字符串替换 - if any(var in random_copy for var in variables): - content = random_copy.format(**{var: value for var, value in variables.items() if var in random_copy}) - else: - content = random_copy - - data = { - "platform": "YouTube", - "username": None, - "content": content - } - - logging.info(f"定时任务:{content}") - - my_handle.process_data(data, "schedule") - - - # 启动定时任务 - def run_schedule(): - global config - - try: - for index, task in enumerate(config.get("schedule")): - if task["enable"]: - # logging.info(task) - # 设置定时任务,每隔n秒执行一次 - schedule.every(task["time"]).seconds.do(partial(schedule_task, index)) - except Exception as e: - logging.error(traceback.format_exc()) - - while True: - schedule.run_pending() - # time.sleep(1) # 控制每次循环的间隔时间,避免过多占用 CPU 资源 - - - if any(item['enable'] for item in config.get("schedule")): - # 创建定时任务子线程并启动 - schedule_thread = threading.Thread(target=run_schedule) - schedule_thread.start() - - - # 启动动态文案 - async def run_trends_copywriting(): - global config - - try: - if False == config.get("trends_copywriting", "enable"): - return - - logging.info(f"动态文案任务线程运行中...") - - while True: - # 文案文件路径列表 - copywriting_file_path_list = [] - - # 获取动态文案列表 - for copywriting in config.get("trends_copywriting", "copywriting"): - # 获取文件夹内所有文件的文件绝对路径,包括文件扩展名 - for tmp in common.get_all_file_paths(copywriting["folder_path"]): - copywriting_file_path_list.append(tmp) - - # 是否开启随机播放 - if config.get("trends_copywriting", "random_play"): - random.shuffle(copywriting_file_path_list) - - # 遍历文案文件路径列表 - for copywriting_file_path in copywriting_file_path_list: - # 获取文案文件内容 - copywriting_file_content = common.read_file_return_content(copywriting_file_path) - # 是否启用提示词对文案内容进行转换 - if copywriting["prompt_change_enable"]: - data_json = { - "username": "trends_copywriting", - "content": copywriting["prompt_change_content"] + copywriting_file_content - } - - # 调用函数进行LLM处理,以及生成回复内容,进行音频合成,需要好好考虑考虑实现 - data_json["content"] = my_handle.llm_handle(config.get("chat_type"), data_json) - else: - data_json = { - "username": "trends_copywriting", - "content": copywriting_file_content - } - - # 空数据判断 - if data_json["content"] != None and data_json["content"] != "": - # 发给直接复读进行处理 - my_handle.reread_handle(data_json) - - await asyncio.sleep(config.get("trends_copywriting", "play_interval")) - except Exception as e: - logging.error(traceback.format_exc()) - - - if config.get("trends_copywriting", "enable"): - # 创建动态文案子线程并启动 - threading.Thread(target=lambda: asyncio.run(run_trends_copywriting())).start() - - # 闲时任务 - async def idle_time_task(): - global config, global_idle_time - - try: - if False == config.get("idle_time_task", "enable"): - return - - logging.info(f"闲时任务线程运行中...") - - # 记录上一次触发的任务类型 - last_mode = 0 - comment_copy_list = None - local_audio_path_list = None - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - - logging.info(f"闲时时间={overflow_time}秒") - - def load_data_list(type): - if type == "comment": - tmp = config.get("idle_time_task", "comment", "copy") - elif type == "local_audio": - tmp = config.get("idle_time_task", "local_audio", "path") - tmp2 = copy.copy(tmp) - return tmp2 - - comment_copy_list = load_data_list("comment") - local_audio_path_list = load_data_list("local_audio") - - logging.debug(f"comment_copy_list={comment_copy_list}") - logging.debug(f"local_audio_path_list={local_audio_path_list}") - - while True: - # 每隔一秒的睡眠进行闲时计数 - await asyncio.sleep(1) - global_idle_time = global_idle_time + 1 - - # 闲时计数达到指定值,进行闲时任务处理 - if global_idle_time >= overflow_time: - # 闲时计数清零 - global_idle_time = 0 - - # 闲时任务处理 - if config.get("idle_time_task", "comment", "enable"): - if last_mode == 0 or not config.get("idle_time_task", "local_audio", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "comment", "random"): - if comment_copy_list != []: - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - # 随机打乱列表中的元素 - random.shuffle(comment_copy_list) - comment_copy = comment_copy_list.pop(0) - else: - if comment_copy_list != []: - comment_copy = comment_copy_list.pop(0) - else: - # 刷新list数据 - comment_copy_list = load_data_list("comment") - comment_copy = comment_copy_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "YouTube", - "username": "闲时任务", - "type": "comment", - "content": comment_copy - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 1 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - if config.get("idle_time_task", "local_audio", "enable"): - if last_mode == 1 or not config.get("idle_time_task", "comment", "enable"): - # 是否开启了随机触发 - if config.get("idle_time_task", "local_audio", "random"): - if local_audio_path_list != []: - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - # 随机打乱列表中的元素 - random.shuffle(local_audio_path_list) - local_audio_path = local_audio_path_list.pop(0) - else: - if local_audio_path_list != []: - local_audio_path = local_audio_path_list.pop(0) - else: - # 刷新list数据 - local_audio_path_list = load_data_list("local_audio") - local_audio_path = local_audio_path_list.pop(0) - - # 发送给处理函数 - data = { - "platform": "YouTube", - "username": "闲时任务", - "type": "local_audio", - "content": common.extract_filename(local_audio_path, False), - "file_path": local_audio_path - } - - my_handle.process_data(data, "idle_time_task") - - # 模式切换 - last_mode = 0 - - overflow_time = int(config.get("idle_time_task", "idle_time")) - # 是否开启了随机闲时时间 - if config.get("idle_time_task", "random_time"): - overflow_time = random.randint(0, overflow_time) - logging.info(f"闲时时间={overflow_time}秒") - - continue - - except Exception as e: - logging.error(traceback.format_exc()) - - if config.get("idle_time_task", "enable"): - # 创建闲时任务子线程并启动 - threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() - - - try: - try: - video_id = config.get("room_display_id") - except Exception as e: - logging.error("获取直播间号失败!\n{0}".format(e)) - - live = pytchat.create(video_id=video_id) - while live.is_alive(): - try: - for c in live.get().sync_items(): - # 过滤表情包 - chat_raw = re.sub(r':[^\s]+:', '', c.message) - chat_raw = chat_raw.replace('#', '') - if chat_raw != '': - # 闲时计数清零 - global_idle_time = 0 - - # chat_author makes the chat look like this: "Nightbot: Hello". So the assistant can respond to the user's name - # chat = '[' + c.author.name + ']: ' + chat_raw - # logging.info(chat) - - content = chat_raw # 获取弹幕内容 - user_name = c.author.name # 获取发送弹幕的用户昵称 - - logging.info(f"[{user_name}]: {content}") - - data = { - "platform": "YouTube", - "username": user_name, - "content": content - } - - my_handle.process_data(data, "comment") - - # time.sleep(1) - except Exception as e: - logging.error(traceback.format_exc()) - logging.error("Error receiving chat: {0}".format(e)) - my_handle.abnormal_alarm_handle("platform") - except KeyboardInterrupt: - logging.warning('程序被强行退出') - finally: - logging.warning('关闭连接...') - os._exit(0) - - -if __name__ == '__main__': - start_server()