yangtb24 commited on
Commit
cf7e318
·
verified ·
1 Parent(s): c182862

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +346 -440
app.py CHANGED
@@ -1,33 +1,24 @@
1
- import asyncio
2
- import json
3
- import logging
4
  import os
5
- import time
 
 
6
  from datetime import datetime
7
 
8
- import aiohttp
9
- from telegram import Bot, Update
10
- from telegram.request import HTTPXRequest
11
- from telegram.ext import (
12
- Application,
13
- CommandHandler,
14
- CallbackQueryHandler,
15
- MessageHandler,
16
- filters,
17
- )
18
- import httpx
19
- from telegram.error import NetworkError
20
-
21
- # 配置
22
- TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
23
- AI_API_ENDPOINT = os.environ.get("AI_API_ENDPOINT", "https://new.yangtb2024.me/v1/chat/completions")
24
- AI_API_KEY = os.environ.get("AI_API_KEY")
25
- AI_MODEL = os.environ.get("AI_MODEL", "gemini-2.0-flash-exp")
26
- PHP_PROXY_URL = os.environ.get("PHP_PROXY_URL")
27
 
28
  AI_API_HEADERS = {
29
- "Content-Type": "application/json",
30
- "Authorization": f"Bearer {AI_API_KEY}",
31
  }
32
 
