diff --git a/base/func_chatgpt.py b/base/func_chatgpt.py deleted file mode 100644 index 5e3c70a..0000000 --- a/base/func_chatgpt.py +++ /dev/null @@ -1,106 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- - -import logging -from datetime import datetime - -import httpx -from openai import APIConnectionError, APIError, AuthenticationError, OpenAI - - -class ChatGPT(): - def __init__(self, conf: dict) -> None: - key = conf.get("key") - api = conf.get("api") - proxy = conf.get("proxy") - prompt = conf.get("prompt") - self.model = conf.get("model", "gpt-3.5-turbo") - self.LOG = logging.getLogger("ChatGPT") - if proxy: - self.client = OpenAI(api_key=key, base_url=api, http_client=httpx.Client(proxy=proxy)) - else: - self.client = OpenAI(api_key=key, base_url=api) - self.conversation_list = {} - self.system_content_msg = {"role": "system", "content": prompt} - - def __repr__(self): - return 'ChatGPT' - - @staticmethod - def value_check(conf: dict) -> bool: - if conf: - if conf.get("key") and conf.get("api") and conf.get("prompt"): - return True - return False - - def get_answer(self, question: str, wxid: str) -> str: - # wxid或者roomid,个人时为微信id,群消息时为群id - self.updateMessage(wxid, question, "user") - rsp = "" - try: - ret = self.client.chat.completions.create(model=self.model, - messages=self.conversation_list[wxid], - temperature=0.2) - rsp = ret.choices[0].message.content - rsp = rsp[2:] if rsp.startswith("\n\n") else rsp - rsp = rsp.replace("\n\n", "\n") - self.updateMessage(wxid, rsp, "assistant") - except AuthenticationError: - self.LOG.error("OpenAI API 认证失败,请检查 API 密钥是否正确") - except APIConnectionError: - self.LOG.error("无法连接到 OpenAI API,请检查网络连接") - except APIError as e1: - self.LOG.error(f"OpenAI API 返回了错误:{str(e1)}") - except Exception as e0: - self.LOG.error(f"发生未知错误:{str(e0)}") - - return rsp - - def updateMessage(self, wxid: str, question: str, role: str) -> None: - now_time = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - - time_mk = "当需要回答时间时请直接参考回复:" - # 初始化聊天记录,组装系统信息 - if wxid not in self.conversation_list.keys(): - question_ = [ - self.system_content_msg, - {"role": "system", "content": "" + time_mk + now_time} - ] - self.conversation_list[wxid] = question_ - - # 当前问题 - content_question_ = {"role": role, "content": question} - self.conversation_list[wxid].append(content_question_) - - for cont in self.conversation_list[wxid]: - if cont["role"] != "system": - continue - if cont["content"].startswith(time_mk): - cont["content"] = time_mk + now_time - - # 只存储10条记录,超过滚动清除 - i = len(self.conversation_list[wxid]) - if i > 10: - print("滚动清除微信记录:" + wxid) - # 删除多余的记录,倒着删,且跳过第一个的系统消息 - del self.conversation_list[wxid][1] - - -if __name__ == "__main__": - from configuration import Config - config = Config().CHATGPT - if not config: - exit(0) - - chat = ChatGPT(config) - - while True: - q = input(">>> ") - try: - time_start = datetime.now() # 记录开始时间 - print(chat.get_answer(q, "wxid")) - time_end = datetime.now() # 记录结束时间 - - print(f"{round((time_end - time_start).total_seconds(), 2)}s") # 计算的时间差为程序的执行时间,单位为秒/s - except Exception as e: - print(e) diff --git a/base/func_report_reminder.py b/base/func_report_reminder.py deleted file mode 100644 index f30b1ba..0000000 --- a/base/func_report_reminder.py +++ /dev/null @@ -1,61 +0,0 @@ -import calendar -import datetime - -from chinese_calendar import is_workday -from robot import Robot - - -class ReportReminder: - - @staticmethod - def remind(robot: Robot) -> None: - - receivers = robot.config.REPORT_REMINDERS - if not receivers: - receivers = ["filehelper"] - # 日报周报月报提醒 - for receiver in receivers: - today = datetime.datetime.now().date() - # 如果是非工作日 - if not is_workday(today): - robot.send_text_msg("休息日快乐", receiver) - # 如果是工作日 - if is_workday(today): - robot.send_text_msg("该发日报啦", receiver) - # 如果是本周最后一个工作日 - if ReportReminder.last_work_day_of_week(today) == today: - robot.send_text_msg("该发周报啦", receiver) - # 如果本日是本月最后一整周的最后一个工作日: - if ReportReminder.last_work_friday_of_month(today) == today: - robot.send_text_msg("该发月报啦", receiver) - - # 计算本月最后一个周的最后一个工作日 - @staticmethod - def last_work_friday_of_month(d: datetime.date) -> datetime.date: - days_in_month = calendar.monthrange(d.year, d.month)[1] - weekday = calendar.weekday(d.year, d.month, days_in_month) - if weekday == 4: - last_friday_of_month = datetime.date( - d.year, d.month, days_in_month) - else: - if weekday >= 5: - last_friday_of_month = datetime.date(d.year, d.month, days_in_month) - \ - datetime.timedelta(days=(weekday - 4)) - else: - last_friday_of_month = datetime.date(d.year, d.month, days_in_month) - \ - datetime.timedelta(days=(weekday + 3)) - while not is_workday(last_friday_of_month): - last_friday_of_month = last_friday_of_month - datetime.timedelta(days=1) - return last_friday_of_month - - # 计算本周最后一个工作日 - @staticmethod - def last_work_day_of_week(d: datetime.date) -> datetime.date: - weekday = calendar.weekday(d.year, d.month, d.day) - last_work_day_of_week = datetime.date( - d.year, d.month, d.day) + datetime.timedelta(days=(6 - weekday)) - - while not is_workday(last_work_day_of_week): - last_work_day_of_week = last_work_day_of_week - \ - datetime.timedelta(days=1) - return last_work_day_of_week diff --git a/base/func_tigerbot.py b/base/func_tigerbot.py deleted file mode 100644 index 54a1bc8..0000000 --- a/base/func_tigerbot.py +++ /dev/null @@ -1,49 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- - -import logging - -import requests -from random import randint - - -class TigerBot: - def __init__(self, tbconf=None) -> None: - self.LOG = logging.getLogger(__file__) - self.tburl = "https://api.tigerbot.com/bot-service/ai_service/gpt" - self.tbheaders = {"Authorization": "Bearer " + tbconf["key"]} - self.tbmodel = tbconf["model"] - self.fallback = ["滚", "快滚", "赶紧滚"] - - def __repr__(self): - return 'TigerBot' - - @staticmethod - def value_check(conf: dict) -> bool: - if conf: - return all(conf.values()) - return False - - def get_answer(self, msg: str, sender: str = None) -> str: - payload = { - "text": msg, - "modelVersion": self.tbmodel - } - rsp = "" - try: - rsp = requests.post(self.tburl, headers=self.tbheaders, json=payload).json() - rsp = rsp["data"]["result"][0] - except Exception as e: - self.LOG.error(f"{e}: {payload}\n{rsp}") - idx = randint(0, len(self.fallback) - 1) - rsp = self.fallback[idx] - - return rsp - - -if __name__ == "__main__": - from configuration import Config - c = Config() - tbot = TigerBot(c.TIGERBOT) - rsp = tbot.get_answer("你还活着?") - print(rsp) diff --git a/base/func_txt_speech.py b/base/func_txt_speech.py deleted file mode 100644 index 8f16f5a..0000000 --- a/base/func_txt_speech.py +++ /dev/null @@ -1,8 +0,0 @@ -import pyttsx3 -engine = pyttsx3.init() -engine.setProperty('rate', 150) -engine.setProperty('volume', 0.9) -engine.setProperty('voice', 'zh-CN') # 选择中文语音风格 -text = '你好,世界!' -engine.say(text) -engine.runAndWait() \ No newline at end of file diff --git a/base/func_xinghuo_web.py b/base/func_xinghuo_web.py deleted file mode 100644 index 2023fd0..0000000 --- a/base/func_xinghuo_web.py +++ /dev/null @@ -1,98 +0,0 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- -from sparkdesk_web.core import SparkWeb -import re - -class XinghuoWeb: - def __init__(self, xhconf=None) -> None: - - self._sparkWeb = SparkWeb( - cookie=xhconf["cookie"], - fd=xhconf["fd"], - GtToken=xhconf["GtToken"], - ) - self._chat = self._sparkWeb.create_continuous_chat() - # 如果有提示词 - if xhconf["prompt"]: - self._chat.chat(xhconf["prompt"]) - - def __repr__(self): - return 'XinghuoWeb' - - @staticmethod - def value_check(conf: dict) -> bool: - if conf: - return all(conf.values()) - return False - - def get_answer(self, msg: str, sender: str = None) -> str: - answer = self._chat.chat(msg) - answer = re.sub(r'```.*?```', '', answer, flags=re.DOTALL) - answer = re.sub(r'^\s*$\n', '', answer, flags=re.MULTILINE) - return answer - - -if __name__ == "__main__": - from configuration import Config - c = Config() - xinghuo = XinghuoWeb(c.XINGHUO_WEB) - question = "请根据新闻标题,按照新闻的类型(财经、彩票、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐)进行分类;内容前加入当前日期和星期几" \ - "内容格式如下:" \ - "### 分类1" \ - "1.#标题1" \ - "2.#标题2" \ - "分类之间使用--号进行分割,无内容则忽略该分组" \ - "当前日期:2024年12月16日 星期一" \ - "1. 标题: 安徽监狱回应李铁可否在监狱踢球" \ - "2. 标题: 董明珠问雷军:你给股民分了多少钱?" \ - "3. 标题: 学习贯彻中央经济工作会议精神" \ - "4. 标题: 网曝阿娇知三当三" \ - "5. 标题: 当我用人民币感受台湾物价" \ - "6. 标题: 男子坐轮椅在高速疾驰" \ - "7. 标题: 刘亦菲陈金飞酒店聚餐" \ - "8. 标题: 冷冷冷冷冷你冷冷冷冷冷" \ - "9. 标题: 男生表演引体向上手滑摔到女生怀里" \ - "10. 标题: 74岁斯琴高娃自曝近况" \ - "11. 标题: 12岁女孩感染HPV 17岁男友被抓" \ - "12. 标题: 高校女生被殴打需植入钢钉?谣言" \ - "13. 标题: 东京将启动上四休三工作制" \ - "14. 标题: 张杰谢娜回应婚变传闻" \ - "15. 标题: 董明珠:小米空调因侵权赔了格力50万" \ - "16. 标题: 汪峰女友森林北晒近况" \ - "17. 标题: 实习生一句小姨让同事汗流浃背" \ - "18. 标题: 日本女学生在麦当劳被捅致死" \ - "19. 标题: 女生用绳子编出五脏六腑" \ - "20. 标题: 带南方女婿吃东北农村大席" \ - "21. 标题: 俄乌均证实朝军已参与作战" \ - "22. 标题: 孙颖莎带着三个保镖开心下班" \ - "23. 标题: 宋佳素颜拍戏好沧桑啊" \ - "24. 标题: 2岁半宝宝“呲溜滑”超快乐" \ - "25. 标题: 央视曝光先享后付套路多" \ - "26. 标题: #阜阳赶大集太香了吧#" \ - "27. 标题: 孟子义的面具焊在脸上了" \ - "28. 标题: 福建一高中生被美国藤校录取" \ - "29. 标题: 2岁社牛女孩跟满屋小朋友搭话" \ - "30. 标题: 王安宇 人怎么能丢这么大的脸" \ - "31. 标题: 被挡在村规民约外的“外嫁女”" \ - "32. 标题: 粉尘爆炸致8死8伤 官方公布调查报告" \ - "33. 标题: 游客在广东发现一窝6枚恐龙蛋化石" \ - "34. 标题: 胖东来回应微信小程序的官方店铺" \ - "35. 标题: 张柏芝把博物馆展品戴在身上" \ - "36. 标题: 长沙一大桥下浮尸已打捞身份待确认" \ - "37. 标题: 陈都灵终于演上一整部剧的女妖了" \ - "38. 标题: 网上下载公开信息发给间谍换钱" \ - "39. 标题: 7岁异瞳女孩在学校受到同学喜爱" \ - "40. 标题: 男子面试遭猥亵后发声竟遭网暴" \ - "41. 标题: 乌女权示威者裸上身破坏纪念雕塑" \ - "42. 标题: 王宝强喊话甄子丹一起打坏人" \ - "43. 标题: 记者卧底非法屠宰场内部极度脏乱" \ - "44. 标题: 印度一教师遭绑架强迫结婚" \ - "45. 标题: 媒体人:高拉特在中国拿了5亿元" \ - "46. 标题: 导演回应53岁的于和伟演大学生" \ - "47. 标题: 山姆销售的床笠甲醛超标被罚" \ - "48. 标题: 留学回国人才纳入国家统一就业体系" \ - "49. 标题: 张家界长满了韩国人" \ - "50. 标题: 圆通快递回应装车工因热射病去世" - rsp = xinghuo.get_answer(question) - - print(rsp) diff --git a/base/func_zhipu.py b/base/func_zhipu.py deleted file mode 100644 index 2eba7c0..0000000 --- a/base/func_zhipu.py +++ /dev/null @@ -1,46 +0,0 @@ -from zhipuai import ZhipuAI - - -class ZhiPu(): - def __init__(self, conf: dict) -> None: - self.api_key = conf.get("api_key") - self.model = conf.get("model", "glm-4") # 默认使用 glm-4 模型 - self.client = ZhipuAI(api_key=self.api_key) - self.converstion_list = {} - - @staticmethod - def value_check(conf: dict) -> bool: - if conf and conf.get("api_key"): - return True - return False - - def __repr__(self): - return 'ZhiPu' - - def get_answer(self, msg: str, wxid: str, **args) -> str: - self._update_message(wxid, str(msg), "user") - response = self.client.chat.completions.create( - model=self.model, - messages=self.converstion_list[wxid] - ) - resp_msg = response.choices[0].message - answer = resp_msg.content - self._update_message(wxid, answer, "assistant") - return answer - - def _update_message(self, wxid: str, msg: str, role: str) -> None: - if wxid not in self.converstion_list.keys(): - self.converstion_list[wxid] = [] - content = {"role": role, "content": str(msg)} - self.converstion_list[wxid].append(content) - - -if __name__ == "__main__": - from configuration import Config - config = Config().ZHIPU - if not config: - exit(0) - - zhipu = ZhiPu(config) - rsp = zhipu.get_answer("你好") - print(rsp) diff --git a/gewechat/client.py b/gewechat/client.py new file mode 100644 index 0000000..c274c69 --- /dev/null +++ b/gewechat/client.py @@ -0,0 +1,59 @@ +import os +import toml +import time +import logging + +# 假设 gewechat_client 已经安装,并且 GewechatClient 可直接导入 +from gewechat_client import GewechatClient + +logger = logging.getLogger(__name__) + +class Client: + def __init__(self, config_path=None): + # 默认配置文件路径 + if config_path is None: + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.toml") + self.config_path = config_path + config = toml.load(config_path) + gewechat_cfg = config.get("Gewechat", {}) + self.base_url = gewechat_cfg.get("base_url", "") + self.gewechat_token = gewechat_cfg.get("gewechat_token", "") + self.app_id = gewechat_cfg.get("app_id", "") + self.callback_url = gewechat_cfg.get("callback_url", "") + # 初始化 GewechatClient + self.client = GewechatClient( + base_url=self.base_url, + token=self.gewechat_token + ) + # 登录, 自动创建二维码,扫码后自动登录 + app_id, error_msg = self.client.login(app_id=self.app_id) + + if error_msg: + logger.error("登录失败") + return + # 休眠等待server启动,防止回调设置失败 + max_retries = 5 + retry_interval = 5 # 秒 + for attempt in range(1, max_retries + 1): + resp = self.client.set_callback(self.app_id, self.callback_url) + if resp and resp.get("success", False): + print(f"set_callback 成功: {resp}") + break + else: + logger.warning(f"set_callback 第{attempt}次失败,{retry_interval}秒后重试...") + time.sleep(retry_interval) + else: + logger.error("set_callback 多次重试后仍失败,请检查server状态。") + + # 如果启动时,配置文件中的app_id为空,那么将app_id写入配置文件 + if not self.app_id and app_id: + # 更新 config.toml 文件中的 app_id + config["Gewechat"]["app_id"] = app_id + with open(self.config_path, "w", encoding="utf-8") as f: + toml.dump(config, f) + logger.info(f"已将新的APP_ID: {app_id} 写入配置文件") + # 同时更新当前实例的 app_id + self.app_id = app_id + +# 项目全局唯一 client 实例 +gewe_client = Client().client \ No newline at end of file diff --git a/gewechat/config.toml b/gewechat/config.toml new file mode 100644 index 0000000..c03fdd8 --- /dev/null +++ b/gewechat/config.toml @@ -0,0 +1,5 @@ +[Gewechat] +base_url= "http://192.168.2.240:2531/v2/api" +gewechat_token= "cb43f52db27e4a56bb6ec7da54373582" +app_id="wx_3BC6eSHGE5xEm_hH3__7c" +callback_url="http://192.168.2.192:8999/gewechat/callback" diff --git a/main.py b/main.py index ad8d1db..9468ebd 100644 --- a/main.py +++ b/main.py @@ -4,78 +4,17 @@ import logging import threading import time from argparse import ArgumentParser -import uvicorn -from fastapi import FastAPI - from gewechat_client import GewechatClient - -import socket -# 启动FastAPI服务器 -# 从callback_url中提取主机和端口 import urllib.parse - from configuration import Config from constants import ChatType +from gewechat.api.start_server import start_fastapi_server from robot import Robot -from gewechat.api.callback import router as callback_router # 配置日志 logger = logging.getLogger(__name__) -def is_port_in_use(port, host='0.0.0.0'): - """检查端口是否被占用""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - s.bind((host, port)) - return False - except socket.error: - return True - - -def start_fastapi_server(host="0.0.0.0", port=8999): - """启动FastAPI服务器""" - # 检查端口是否被占用 - if is_port_in_use(port, host): - logger.warning(f"端口 {port} 已被占用,尝试使用其他端口") - # 尝试其他端口 - for test_port in range(9000, 9100): - if not is_port_in_use(test_port, host): - port = test_port - break - else: - logger.error("无法找到可用端口,服务器启动失败") - return False - - try: - app = FastAPI() - app.include_router(callback_router) - - # 添加健康检查路由 - @app.get("/health") - async def health_check(): - return {"status": "ok"} - - logger.info(f"正在启动FastAPI服务器,地址: http://{host}:{port}") - - # 使用线程启动uvicorn服务器 - server_thread = threading.Thread( - target=uvicorn.run, - args=(app,), - kwargs={"host": host, "port": port, "log_level": "info"}, - daemon=True - ) - server_thread.start() - logger.info(f"FastAPI 服务已在 http://{host}:{port} 启动") - logger.info(f"回调URL: http://{host}:{port}/gewechat/callback") - - # 返回启动的端口,以便调用者知道实际使用的端口 - return port - except Exception as e: - logger.error(f"启动FastAPI服务器失败: {e}", exc_info=True) - return False - - def main(chat_type: int): config = Config() base_url = config.BASE_URL @@ -99,8 +38,8 @@ def main(chat_type: int): if error_msg: logger.error("登录失败") return - #休眠等待server启动,防止回调设置失败 - time.sleep(10) + # 休眠等待server启动,防止回调设置失败 + time.sleep(5) resp = client.set_callback(token, callback_url) print(f"set_callback:{resp}") diff --git a/robot.py b/robot.py index 152d6cd..3044edf 100644 --- a/robot.py +++ b/robot.py @@ -151,7 +151,8 @@ class Robot(Job): # 设置ROBOT功能为启用状态 GroupBotManager.set_group_permission(msg.roomid, Feature.ROBOT, PermissionStatus.ENABLED) # 更新联系人信息 - self.refresh_contacts() + # 如果是加入新群,则拉取群用户信息,并且将群加入联系人 + self.client.get_chatroom_info(self.app_id, msg.roomid) except Exception as e: self.LOG.error(f"加入新群,自动添加并开启机器人功能 error: {e}")