import asyncio, os, sys, threading, time from collections import deque sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from agentmain import GeneraticAgent from chatapp_common import AgentChatMixin, ensure_single_instance, public_access, redirect_log, require_runtime, split_text from llmcore import mykeys try: import botpy from botpy.message import C2CMessage, GroupMessage except Exception: print("Please install qq-botpy to use QQ module: pip install qq-botpy") sys.exit(1) agent = GeneraticAgent(); agent.verbose = False APP_ID = str(mykeys.get("qq_app_id", "") or "").strip() APP_SECRET = str(mykeys.get("qq_app_secret", "") or "").strip() ALLOWED = {str(x).strip() for x in mykeys.get("qq_allowed_users", []) if str(x).strip()} PROCESSED_IDS, USER_TASKS = deque(maxlen=1000), {} SEQ_LOCK, MSG_SEQ = threading.Lock(), 1 def _next_msg_seq(): global MSG_SEQ with SEQ_LOCK: MSG_SEQ += 1 return MSG_SEQ def _build_intents(): try: return botpy.Intents(public_messages=True, direct_message=True) except Exception: intents = botpy.Intents.none() if hasattr(botpy.Intents, "none") else botpy.Intents() for attr in ("public_messages", "public_guild_messages", "direct_message", "direct_messages", "c2c_message", "c2c_messages", "group_at_message", "group_at_messages"): if hasattr(intents, attr): try: setattr(intents, attr, True) except Exception: pass return intents def _make_bot_class(app): class QQBot(botpy.Client): def __init__(self): super().__init__(intents=_build_intents(), ext_handlers=False) async def on_ready(self): print(f"[QQ] bot ready: {getattr(getattr(self, 'robot', None), 'name', 'QQBot')}") async def on_c2c_message_create(self, message: C2CMessage): await app.on_message(message, is_group=False) async def on_group_at_message_create(self, message: GroupMessage): await app.on_message(message, is_group=True) async def on_direct_message_create(self, message): await app.on_message(message, is_group=False) return QQBot class QQApp(AgentChatMixin): label, source, split_limit = "QQ", "qq", 1500 def __init__(self): super().__init__(agent, USER_TASKS) self.client = None async def send_text(self, chat_id, content, *, msg_id=None, is_group=False): if not self.client: return api = self.client.api.post_group_message if is_group else self.client.api.post_c2c_message key = "group_openid" if is_group else "openid" for part in split_text(content, self.split_limit): await api(**{key: chat_id, "msg_type": 0, "content": part, "msg_id": msg_id, "msg_seq": _next_msg_seq()}) async def on_message(self, data, is_group=False): try: msg_id = getattr(data, "id", None) if msg_id in PROCESSED_IDS: return PROCESSED_IDS.append(msg_id) content = (getattr(data, "content", "") or "").strip() if not content: return author = getattr(data, "author", None) user_id = str(getattr(author, "member_openid" if is_group else "user_openid", "") or getattr(author, "id", "") or "unknown") chat_id = str(getattr(data, "group_openid", "") or user_id) if is_group else user_id if not public_access(ALLOWED) and user_id not in ALLOWED: print(f"[QQ] unauthorized user: {user_id}") return print(f"[QQ] message from {user_id} ({'group' if is_group else 'c2c'}): {content}") if content.startswith("/"): return await self.handle_command(chat_id, content, msg_id=msg_id, is_group=is_group) asyncio.create_task(self.run_agent(chat_id, content, msg_id=msg_id, is_group=is_group)) except Exception: import traceback print("[QQ] handle_message error") traceback.print_exc() async def start(self): self.client = _make_bot_class(self)() while True: try: print(f"[QQ] bot starting... {time.strftime('%m-%d %H:%M')}") await self.client.start(appid=APP_ID, secret=APP_SECRET) except Exception as e: print(f"[QQ] bot error: {e}") print("[QQ] reconnect in 5s...") await asyncio.sleep(5) if __name__ == "__main__": _LOCK_SOCK = ensure_single_instance(19528, "QQ") require_runtime(agent, "QQ", qq_app_id=APP_ID, qq_app_secret=APP_SECRET) redirect_log(__file__, "qqapp.log", "QQ", ALLOWED) threading.Thread(target=agent.run, daemon=True).start() asyncio.run(QQApp().start())