33
  PROMPT_TEMPLATES = {
@@ -37,12 +28,13 @@ PROMPT_TEMPLATES = {
37
  }
38
 
39
  CURRENT_PROMPT_INDEX = 0
 
40
  MAX_TOKENS = 500
41
  TEMPERATURE = 1.5
42
  MAX_HISTORY_LENGTH = 10
43
- chat_histories = {} # Use a dict instead of a Map
44
- GROUP_SETTINGS = {} # Use a dict instead of a Map
45
- USER_SETTINGS = {} # Use a dict instead of a Map
46
  BOT_COMMANDS = [
47
  {"command": "start", "description": "显示欢迎信息和操作按钮"},
48
  {"command": "clearall", "description": "清空当前会话的聊天记录"},
@@ -57,7 +49,7 @@ BOT_COMMANDS = [
57
  {"command": "promat", "description": "切换提示词,例如: /promat 0, 1, 2"},
58
  {"command": "getpromat", "description": "获取当前使用的提示词索引"},
59
  ]
60
- BOT_USERNAME = "zfs732_bot"
61
  DEFAULT_TEMP = 1.5
62
 
63
  TOOL_DEFINITIONS = [
@@ -69,11 +61,11 @@ TOOL_DEFINITIONS = [
69
  "parameters": {
70
  "type": "object",
71
  "properties": {},
72
- "required": [],
73
- },
74
- },
75
  },
76
- {
77
  "type": "function",
78
  "function": {
79
  "name": "get_current_date",
@@ -81,9 +73,9 @@ TOOL_DEFINITIONS = [
81
  "parameters": {
82
  "type": "object",
83
  "properties": {},
84
- "required": [],
85
- },
86
- },
87
  },
88
  {
89
  "type": "function",
@@ -104,424 +96,370 @@ TOOL_DEFINITIONS = [
104
  },
105
  },
106
  ]
107
-
108
  class EventEmitter:
109
- def __init__(self, event_emitter):
110
  self.event_emitter = event_emitter
111
 
112
  async def emit(self, event_type, data):
113
  if self.event_emitter:
114
  await self.event_emitter(type=event_type, data=data)
115
 
116
- async def update_status(self, description, done, action, urls=None):
117
- await self.emit("status", {"done": done, "action": action, "description": description, "urls": urls})
118
 
119
  async def send_citation(self, title, url, content):
120
- await self.emit(
121
- "citation",
122
- {"document": [content], "metadata": [{"name": title, "source": url, "html": False}]},
123
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- async def check_php_proxy_health():
126
  try:
127
- url = f"{PHP_PROXY_URL}/health"
128
- logging.info(f"Checking PHP proxy health at: {url}")
129
- async with aiohttp.ClientSession() as session:
130
- async with session.get(url) as response:
131
- logging.info(f"PHP proxy health check response status: {response.status}")
132
- if response.ok:
133
- logging.info("PHP proxy health check passed.")
134
- return True
135
- else:
136
- error_text = await response.text()
137
- logging.error(f"PHP proxy health check failed: {response.status}, {error_text}")
138
- return False
139
- except Exception as e:
140
- logging.error(f"PHP proxy health check error: {e}")
141
- return False
142
-
143
- async def set_bot_commands(bot: Bot):
144
- max_retries = 3
145
- for attempt in range(max_retries):
146
- try:
147
- url = f"{PHP_PROXY_URL}/bot{TELEGRAM_BOT_TOKEN}/deleteMyCommands"
148
- async with aiohttp.ClientSession() as session:
149
- async with session.post(url) as response:
150
- if not response.ok:
151
- error_text = await response.text()
152
- logging.error(f"Telegram delete commands failed: {response.status}, {error_text}")
153
- return
154
- logging.info("Telegram commands deleted successfully")
155
- commands = [
156
- {"command": command["command"], "description": command["description"]}
157
- for command in BOT_COMMANDS
158
- ]
159
- url = f"{PHP_PROXY_URL}/bot{TELEGRAM_BOT_TOKEN}/setMyCommands"
160
- json_data = {"commands": commands}
161
- logging.info(f"Sending setMyCommands request with data: {json.dumps(json_data)}")
162
- async with aiohttp.ClientSession() as session:
163
- async with session.post(url, json=json_data) as response:
164
- if not response.ok:
165
- error_text = await response.text()
166
- logging.error(f"Telegram set commands failed: {response.status}, {error_text}")
167
- return
168
- logging.info("Telegram commands set successfully")
169
- return
170
- except Exception as e:
171
- logging.error(f"Error setting Telegram commands, attempt {attempt + 1}: {e}")
172
- if attempt == max_retries - 1:
173
- raise
174
- await asyncio.sleep(2) # Wait for 2 second before retrying
175
-
176
- def parse_command(user_message):
177
- command = user_message.split(" ")[0]
178
- if "@" in command:
179
- command = command.split("@")[0]
180
- return command[1:]
181
 
182
- async def handle_telegram_update(update: Update, context):
183
- if not update.message:
184
- if update.callback_query:
185
- await handle_callback_query(update.callback_query, context)
186
  return
187
 
188
- chat_id = update.message.chat.id
189
- user_message = update.message.text
190
- is_group_chat = update.message.chat.type in ["group", "supergroup"]
191
- from_user_id = update.message.from_user.id
192
 
193
- if not user_message:
194
  return
195
 
196
- if user_message.startswith("/"):
197
- command = parse_command(user_message)
198
 
199
- if command == "clearall":
200
- chat_histories.pop(chat_id, None)
201
- await send_telegram_message(chat_id=chat_id, text="聊天记录已清空。", context=context)
202
  return
203
- if command == "help":
204
- await send_telegram_message(chat_id=chat_id, text=get_help_message(), context=context)
205
  return
206
- if command == "start":
207
- await send_telegram_message(
208
- chat_id=chat_id,
209
- text="欢迎使用!请选择操作:",
210
- reply_markup={
 
211
  "inline_keyboard": [[{"text": "清空聊天记录", "callback_data": "clearall"}]],
212
  },
213
- context=context
214
- )
215
  return
216
- if is_group_chat:
217
- if command == "enableai":
218
- GROUP_SETTINGS.setdefault(chat_id, {}).update({"aiEnabled": True})
219
- await send_telegram_message(chat_id=chat_id, text="已在群组中启用 AI 回复。", context=context)
220
  return
221
- if command == "disableai":
222
- GROUP_SETTINGS.setdefault(chat_id, {}).update({"aiEnabled": False})
223
- await send_telegram_message(chat_id=chat_id, text="已在群组中禁用 AI 回复。", context=context)
224
  return
225
- if user_message.startswith("/setprefix "):
226
- prefix = user_message[len("/setprefix ") :].strip()
227
- GROUP_SETTINGS.setdefault(chat_id, {}).update({"prefix": prefix})
228
- await send_telegram_message(chat_id=chat_id, text=f"已设置群组触发前缀为: {prefix}", context=context)
229
  return
230
- if command == "getprefix":
231
- prefix = GROUP_SETTINGS.get(chat_id, {}).get("prefix", "")
232
- await send_telegram_message(chat_id=chat_id, text=f"当前群组触发前缀为: {prefix}", context=context)
233
  return
234
  else:
235
- if user_message.startswith("/settemp "):
236
  try:
237
- temp = float(user_message[len("/settemp ") :].strip())
238
- if 0 <= temp <= 2:
239
- USER_SETTINGS.setdefault(from_user_id, {}).update({"temperature": temp})
240
- await send_telegram_message(chat_id=chat_id, text=f"已设置AI回复温度为: {temp}", context=context)
241
- else:
242
- await send_telegram_message(chat_id=chat_id, text="温度设置无效,请输入0到2之间的数字。", context=context)
243
  except ValueError:
244
- await send_telegram_message(chat_id=chat_id, text="温度设置无效,请输入0到2之间的数字。", context=context)
245
  return
246
- if command == "gettemp":
247
- temp = USER_SETTINGS.get(from_user_id, {}).get("temperature", DEFAULT_TEMP)
248
- await send_telegram_message(chat_id=chat_id, text=f"当前AI回复温度为: {temp}", context=context)
249
  return
250
- if user_message.startswith("/promat "):
251
  try:
252
- index = int(user_message[len("/promat ") :].strip())
253
- if index in PROMPT_TEMPLATES:
254
- global CURRENT_PROMPT_INDEX
255
- CURRENT_PROMPT_INDEX = index
256
- await send_telegram_message(chat_id=chat_id, text=f"已切换到提示词 {index}。", context=context)
257
- else:
258
- await send_telegram_message(
259
- chat_id=chat_id, text="提示词索引无效。请��用 /getpromat 查看可用的索引。", context=context
260
- )
261
  except ValueError:
262
- await send_telegram_message(
263
- chat_id=chat_id, text="提示词索引无效。请使用 /getpromat 查看可用的索引。", context=context
264
- )
265
  return
266
- if command == "getpromat":
267
- await send_telegram_message(
268
- chat_id=chat_id, text=f"当前使用的提示词索引是: {CURRENT_PROMPT_INDEX}", context=context
269
- )
270
  return
271
- if command == "resetuser":
272
- USER_SETTINGS.pop(from_user_id, None)
273
- await send_telegram_message(chat_id=chat_id, text="已重置您的个人设置。", context=context)
274
  return
 
 
 
 
275
 
276
- if is_group_chat:
277
- if chat_id not in GROUP_SETTINGS:
278
- GROUP_SETTINGS[chat_id] = {"aiEnabled": True, "prefix": None}
279
- logging.info(f"群组 {chat_id} 首次检测到,默认启用 AI。")
280
 
281
- group_settings = GROUP_SETTINGS[chat_id]
282
- prefix = group_settings["prefix"]
283
- if group_settings["aiEnabled"]:
284
- if prefix and not user_message.startswith(prefix):
285
  return
286
 
287
- message_content = user_message[len(prefix) :].strip() if prefix else user_message
288
- if len(message_content) > 0:
289
- await process_ai_message(chat_id, message_content, from_user_id, context)
290
  else:
291
- await process_ai_message(chat_id, user_message, from_user_id, context)
292
 
293
- async def process_ai_message(chat_id, user_message, from_user_id, context):
294
- history = chat_histories.get(chat_id, [])
295
- user_temp = USER_SETTINGS.get(from_user_id, {}).get("temperature", DEFAULT_TEMP)
296
- current_prompt = PROMPT_TEMPLATES.get(CURRENT_PROMPT_INDEX, "")
297
- history.append({"role": "user", "content": user_message})
 
 
 
 
 
 
298
 
299
  if len(history) > MAX_HISTORY_LENGTH:
300
  history = history[-MAX_HISTORY_LENGTH:]
301
 
302
  messages = [
303
- {"role": "system", "content": current_prompt},
304
- *history,
305
  ]
306
-
307
- event_emitter = EventEmitter(
308
- lambda event: send_event_message(chat_id, event, context)
309
- )
 
 
 
 
310
 
311
  try:
312
- async with aiohttp.ClientSession() as session:
313
- async with session.post(
314
- AI_API_ENDPOINT,
315
- headers=AI_API_HEADERS,
316
- json={
317
- "model": AI_MODEL,
318
- "messages": messages,
319
- "max_tokens": MAX_TOKENS,
320
- "temperature": user_temp,
321
- "tools": TOOL_DEFINITIONS,
322
- },
323
- ) as ai_response:
324
- if not ai_response.ok:
325
- error_text = await ai_response.text()
326
- logging.error(f"AI API 响应失败: {ai_response.status}, {error_text}")
327
- await send_telegram_message(chat_id=chat_id, text="AI API 响应失败,请稍后再试", context=context)
328
- return
329
-
330
- ai_data = await ai_response.json()
331
- ai_reply = await handle_ai_response(ai_data, chat_id, history, event_emitter, context)
332
-
333
- history.append({"role": "assistant", "content": ai_reply})
334
- chat_histories[chat_id] = history
335
-
336
- await send_telegram_message(chat_id=chat_id, text=ai_reply, context=context)
337
-
338
  except Exception as error:
339
- logging.error(f"处理消息时发生错误: {error}")
340
- await send_telegram_message(chat_id=chat_id, text="处理消息时发生错误,请稍后再试", context=context)
341
-
342
- async def handle_ai_response(ai_data, chat_id, history, event_emitter, context):
343
- if ai_data and ai_data.get("choices") and len(ai_data["choices"]) > 0:
344
- choice = ai_data["choices"][0]
345
- if choice.get("message") and choice["message"].get("content"):
346
- return choice["message"]["content"]
347
- elif choice.get("message") and choice["message"].get("tool_calls"):
348
- tool_calls = choice["message"]["tool_calls"]
349
- tool_results = []
350
- for tool_call in tool_calls:
351
- tool_result = await execute_tool_call(tool_call, event_emitter)
352
- tool_results.append(tool_result)
353
-
354
- new_messages = [
355
  *history,
356
- {"role": "assistant", "content": None, "tool_calls": tool_calls},
357
- *tool_results,
358
  ]
359
-
360
- user_temp = USER_SETTINGS.get(chat_id, {}).get("temperature", DEFAULT_TEMP)
361
- async with aiohttp.ClientSession() as session:
362
- async with session.post(
363
- AI_API_ENDPOINT,
364
- headers=AI_API_HEADERS,
365
- json={
366
- "model": AI_MODEL,
367
- "messages": new_messages,
368
- "max_tokens": MAX_TOKENS,
369
- "temperature": user_temp,
370
- },
371
- ) as ai_response:
372
- if not ai_response.ok:
373
- error_text = await ai_response.text()
374
- logging.error(f"AI API 响应失败: {ai_response.status}, {error_text}")
375
- return "AI API 响应失败,请稍后再试"
376
-
377
- ai_data = await ai_response.json()
378
- if (
379
- ai_data
380
- and ai_data.get("choices")
381
- and len(ai_data["choices"]) > 0
382
- and ai_data["choices"][0].get("message")
383
- and ai_data["choices"][0]["message"].get("content")
384
- ):
385
- return ai_data["choices"][0]["message"]["content"]
386
- return "AI 返回了无法识别的格式"
387
-
388
- return "AI 返回了无法识别的格式"
389
- return "AI 返回了无法识别的格式"
390
-
391
- async def execute_tool_call(tool_call, event_emitter):
392
- name = tool_call["function"]["name"]
393
- args = tool_call["function"].get("arguments", {})
394
- if isinstance(args, str):
395
- args = json.loads(args)
396
-
397
- if name == "web_scrape":
398
- urls = args.get("urls")
399
- if not urls or not isinstance(urls, list) or len(urls) == 0:
400
- return {
401
- "tool_call_id": tool_call["id"],
402
- "role": "tool",
403
- "name": name,
404
- "content": "请提供有效的 URL 列表。",
405
- }
406
- api_url = "https://gpts.webpilot.ai/api/read"
407
- headers = {"Content-Type": "application/json", "WebPilot-Friend-UID": "0"}
408
-
409
- await event_emitter.update_status(
410
- f"开始读取 {len(urls)} 个网页", False, "web_search", urls
411
- )
412
-
413
- async def process_url(url):
414
- try:
415
- async with aiohttp.ClientSession() as session:
416
- async with session.post(
417
- api_url,
418
- headers=headers,
419
- json={
420
- "link": url,
421
- "ur": "summary of the page",
422
- "lp": True,
423
- "rt": False,
424
- "l": "en",
425
- },
426
- ) as response:
427
- if not response.ok:
428
- error_text = await response.text()
429
- raise Exception(f"HTTP error! Status: {response.status}, Text: {error_text}")
430
- result = await response.json()
431
- if "rules" in result:
432
- del result["rules"]
433
- content = json.dumps(result)
434
- title = result.get("title", url)
435
- await event_emitter.send_citation(title, url, content)
436
- return f"{content}\n"
437
- except Exception as error:
438
- error_message = f"读取网页 {url} 时出错: {str(error)}"
439
- await event_emitter.update_status(error_message, False, "web_scrape", [url])
440
- await event_emitter.send_citation(f"Error from {url}", url, str(error))
441
- return f"URL: {url}\n错误: {error_message}\n"
442
-
443
- results = await asyncio.gather(*[process_url(url) for url in urls])
444
-
445
- await event_emitter.update_status(
446
- f"已完成 {len(urls)} 个网页的读取", True, "web_search", urls
447
- )
448
-
449
- return {
450
- "tool_call_id": tool_call["id"],
451
- "role": "tool",
452
- "name": name,
453
- "content": "\n".join(results),
454
- }
455
- elif name == "get_current_time":
456
- now = datetime.utcnow()
457
- utc8_offset = 8 * 60 * 60
458
- utc8_time = now.timestamp() + utc8_offset
459
- utc8_time_obj = datetime.fromtimestamp(utc8_time)
460
- formatted_time = utc8_time_obj.strftime("%H:%M:%S")
461
  return {
462
- "tool_call_id": tool_call["id"],
463
- "role": "tool",
464
- "name": name,
465
- "content": f"Current Time: {formatted_time}",
466
  }
467
- elif name == "get_current_date":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  now = datetime.utcnow()
469
- utc8_offset = 8 * 60 * 60
470
- utc8_time = now.timestamp() + utc8_offset
471
- utc8_time_obj = datetime.fromtimestamp(utc8_time)
472
- formatted_date = utc8_time_obj.strftime("%A, %B %d, %Y")
473
  return {
474
- "tool_call_id": tool_call["id"],
475
- "role": "tool",
476
- "name": name,
477
- "content": f"Today's date is {formatted_date}",
478
  }
479
  else:
480
  return {
481
- "tool_call_id": tool_call["id"],
482
- "role": "tool",
483
- "name": name,
484
- "content": "未知的工具调用",
485
  }
486
 
487
- async def handle_callback_query(callback_query, context):
488
- chat_id = callback_query.message.chat.id
489
- data = callback_query.data
490
 
491
  if data == "clearall":
492
- chat_histories.pop(chat_id, None)
493
- await send_telegram_message(chat_id=chat_id, text="聊天记录已清空。", context=context)
494
-
495
- await send_telegram_message(
496
- chat_id=chat_id,
497
- text="请选择操作:",
498
- reply_markup={
499
- "inline_keyboard": [[{"text": "清空聊天记录", "callback_data": "clearall"}]],
500
  },
501
- context=context
502
- )
503
-
504
- async def send_event_message(chat_id, event, context):
505
- if event["type"] == "status":
506
- await send_telegram_message(chat_id=chat_id, text=f"状态更新: {event['data']['description']}", context=context)
507
- elif event["type"] == "citation":
508
- await send_telegram_message(chat_id=chat_id, text=f"引用信息: {event['data']['metadata'][0]['name']}", context=context)
509
-
510
- async def send_telegram_message(chat_id, text, context, options=None):
511
- url = f"{PHP_PROXY_URL}/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
512
- json_data = {"chat_id": chat_id, "text": text}
513
- if options:
514
- json_data.update(options)
515
  try:
516
- async with aiohttp.ClientSession() as session:
517
- async with session.post(url, json=json_data) as response:
518
- if not response.ok:
519
- error_text = await response.text()
520
- logging.error(f"发送 Telegram 消息失败: {response.status}, {error_text}")
521
- except Exception as error:
522
- logging.error(f"发送 Telegram 消息时发生错误: {error}")
523
 
524
- def get_help_message():
525
  return f"""
526
  可用指令:
527
  /start - 显示欢迎信息和操作按钮。
@@ -550,62 +488,30 @@ def get_help_message():
550
  - 机器人具有攻击性,请谨慎使用。
551
  """
552
 
553
- async def initialize_with_retry(application):
554
- max_retries = 3
555
- for attempt in range(max_retries):
556
- try:
557
- await application.initialize()
558
- return
559
- except NetworkError as e:
560
- logging.error(f"Error initializing application, attempt {attempt + 1}: {e}")
561
- if attempt == max_retries - 1:
562
- raise
563
- await asyncio.sleep(2) # Wait for 2 seconds before retrying
564
-
565
- async def main():
566
- logging.basicConfig(level=logging.INFO)
567
 
568
- if not await check_php_proxy_health():
569
- logging.error("PHP proxy health check failed. Exiting.")
570
- return
 
571
 
572
- # 修改这里
573
- request = HTTPXRequest(httpx.AsyncClient(trust_env=True))
574
- application = Application.builder().token(TELEGRAM_BOT_TOKEN).request(request).build()
575
 
576
- # Set bot commands
 
577
  try:
578
- await set_bot_commands(application.bot)
 
 
579
  except Exception as e:
580
- logging.error(f"Failed to set bot commands after multiple retries: {e}")
581
- return
582
-
583
- # Command handlers
584
- application.add_handler(CommandHandler("start", handle_telegram_update))
585
- application.add_handler(CommandHandler("clearall", handle_telegram_update))
586
- application.add_handler(CommandHandler("help", handle_telegram_update))
587
- application.add_handler(CommandHandler("enableai", handle_telegram_update))
588
- application.add_handler(CommandHandler("disableai", handle_telegram_update))
589
- application.add_handler(CommandHandler("setprefix", handle_telegram_update))
590
- application.add_handler(CommandHandler("getprefix", handle_telegram_update))
591
- application.add_handler(CommandHandler("settemp", handle_telegram_update))
592
- application.add_handler(CommandHandler("gettemp", handle_telegram_update))
593
- application.add_handler(CommandHandler("resetuser", handle_telegram_update))
594
- application.add_handler(CommandHandler("promat", handle_telegram_update))
595
- application.add_handler(CommandHandler("getpromat", handle_telegram_update))
596
-
597
- # Callback query handler
598
- application.add_handler(CallbackQueryHandler(handle_callback_query))
599
-
600
- # Message handler for all text messages
601
- application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_telegram_update))
602
-
603
- # Initialize the bot
604
- await application.initialize()
605
- # Start the bot
606
- await application.start()
607
- # Start polling updates from Telegram
608
- await application.updater.start_polling()
609
-
610
- if __name__ == "__main__":
611
- asyncio.run(main())
 
 
 
 
1
  import os
2
+ import json
3
+ import requests
4
+ from flask import Flask, request, jsonify
5
  from datetime import datetime
6
 
7
+ app = Flask(__name__)
8
+
9
+ # 从环境变量中获取配置
10
+ TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
11
+ AI_API_ENDPOINT = os.environ.get('AI_API_ENDPOINT')
12
+ AI_API_KEY = os.environ.get('AI_API_KEY')
13
+ AI_MODEL = os.environ.get('AI_MODEL')
14
+ PHP_PROXY_URL = os.environ.get('PHP_PROXY_URL') # 代理地址
15
+
16
+ if not all([TELEGRAM_BOT_TOKEN, AI_API_ENDPOINT, AI_API_KEY, AI_MODEL]):
17
+ raise ValueError("请设置所有必要的环境变量")
 
 
 
 
 
 
 
 
18
 
19
  AI_API_HEADERS = {
20
+ 'Content-Type': 'application/json',
21
+ 'Authorization': f'Bearer {AI_API_KEY}',
22
  }
23
 
24
  PROMPT_TEMPLATES = {
 
28
  }
29
 
30
  CURRENT_PROMPT_INDEX = 0
31
+
32
  MAX_TOKENS = 500
33
  TEMPERATURE = 1.5
34
  MAX_HISTORY_LENGTH = 10
35
+ chatHistories = {}
36
+ GROUP_SETTINGS = {}
37
+ USER_SETTINGS = {}
38
  BOT_COMMANDS = [
39
  {"command": "start", "description": "显示欢迎信息和操作按钮"},
40
  {"command": "clearall", "description": "清空当前会话的聊天记录"},
 
49
  {"command": "promat", "description": "切换提示词,例如: /promat 0, 1, 2"},
50
  {"command": "getpromat", "description": "获取当前使用的提示词索引"},
51
  ]
52
+ BOT_USERNAME = 'zfs732_bot'
53
  DEFAULT_TEMP = 1.5
54
 
55
  TOOL_DEFINITIONS = [
 
61
  "parameters": {
62
  "type": "object",
63
  "properties": {},
64
+ "required": []
65
+ }
66
+ }
67
  },
68
+ {
69
  "type": "function",
70
  "function": {
71
  "name": "get_current_date",
 
73
  "parameters": {
74
  "type": "object",
75
  "properties": {},
76
+ "required": []
77
+ }
78
+ }
79
  },
80
  {
81
  "type": "function",
 
96
  },
97
  },
98
  ]
 
99
  class EventEmitter:
100
+ def __init__(self, event_emitter=None):
101
  self.event_emitter = event_emitter
102
 
103
  async def emit(self, event_type, data):
104
  if self.event_emitter:
105
  await self.event_emitter(type=event_type, data=data)
106
 
107
+ async def update_status(self, description, done, action, urls):
108
+ await self.emit('status', {'done': done, 'action': action, 'description': description, 'urls': urls})
109
 
110
  async def send_citation(self, title, url, content):
111
+ await self.emit('citation', {
112
+ 'document': [content],
113
+ 'metadata': [{'name': title, 'source': url, 'html': False}],
114
+ })
115
+
116
+
117
+ def make_telegram_request(method, data=None):
118
+ url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/{method}"
119
+ if PHP_PROXY_URL: # 使用代理
120
+ url = f"{PHP_PROXY_URL}{method}"
121
+ headers = {'Content-Type': 'application/json'}
122
+ if data:
123
+ data = json.dumps(data)
124
+ try:
125
+ response = requests.post(url, headers=headers, data=data)
126
+ response.raise_for_status()
127
+ return response.json()
128
+ except requests.exceptions.RequestException as e:
129
+ print(f"Telegram request failed: {e}")
130
+ return None
131
+ except json.JSONDecodeError as e:
132
+ print(f"Telegram response decode error: {e}")
133
+ return None
134
+
135
+ async def setBotCommands():
136
+ delete_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/deleteMyCommands"
137
+ set_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/setMyCommands"
138
+ if PHP_PROXY_URL: # 使用代理
139
+ delete_url = f"{PHP_PROXY_URL}deleteMyCommands"
140
+ set_url = f"{PHP_PROXY_URL}setMyCommands"
141
 
 
142
  try:
143
+ delete_response = make_telegram_request('deleteMyCommands')
144
+ if delete_response:
145
+ print('Telegram 命令删除成功')
146
+ else:
147
+ print('Telegram 命令删除失败')
148
+
149
+ set_response = make_telegram_request('setMyCommands', {"commands": BOT_COMMANDS})
150
+ if set_response:
151
+ print('Telegram 命令设置成功')
152
+ else:
153
+ print('设置 Telegram 命令失败')
154
+ except Exception as error:
155
+ print(f'设置 Telegram 命令时发生错误: {error}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ async def handleTelegramUpdate(update):
158
+ if not update.get('message'):
159
+ if update.get('callback_query'):
160
+ await handleCallbackQuery(update.get('callback_query'))
161
  return
162
 
163
+ chatId = update['message']['chat']['id']
164
+ userMessage = update['message'].get('text', '')
165
+ isGroupChat = update['message']['chat']['type'] in ['group', 'supergroup']
166
+ fromUserId = update['message']['from']['id']
167
 
168
+ if not userMessage:
169
  return
170
 
171
+ if userMessage.startswith('/'):
172
+ command = parseCommand(userMessage)
173
 
174
+ if command == 'clearall':
175
+ chatHistories.pop(chatId, None)
176
+ await sendTelegramMessage(chatId, '聊天记录已清空。')
177
  return
178
+ if command == 'test':
179
+ await sendTelegramMessage(chatId, '测试命令已执行')
180
  return
181
+ if command == 'help':
182
+ await sendTelegramMessage(chatId, getHelpMessage())
183
+ return
184
+ if command == 'start':
185
+ await sendTelegramMessage(chatId, "欢迎使用!请选择操作:", {
186
+ "reply_markup": {
187
  "inline_keyboard": [[{"text": "清空聊天记录", "callback_data": "clearall"}]],
188
  },
189
+ })
 
190
  return
191
+ if isGroupChat:
192
+ if command == 'enableai':
193
+ GROUP_SETTINGS.setdefault(chatId, {}).update({'aiEnabled': True})
194
+ await sendTelegramMessage(chatId, '已在群组中启用 AI 回复。')
195
  return
196
+ if command == 'disableai':
197
+ GROUP_SETTINGS.setdefault(chatId, {}).update({'aiEnabled': False})
198
+ await sendTelegramMessage(chatId, '已在群组中禁用 AI 回复。')
199
  return
200
+ if userMessage.startswith('/setprefix '):
201
+ prefix = userMessage[len('/setprefix '):].strip()
202
+ GROUP_SETTINGS.setdefault(chatId, {}).update({'prefix': prefix})
203
+ await sendTelegramMessage(chatId, f'已设置群组触发前缀为: {prefix}')
204
  return
205
+ if command == 'getprefix':
206
+ prefix = GROUP_SETTINGS.get(chatId, {}).get('prefix', '')
207
+ await sendTelegramMessage(chatId, f'当前群组触发前缀为: {prefix}')
208
  return
209
  else:
210
+ if userMessage.startswith('/settemp '):
211
  try:
212
+ temp = float(userMessage[len('/settemp '):].strip())
213
+ if 0 <= temp <= 2:
214
+ USER_SETTINGS.setdefault(fromUserId, {}).update({'temperature': temp})
215
+ await sendTelegramMessage(chatId, f'已设置AI回复温度为: {temp}')
216
+ else:
217
+ await sendTelegramMessage(chatId, '温度设置无效,请输入0到2之间的数字。')
218
  except ValueError:
219
+ await sendTelegramMessage(chatId, '温度设置无效,请输入0到2之间的数字。')
220
  return
221
+ if command == 'gettemp':
222
+ temp = USER_SETTINGS.get(fromUserId, {}).get('temperature', DEFAULT_TEMP)
223
+ await sendTelegramMessage(chatId, f'当前AI回复温度为: {temp}')
224
  return
225
+ if userMessage.startswith('/promat '):
226
  try:
227
+ index = int(userMessage[len('/promat '):].strip())
228
+ if index in PROMPT_TEMPLATES:
229
+ global CURRENT_PROMPT_INDEX
230
+ CURRENT_PROMPT_INDEX = index
231
+ await sendTelegramMessage(chatId, f'已切换到提示词 {index}。')
232
+ else:
233
+ await sendTelegramMessage(chatId, '提示词索引无效。请使用 /getpromat 查看可用的索引。')
 
 
234
  except ValueError:
235
+ await sendTelegramMessage(chatId, '提示词索引无效。请使用 /getpromat 查看可用的索引。')
 
 
236
  return
237
+ if command == 'getpromat':
238
+ await sendTelegramMessage(chatId, f'当前使用的提示词索引是: {CURRENT_PROMPT_INDEX}')
 
 
239
  return
240
+ if command == 'resetuser':
241
+ USER_SETTINGS.pop(fromUserId, None)
242
+ await sendTelegramMessage(chatId, '已重置您的个人设置。')
243
  return
244
+ if isGroupChat:
245
+ if chatId not in GROUP_SETTINGS:
246
+ GROUP_SETTINGS[chatId] = {'aiEnabled': True, 'prefix': None}
247
+ print(f'群组 {chatId} 首次检测到,默认启用 AI。')
248
 
249
+ groupSettings = GROUP_SETTINGS[chatId]
250
+ prefix = groupSettings.get('prefix')
 
 
251
 
252
+ if groupSettings['aiEnabled']:
253
+ if prefix and not userMessage.startswith(prefix):
 
 
254
  return
255
 
256
+ messageContent = userMessage[len(prefix):].strip() if prefix else userMessage
257
+ if messageContent:
258
+ await processAiMessage(chatId, messageContent, fromUserId)
259
  else:
260
+ await processAiMessage(chatId, userMessage, fromUserId)
261
 
262
+ def parseCommand(userMessage):
263
+ command = userMessage.split(' ')[0]
264
+ if '@' in command:
265
+ command = command.split('@')[0]
266
+ return command[1:]
267
+
268
+ async def processAiMessage(chatId, userMessage, fromUserId):
269
+ history = chatHistories.get(chatId, [])
270
+ userTemp = USER_SETTINGS.get(fromUserId, {}).get('temperature', DEFAULT_TEMP)
271
+ currentPrompt = PROMPT_TEMPLATES.get(CURRENT_PROMPT_INDEX, "")
272
+ history.append({'role': 'user', 'content': userMessage})
273
 
274
  if len(history) > MAX_HISTORY_LENGTH:
275
  history = history[-MAX_HISTORY_LENGTH:]
276
 
277
  messages = [
278
+ {'role': 'system', 'content': currentPrompt},
279
+ *history
280
  ]
281
+
282
+ eventEmitter = EventEmitter(async (event) => {
283
+ print('Event:', event)
284
+ if event.get('type') == 'status':
285
+ await sendTelegramMessage(chatId, f"状态更新: {event['data']['description']}")
286
+ elif event.get('type') == 'citation':
287
+ await sendTelegramMessage(chatId, f"引用信息: {event['data']['metadata'][0]['name']}")
288
+ })
289
 
290
  try:
291
+ ai_response = requests.post(AI_API_ENDPOINT, headers=AI_API_HEADERS, json={
292
+ 'model': AI_MODEL,
293
+ 'messages': messages,
294
+ 'max_tokens': MAX_TOKENS,
295
+ 'temperature': userTemp,
296
+ 'tools': TOOL_DEFINITIONS
297
+ })
298
+ ai_response.raise_for_status()
299
+ ai_data = ai_response.json()
300
+ ai_reply = await handleAiResponse(ai_data, chatId, history, eventEmitter)
301
+
302
+ history.append({'role': 'assistant', 'content': ai_reply})
303
+ chatHistories[chatId] = history
304
+
305
+ await sendTelegramMessage(chatId, ai_reply)
306
+ except requests.exceptions.RequestException as e:
307
+ print(f'AI API 响应失败: {e}')
308
+ await sendTelegramMessage(chatId, 'AI API 响应失败,请稍后再试')
 
 
 
 
 
 
 
 
309
  except Exception as error:
310
+ print(f'处理消息时发生错误: {error}')
311
+ await sendTelegramMessage(chatId, '处理消息时发生错误,请稍后再试')
312
+
313
+ async def handleAiResponse(ai_data, chatId, history, eventEmitter):
314
+ if ai_data and ai_data.get('choices') and len(ai_data['choices']) > 0:
315
+ choice = ai_data['choices'][0]
316
+ if choice.get('message') and choice['message'].get('content'):
317
+ return choice['message']['content']
318
+ elif choice.get('message') and choice['message'].get('tool_calls'):
319
+ toolCalls = choice['message']['tool_calls']
320
+ toolResults = []
321
+ for toolCall in toolCalls:
322
+ toolResult = await executeToolCall(toolCall, eventEmitter)
323
+ toolResults.append(toolResult)
324
+
325
+ newMessages = [
326
  *history,
327
+ {'role': "assistant", 'content': None, 'tool_calls': toolCalls},
328
+ *toolResults,
329
  ]
330
+ ai_response = requests.post(AI_API_ENDPOINT, headers=AI_API_HEADERS, json={
331
+ 'model': AI_MODEL,
332
+ 'messages': newMessages,
333
+ 'max_tokens': MAX_TOKENS,
334
+ 'temperature': USER_SETTINGS.get(chatId, {}).get('temperature', DEFAULT_TEMP)
335
+ })
336
+ ai_response.raise_for_status()
337
+ ai_data = ai_response.json()
338
+ if ai_data and ai_data.get('choices') and len(ai_data['choices']) > 0 and ai_data['choices'][0].get('message') and ai_data['choices'][0]['message'].get('content'):
339
+ return ai_data['choices'][0]['message']['content']
340
+ return 'AI 返回了无法识别的格式'
341
+ return 'AI 返回了无法识别的格式'
342
+
343
+ async def executeToolCall(toolCall, eventEmitter):
344
+ name = toolCall['function']['name']
345
+ args = toolCall['function'].get('arguments', {})
346
+
347
+ if name == 'web_scrape':
348
+ urls = args.get('urls', [])
349
+ if not urls or not isinstance(urls, list) or len(urls) == 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  return {
351
+ 'tool_call_id': toolCall['id'],
352
+ 'role': "tool",
353
+ 'name': name,
354
+ 'content': '请提供有效的 URL 列表。',
355
  }
356
+ api_url = "https://gpts.webpilot.ai/api/read"
357
+ headers = { "Content-Type": "application/json", "WebPilot-Friend-UID": "0" }
358
+
359
+ await eventEmitter.update_status(
360
+ f"开始读取 {len(urls)} 个网页",
361
+ False,
362
+ "web_search",
363
+ urls
364
+ )
365
+
366
+ async def processUrl(url):
367
+ try:
368
+ response = requests.post(api_url, headers=headers, json={
369
+ "link": url,
370
+ "ur": "summary of the page",
371
+ "lp": True,
372
+ "rt": False,
373
+ "l": "en",
374
+ })
375
+ response.raise_for_status()
376
+ result = response.json()
377
+ if result.get('rules'):
378
+ del result['rules']
379
+ content = json.dumps(result)
380
+ title = result.get('title', url)
381
+ await eventEmitter.send_citation(title, url, content)
382
+ return f"{content}\n"
383
+ except requests.exceptions.RequestException as e:
384
+ error_message = f"读取网页 {url} 时出错: {e}"
385
+ await eventEmitter.update_status(error_message, False, "web_scrape", [url])
386
+ await eventEmitter.send_citation(f"Error from {url}", url, str(e))
387
+ return f"URL: {url}\n错误: {error_message}\n"
388
+
389
+ results = await asyncio.gather(*[processUrl(url) for url in urls])
390
+
391
+ await eventEmitter.update_status(
392
+ f"已完成 {len(urls)} 个网页的读取",
393
+ True,
394
+ "web_search",
395
+ urls
396
+ )
397
+
398
+ return {
399
+ 'tool_call_id': toolCall['id'],
400
+ 'role': "tool",
401
+ 'name': name,
402
+ 'content': '\n'.join(results)
403
+ }
404
+ elif name == 'get_current_time':
405
+ now = datetime.utcnow()
406
+ utc8_time = now + datetime.timedelta(hours=8)
407
+ formatted_time = utc8_time.strftime("%H:%M:%S")
408
+ return {
409
+ 'tool_call_id': toolCall['id'],
410
+ 'role': "tool",
411
+ 'name': name,
412
+ 'content': f"Current Time: {formatted_time}"
413
+ }
414
+ elif name == 'get_current_date':
415
  now = datetime.utcnow()
416
+ utc8_time = now + datetime.timedelta(hours=8)
417
+ formatted_date = utc8_time.strftime("%A, %B %d, %Y")
 
 
418
  return {
419
+ 'tool_call_id': toolCall['id'],
420
+ 'role': "tool",
421
+ 'name': name,
422
+ 'content': f"Today's date is {formatted_date}"
423
  }
424
  else:
425
  return {
426
+ 'tool_call_id': toolCall['id'],
427
+ 'role': "tool",
428
+ 'name': name,
429
+ 'content': '未知的工具调用'
430
  }
431
 
432
+ async def handleCallbackQuery(callbackQuery):
433
+ chatId = callbackQuery['message']['chat']['id']
434
+ data = callbackQuery['data']
435
 
436
  if data == "clearall":
437
+ chatHistories.pop(chatId, None)
438
+ await sendTelegramMessage(chatId, "聊天记录已清空。")
439
+
440
+ await sendTelegramMessage(chatId, '请选择操作:', {
441
+ 'reply_markup': {
442
+ 'inline_keyboard': [[{'text': "清空聊天记录", 'callback_data': "clearall"}]],
 
 
443
  },
444
+ })
445
+
446
+ async def sendTelegramMessage(chatId, text, options={}):
447
+ url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
448
+ if PHP_PROXY_URL: # 使用代理
449
+ url = f"{PHP_PROXY_URL}sendMessage"
450
+ data = {
451
+ 'chat_id': chatId,
452
+ 'text': text,
453
+ **options
454
+ }
 
 
 
455
  try:
456
+ response = requests.post(url, headers={'Content-Type': 'application/json'}, json=data)
457
+ response.raise_for_status()
458
+ except requests.exceptions.RequestException as e:
459
+ print(f'发送 Telegram 消息失败: {e}')
460
+
 
 
461
 
462
+ def getHelpMessage():
463
  return f"""
464
  可用指令:
465
  /start - 显示欢迎信息和操作按钮。
 
488
  - 机器人具有攻击性,请谨慎使用。
489
  """
490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
 
492
+ @app.route('/update_commands', methods=['GET'])
493
+ async def update_commands():
494
+ await setBotCommands()
495
+ return jsonify({'message': 'Commands updated successfully!'})
496
 
 
 
 
497
 
498
+ @app.route('/', methods=['POST'])
499
+ async def handle_webhook():
500
  try:
501
+ update = request.get_json()
502
+ await handleTelegramUpdate(update)
503
+ return jsonify({'status': 'ok'})
504
  except Exception as e:
505
+ print(f"请求解析失败: {e}")
506
+ return jsonify({'status': 'error', 'message': str(e)}), 400
507
+
508
+
509
+ @app.route('/health', methods=['GET'])
510
+ def health_check():
511
+ return 'OK'
512
+
513
+
514
+ import asyncio
515
+ if __name__ == '__main__':
516
+ app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
517
+