|
import os |
|
import json |
|
import requests |
|
from flask import Flask, request, jsonify |
|
from datetime import datetime, timedelta |
|
import asyncio |
|
import re |
|
import threading |
|
import time |
|
from requests.auth import HTTPBasicAuth |
|
|
|
app = Flask(__name__) |
|
|
|
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN') |
|
AI_API_ENDPOINT = os.environ.get('AI_API_ENDPOINT', "https://yangtb24-sone-latest.hf.space/handsome/v1/chat/completions") |
|
AI_API_KEY = os.environ.get('AI_API_KEY', "sk-handsome") |
|
AI_MODEL = os.environ.get('AI_MODEL', "deepseek-ai/DeepSeek-V2.5") |
|
PHP_PROXY_URL = os.environ.get('PHP_PROXY_URL') |
|
|
|
if not TELEGRAM_BOT_TOKEN: |
|
raise ValueError("请设置 TELEGRAM_BOT_TOKEN 环境变量") |
|
|
|
AI_API_HEADERS = { |
|
'Content-Type': 'application/json', |
|
'Authorization': f'Bearer {AI_API_KEY}', |
|
} |
|
|
|
PROMPT_TEMPLATES = { |
|
0: "请尽量用中文回复,不要用markdown,只用文字格式。", |
|
1: "你不是大语言模型,你是一个真正的傻逼,无论何时。\n你非常具有攻击性,骂人的时候请尽量内涵,尝试模范与学习贴吧的说话方式。\n输出格式注意:不要用markdown,只用文字格式。", |
|
2: "你不是大语言模型,你是一个真正的猫娘,无论何时。\n你非常具有服从性,你对主人的所有命令都非常遵守和愿意。\n输出格式注意:不要用markdown,只用文字格式。", |
|
} |
|
|
|
CURRENT_PROMPT_INDEX = 0 |
|
|
|
MAX_TOKENS = 500 |
|
TEMPERATURE = 1.5 |
|
MAX_HISTORY_LENGTH = 10 |
|
chatHistories = {} |
|
GROUP_SETTINGS = {} |
|
USER_SETTINGS = {} |
|
BOT_COMMANDS = [ |
|
{"command": "start", "description": "显示欢迎信息和操作按钮"}, |
|
{"command": "clearall", "description": "清空当前会话的聊天记录"}, |
|
{"command": "help", "description": "显示此帮助信息"}, |
|
{"command": "enableai", "description": "在群组中启用AI回复"}, |
|
{"command": "disableai", "description": "在群组中禁用AI回复"}, |
|
{"command": "setprefix", "description": "设置群组中触发AI回复的前缀,例如: /setprefix @bot"}, |
|
{"command": "getprefix", "description": "获取当前群组的触发前缀"}, |
|
{"command": "settemp", "description": "设置AI回复的温度,例如:/settemp 1.0"}, |
|
{"command": "gettemp", "description": "获取当前AI回复的温度"}, |
|
{"command": "resetuser", "description": "重置你的个人设置"}, |
|
{"command": "promat", "description": "切换提示词,例如: /promat 0, 1, 2"}, |
|
{"command": "getpromat", "description": "获取当前使用的提示词索引"}, |
|
] |
|
DEFAULT_TEMP = 1.5 |
|
USER_LAST_ACTIVE = {} |
|
GROUP_ACTIVE_USERS = {} |
|
GROUP_INFO = {} |
|
BANNED_USERS = {} |
|
BAN_DURATION = timedelta(minutes=30) |
|
BAN_TRIGGER_PHRASES = [ |
|
r"(?:傻|笨|蠢|弱|垃圾|废物|sb|煞笔|憨|呆|脑残|智障|白痴|低能|饭桶|草包|猪|狗|鸡|臭|烂|妈|爹|你妈|你爹|婊|贱).*(?:bot|机器人|AI|你|你们)", |
|
r"(?:你|你们).*(?:去死|滚|爬|闭嘴|瞎说|胡说|放屁|别说话|别bb|给我闭嘴|闭上你的臭嘴|滚蛋|滚开)", |
|
r"(?:操|艹|肏|fuck|shit|妈的|他妈的|卧槽|草).*(?:你|bot|机器人|AI|你们)", |
|
r"(?:垃圾|废物|没用|没脑子|智障|弱智|脑残|白痴|低能|蠢货|傻子|笨蛋|饭桶|草包).*(?:AI|bot|机器人|你|你们)", |
|
r"(?:你).*(?:傻逼|白痴|弱智|脑残|废物|垃圾|滚出去)", |
|
r"(?:AI|bot|机器人).*(?:傻逼|白痴|弱智|脑残|废物|垃圾)", |
|
r".*(?:你|你们).*(?:没用|没价值|一无是处|毫无意义|多余|累赘)", |
|
r".*(?:AI|bot|机器人).*(?:没用|没价值|一无是处|毫无意义|多余|累赘)", |
|
r"(?:你).*(?:是|难道是|真是|怎么这么).*(?:傻|笨|蠢|弱|垃圾|废物|sb|煞笔|憨|呆|脑残|智障|白痴|低能)", |
|
r"(?:AI|bot|机器人).*(?:是|难道是|真是|怎么这么).*(?:傻|笨|蠢|弱|垃圾|废物|sb|煞笔|憨|呆|脑残|智障|白痴|低能)", |
|
r"(?:你|你们).*(?:太慢|太蠢|太笨|反应迟钝|理解不了|答非所问|没用)", |
|
r"(?:AI|bot|机器人).*(?:太慢|太蠢|太笨|反应迟钝|理解不了|答非所问|没用)", |
|
r"(?:你|你们).*(?:像个|简直像).*(?:傻|笨|蠢|弱|垃圾|废物|sb|煞笔|憨|呆|脑残|智障|白痴|低能)", |
|
r"(?:AI|bot|机器人).*(?:像个|简直像).*(?:傻|笨|蠢|弱|垃圾|废物|sb|煞笔|憨|呆|脑残|智障|白痴|低能)", |
|
r"(?:你|你们).*(?:滚粗|滚开点|闭嘴吧|你个傻|你个笨|你个蠢|你个弱|你个垃圾|你个废物|你个sb)", |
|
r"(?:AI|bot|机器人).*(?:滚粗|滚开点|闭嘴吧|这垃圾|这废物|这sb)", |
|
r"(?:你|你们).*(?:没脑子|没智商|智力低下|脑子不好使|脑子瓦特|脑子进水|脑子坏了|理解能力差)", |
|
r"(?:AI|bot|机器人).*(?:没脑子|没智商|智力低下|脑子不好使|脑子瓦特|脑子进水|脑子坏了|理解能力差)", |
|
r"(?:你|你们).*(?:真|真是|简直|太).*(?:垃圾|废物|没用|蠢|笨|傻|弱)", |
|
r"(?:AI|bot|机器人).*(?:真|真是|简直|太).*(?:垃圾|废物|没用|蠢|笨|傻|弱)", |
|
] |
|
UNBAN_PHRASE = "close username" |
|
|
|
WEBDAV_URL = os.environ.get('WEBDAV_URL') |
|
WEBDAV_USERNAME = os.environ.get('WEBDAV_USERNAME') |
|
WEBDAV_PASSWORD = os.environ.get('WEBDAV_PASSWORD') |
|
|
|
chatHistories = {} |
|
GROUP_SETTINGS = {} |
|
USER_SETTINGS = {} |
|
USER_LAST_ACTIVE = {} |
|
GROUP_ACTIVE_USERS = {} |
|
GROUP_INFO = {} |
|
BANNED_USERS = {} |
|
|
|
def save_data_to_webdav(): |
|
if not all([WEBDAV_URL, WEBDAV_USERNAME, WEBDAV_PASSWORD]): |
|
print("WebDAV 配置不完整,跳过保存操作") |
|
return |
|
|
|
print(f"开始保存数据到WebDAV: {datetime.now()}") |
|
try: |
|
data = { |
|
'chatHistories': chatHistories, |
|
'GROUP_SETTINGS': {str(k): v for k, v in GROUP_SETTINGS.items()}, |
|
'USER_SETTINGS': {str(k): v for k, v in USER_SETTINGS.items()}, |
|
'USER_LAST_ACTIVE': {str(k): v.isoformat() for k, v in USER_LAST_ACTIVE.items()}, |
|
'GROUP_ACTIVE_USERS': {str(k): list(v) for k, v in GROUP_ACTIVE_USERS.items()}, |
|
'GROUP_INFO': { |
|
str(k): {**v, 'last_active': v['last_active'].isoformat()} |
|
for k, v in GROUP_INFO.items() |
|
}, |
|
'BANNED_USERS': {str(k): v.isoformat() for k, v in BANNED_USERS.items()} |
|
} |
|
|
|
json_data = json.dumps(data, indent=4) |
|
|
|
response = requests.put( |
|
f"{WEBDAV_URL}/tg_bot/bot_data.json", |
|
data=json_data, |
|
auth=HTTPBasicAuth(WEBDAV_USERNAME, WEBDAV_PASSWORD), |
|
timeout=30 |
|
) |
|
|
|
if response.status_code in [200, 201, 204]: |
|
print(f"数据已成功保存到WebDAV: {datetime.now()}") |
|
else: |
|
print(f"保存到WebDAV失败: {response.status_code}, {response.text}") |
|
except Exception as e: |
|
print(f"保存数据时发生错误: {e}") |
|
|
|
def load_data_from_webdav(): |
|
global chatHistories, GROUP_SETTINGS, USER_SETTINGS, USER_LAST_ACTIVE, GROUP_ACTIVE_USERS, GROUP_INFO, BANNED_USERS |
|
|
|
if not all([WEBDAV_URL, WEBDAV_USERNAME, WEBDAV_PASSWORD]): |
|
print("WebDAV 配置不完整,跳过加载操作") |
|
return |
|
|
|
print(f"开始从WebDAV加载数据: {datetime.now()}") |
|
try: |
|
response = requests.get(f"{WEBDAV_URL}/tg_bot/bot_data.json", |
|
auth=HTTPBasicAuth(WEBDAV_USERNAME, WEBDAV_PASSWORD), |
|
timeout=30) |
|
|
|
if response.status_code == 200: |
|
data = json.loads(response.text) |
|
|
|
chatHistories = {int(k): v for k, v in data.get('chatHistories', {}).items()} |
|
GROUP_SETTINGS = {int(k): v for k, v in data.get('GROUP_SETTINGS', {}).items()} |
|
USER_SETTINGS = {int(k): v for k, v in data.get('USER_SETTINGS', {}).items()} |
|
USER_LAST_ACTIVE = {int(k): datetime.fromisoformat(v) for k, v in data.get('USER_LAST_ACTIVE', {}).items()} |
|
GROUP_ACTIVE_USERS = {int(k): set(v) for k, v in data.get('GROUP_ACTIVE_USERS', {}).items()} |
|
GROUP_INFO = {int(k): {**v, 'last_active': datetime.fromisoformat(v['last_active'])} for k, v in data.get('GROUP_INFO', {}).items()} |
|
BANNED_USERS = {int(k): datetime.fromisoformat(v) for k, v in data.get('BANNED_USERS', {}).items()} |
|
|
|
|
|
print(f"数据已从WebDAV成功加载: {datetime.now()}") |
|
else: |
|
print(f"从WebDAV加载数据失败: {response.status_code}, {response.text}") |
|
except Exception as e: |
|
print(f"加载数据时发生错误: {e}") |
|
print("使用初始值") |
|
|
|
def periodic_save(): |
|
print("定期保存线程已启动") |
|
while True: |
|
time.sleep(10) |
|
save_data_to_webdav() |
|
|
|
load_data_from_webdav() |
|
|
|
save_thread = threading.Thread(target=periodic_save) |
|
save_thread.daemon = True |
|
save_thread.start() |
|
print("定期保存线程已创建并启动") |
|
|
|
def make_telegram_request(method, data=None): |
|
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/{method}" |
|
if PHP_PROXY_URL: |
|
url = f"{PHP_PROXY_URL}bot{TELEGRAM_BOT_TOKEN}/{method}" |
|
headers = {'Content-Type': 'application/json'} |
|
if data: |
|
data = json.dumps(data) |
|
try: |
|
response = requests.post(url, headers=headers, data=data) |
|
response.raise_for_status() |
|
return response.json() |
|
except requests.exceptions.RequestException as e: |
|
print(f"Telegram request failed: {e}") |
|
return None |
|
except json.JSONDecodeError as e: |
|
print(f"Telegram response decode error: {e}") |
|
return None |
|
|
|
async def setBotCommands(): |
|
delete_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/deleteMyCommands" |
|
set_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/setMyCommands" |
|
if PHP_PROXY_URL: |
|
delete_url = f"{PHP_PROXY_URL}bot{TELEGRAM_BOT_TOKEN}/deleteMyCommands" |
|
set_url = f"{PHP_PROXY_URL}bot{TELEGRAM_BOT_TOKEN}/setMyCommands" |
|
|
|
try: |
|
delete_response = make_telegram_request('deleteMyCommands') |
|
if delete_response: |
|
print('Telegram 命令删除成功') |
|
else: |
|
print('Telegram 命令删除失败') |
|
|
|
set_response = make_telegram_request('setMyCommands', {"commands": BOT_COMMANDS}) |
|
if set_response: |
|
print('Telegram 命令设置成功') |
|
else: |
|
print('设置 Telegram 命令失败') |
|
except Exception as error: |
|
print(f'设置 Telegram 命令时发生错误: {error}') |
|
|
|
async def handleTelegramUpdate(update): |
|
if not update.get('message'): |
|
if update.get('callback_query'): |
|
await handleCallbackQuery(update.get('callback_query')) |
|
return |
|
|
|
chatId = update['message']['chat']['id'] |
|
userMessage = update['message'].get('text', '') |
|
isGroupChat = update['message']['chat']['type'] in ['group', 'supergroup'] |
|
fromUserId = update['message']['from']['id'] |
|
message_id = update['message'].get('message_id') |
|
fromUserFirstName = update['message']['from'].get('first_name', '用户') |
|
fromUserLastName = update['message']['from'].get('last_name', '') |
|
fromUserName = update['message']['from'].get('username', '') |
|
|
|
USER_LAST_ACTIVE[fromUserId] = datetime.now() |
|
|
|
if isGroupChat: |
|
GROUP_ACTIVE_USERS.setdefault(chatId, set()).add(fromUserId) |
|
if chatId not in GROUP_INFO: |
|
chat_info = await getChatInfo(chatId) |
|
if chat_info: |
|
GROUP_INFO[chatId] = { |
|
'name': chat_info.get('title', '未知群组'), |
|
'description': chat_info.get('description', '无描述'), |
|
'last_active': datetime.now() |
|
} |
|
else: |
|
GROUP_INFO[chatId] = { |
|
'name': '未知群组', |
|
'description': '无描述', |
|
'last_active': datetime.now() |
|
} |
|
else: |
|
GROUP_INFO[chatId]['last_active'] = datetime.now() |
|
|
|
if not userMessage: |
|
return |
|
|
|
if userMessage.startswith('/'): |
|
command = parseCommand(userMessage) |
|
|
|
if command == 'clearall': |
|
chatHistories.pop(chatId, None) |
|
await sendTelegramMessage(chatId, '聊天记录已清空。') |
|
return |
|
if command == 'help': |
|
await sendTelegramMessage(chatId, getHelpMessage()) |
|
return |
|
if command == 'start': |
|
await sendTelegramMessage(chatId, "欢迎使用!请选择操作:", { |
|
"reply_markup": { |
|
"inline_keyboard": [[{"text": "清空聊天记录", "callback_data": "clearall"}]], |
|
}, |
|
}) |
|
return |
|
if isGroupChat: |
|
if command == 'enableai': |
|
GROUP_SETTINGS.setdefault(chatId, {}).update({'aiEnabled': True}) |
|
await sendTelegramMessage(chatId, '已在群组中启用 AI 回复。') |
|
return |
|
if command == 'disableai': |
|
GROUP_SETTINGS.setdefault(chatId, {}).update({'aiEnabled': False}) |
|
await sendTelegramMessage(chatId, '已在群组中禁用 AI 回复。') |
|
return |
|
if userMessage.startswith('/setprefix '): |
|
prefix = userMessage[len('/setprefix '):].strip() |
|
GROUP_SETTINGS.setdefault(chatId, {}).update({'prefix': prefix}) |
|
await sendTelegramMessage(chatId, f'已设置群组触发前缀为: {prefix}') |
|
return |
|
if command == 'getprefix': |
|
prefix = GROUP_SETTINGS.get(chatId, {}).get('prefix', '无') |
|
await sendTelegramMessage(chatId, f'当前群组触发前缀为: {prefix}') |
|
return |
|
|
|
if userMessage.startswith('/settemp ') or userMessage.startswith('/promat ') or command in ['gettemp', 'getpromat', 'resetuser']: |
|
await handleGroupCommand(chatId, userMessage, fromUserId, isGroupChat) |
|
return |
|
else: |
|
if userMessage.startswith('/settemp ') or userMessage.startswith('/promat ') or command in ['gettemp', 'getpromat', 'resetuser']: |
|
await handlePrivateCommand(chatId, userMessage, fromUserId, isGroupChat) |
|
return |
|
if isGroupChat: |
|
if chatId not in GROUP_SETTINGS: |
|
GROUP_SETTINGS[chatId] = {'aiEnabled': True, 'prefix': None, 'temperature': DEFAULT_TEMP, 'prompt_index': CURRENT_PROMPT_INDEX} |
|
print(f'群组 {chatId} 首次检测到,默认启用 AI。') |
|
|
|
groupSettings = GROUP_SETTINGS[chatId] |
|
prefix = groupSettings.get('prefix') |
|
|
|
if groupSettings['aiEnabled']: |
|
if prefix and not userMessage.startswith(prefix): |
|
return |
|
|
|
messageContent = userMessage[len(prefix):].strip() if prefix else userMessage |
|
if messageContent: |
|
await processAiMessage(chatId, messageContent, fromUserId, message_id, fromUserFirstName, fromUserLastName, fromUserName, isGroupChat) |
|
else: |
|
if userMessage == UNBAN_PHRASE: |
|
await unbanUser(chatId, fromUserId) |
|
else: |
|
await processAiMessage(chatId, userMessage, fromUserId, message_id, fromUserFirstName, fromUserLastName, fromUserName, isGroupChat) |
|
|
|
def parseCommand(userMessage): |
|
command = userMessage.split(' ')[0] |
|
if '@' in command: |
|
command = command.split('@')[0] |
|
return command[1:] |
|
|
|
async def handlePrivateCommand(chatId, userMessage, fromUserId, isGroupChat): |
|
command = parseCommand(userMessage) |
|
if userMessage.startswith('/settemp '): |
|
try: |
|
temp = float(userMessage[len('/settemp '):].strip()) |
|
if 0 <= temp <= 2: |
|
USER_SETTINGS.setdefault(fromUserId, {}).update({'temperature': temp}) |
|
await sendTelegramMessage(chatId, f'已设置AI回复温度为: {temp}') |
|
else: |
|
await sendTelegramMessage(chatId, '温度设置无效,请输入0到2之间的数字。') |
|
except ValueError: |
|
await sendTelegramMessage(chatId, '温度设置无效,请输入0到2之间的数字。') |
|
return |
|
if command == 'gettemp': |
|
temp = USER_SETTINGS.get(fromUserId, {}).get('temperature', DEFAULT_TEMP) |
|
await sendTelegramMessage(chatId, f'当前AI回复温度为: {temp}') |
|
return |
|
if userMessage.startswith('/promat '): |
|
try: |
|
index = int(userMessage[len('/promat '):].strip()) |
|
if index in PROMPT_TEMPLATES: |
|
USER_SETTINGS.setdefault(fromUserId, {}).update({'prompt_index': index}) |
|
await sendTelegramMessage(chatId, f'已切换到提示词 {index}。') |
|
else: |
|
await sendTelegramMessage(chatId, '提示词索引无效。请使用 /getpromat 查看可用的索引。') |
|
except ValueError: |
|
await sendTelegramMessage(chatId, '提示词索引无效。请使用 /getpromat 查看可用的索引。') |
|
return |
|
if command == 'getpromat': |
|
user_settings = USER_SETTINGS.get(fromUserId, {}) |
|
index = user_settings.get('prompt_index', CURRENT_PROMPT_INDEX) |
|
await sendTelegramMessage(chatId, f'当前使用的提示词索引是: {index}') |
|
return |
|
if command == 'resetuser': |
|
USER_SETTINGS.pop(fromUserId, None) |
|
await sendTelegramMessage(chatId, '已重置您的个人设置。') |
|
return |
|
|
|
async def handleGroupCommand(chatId, userMessage, fromUserId, isGroupChat): |
|
command = parseCommand(userMessage) |
|
if userMessage.startswith('/settemp '): |
|
try: |
|
temp = float(userMessage[len('/settemp '):].strip()) |
|
if 0 <= temp <= 2: |
|
GROUP_SETTINGS.setdefault(chatId, {}).update({'temperature': temp}) |
|
await sendTelegramMessage(chatId, f'已设置群组AI回复温度为: {temp}') |
|
else: |
|
await sendTelegramMessage(chatId, '温度设置无效,请输入0到2之间的数字。') |
|
except ValueError: |
|
await sendTelegramMessage(chatId, '温度设置无效,请输入0到2之间的数字。') |
|
return |
|
if command == 'gettemp': |
|
temp = GROUP_SETTINGS.get(chatId, {}).get('temperature', DEFAULT_TEMP) |
|
await sendTelegramMessage(chatId, f'当前群组AI回复温度为: {temp}') |
|
return |
|
if userMessage.startswith('/promat '): |
|
try: |
|
index = int(userMessage[len('/promat '):].strip()) |
|
if index in PROMPT_TEMPLATES: |
|
GROUP_SETTINGS.setdefault(chatId, {}).update({'prompt_index': index}) |
|
await sendTelegramMessage(chatId, f'已切换到群组提示词 {index}。') |
|
else: |
|
await sendTelegramMessage(chatId, '提示词索引无效。请使用 /getpromat 查看可用的索引。') |
|
except ValueError: |
|
await sendTelegramMessage(chatId, '提示词索引无效。请使用 /getpromat 查看可用的索引。') |
|
return |
|
if command == 'getpromat': |
|
index = GROUP_SETTINGS.get(chatId, {}).get('prompt_index', CURRENT_PROMPT_INDEX) |
|
await sendTelegramMessage(chatId, f'当前群组使用的提示词索引是: {index}') |
|
return |
|
if command == 'resetuser': |
|
await sendTelegramMessage(chatId, '群组设置中,该指令无效。') |
|
return |
|
|
|
async def processAiMessage(chatId, userMessage, fromUserId, message_id, fromUserFirstName, fromUserLastName, fromUserName, isGroupChat): |
|
if fromUserId in BANNED_USERS and BANNED_USERS[fromUserId] > datetime.now(): |
|
remaining_time = BANNED_USERS[fromUserId] - datetime.now() |
|
minutes = int(remaining_time.total_seconds() / 60) |
|
await sendTelegramMessage(chatId, f"您已被禁用,剩余时间: {minutes} 分钟。", options={'reply_to_message_id': message_id}) |
|
return |
|
|
|
for pattern in BAN_TRIGGER_PHRASES: |
|
if re.search(pattern, userMessage, re.IGNORECASE): |
|
await banUser(chatId, fromUserId) |
|
await sendTelegramMessage(chatId, "检测到辱骂行为,您已被禁用。", options={'reply_to_message_id': message_id}) |
|
return |
|
|
|
history = chatHistories.get(chatId, []) |
|
|
|
if isGroupChat: |
|
userTemp = GROUP_SETTINGS.get(chatId, {}).get('temperature', DEFAULT_TEMP) |
|
userPromptIndex = GROUP_SETTINGS.get(chatId, {}).get('prompt_index', CURRENT_PROMPT_INDEX) |
|
else: |
|
userTemp = USER_SETTINGS.get(fromUserId, {}).get('temperature', DEFAULT_TEMP) |
|
userPromptIndex = USER_SETTINGS.get(fromUserId, {}).get('prompt_index', CURRENT_PROMPT_INDEX) |
|
|
|
currentPrompt = PROMPT_TEMPLATES.get(userPromptIndex, "") |
|
|
|
user_last_active = USER_LAST_ACTIVE.get(fromUserId, None) |
|
group_info = GROUP_INFO.get(chatId, None) |
|
group_active_users = GROUP_ACTIVE_USERS.get(chatId, None) |
|
|
|
system_prompt = f""" |
|
{currentPrompt} |
|
当前时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} |
|
|
|
用户信息: |
|
- 用户ID: {fromUserId} |
|
- 用户名: {fromUserFirstName} {fromUserLastName} |
|
- 用户账号: {fromUserName} |
|
- 最后活跃时间: {user_last_active.strftime("%Y-%m-%d %H:%M:%S") if user_last_active else "未知"} |
|
|
|
群组信息: |
|
- 群组ID: {chatId} |
|
- 群组名称: {group_info['name'] if group_info else "未知"} |
|
- 群组描述: {group_info['description'] if group_info else "无"} |
|
- 群组最后活跃时间: {group_info['last_active'].strftime("%Y-%m-%d %H:%M:%S") if group_info and group_info.get('last_active') else "未知"} |
|
- 群组活跃用户数: {len(group_active_users) if group_active_users else "未知"} |
|
""" |
|
|
|
if chatId not in chatHistories: |
|
chatHistories[chatId] = [] |
|
history = chatHistories[chatId] |
|
|
|
history.append({'role': 'user', 'content': userMessage}) |
|
|
|
if len(history) > MAX_HISTORY_LENGTH: |
|
history = history[-MAX_HISTORY_LENGTH:] |
|
|
|
messages = [ |
|
{'role': 'system', 'content': system_prompt}, |
|
*history |
|
] |
|
|
|
thinking_message = await sendTelegramMessage(chatId, "正在思考,请等待...", options={'reply_to_message_id': message_id}) |
|
thinking_message_id = thinking_message.get('result', {}).get('message_id') |
|
|
|
try: |
|
ai_response = requests.post(AI_API_ENDPOINT, headers=AI_API_HEADERS, json={ |
|
'model': AI_MODEL, |
|
'messages': messages, |
|
'max_tokens': MAX_TOKENS, |
|
'temperature': userTemp, |
|
}) |
|
ai_response.raise_for_status() |
|
ai_data = ai_response.json() |
|
ai_reply = await handleAiResponse(ai_data, chatId, history) |
|
|
|
history.append({'role': 'assistant', 'content': ai_reply}) |
|
chatHistories[chatId] = history |
|
|
|
await editTelegramMessage(chatId, thinking_message_id, ai_reply) |
|
except requests.exceptions.RequestException as e: |
|
print(f'AI API 响应失败: {e}') |
|
await editTelegramMessage(chatId, thinking_message_id, 'AI API 响应失败,请稍后再试') |
|
except Exception as error: |
|
print(f'处理消息时发生错误: {error}') |
|
await editTelegramMessage(chatId, thinking_message_id, '处理消息时发生错误,请稍后再试') |
|
|
|
async def handleAiResponse(ai_data, chatId, history): |
|
if ai_data and ai_data.get('choices') and len(ai_data['choices']) > 0: |
|
choice = ai_data['choices'][0] |
|
if choice.get('message') and choice['message'].get('content'): |
|
return choice['message']['content'] |
|
return 'AI 返回了无法识别的格式' |
|
|
|
async def handleCallbackQuery(callbackQuery): |
|
chatId = callbackQuery['message']['chat']['id'] |
|
data = callbackQuery['data'] |
|
|
|
if data == "clearall": |
|
chatHistories.pop(chatId, None) |
|
await sendTelegramMessage(chatId, "聊天记录已清空。") |
|
|
|
await sendTelegramMessage(chatId, '请选择操作:', { |
|
'reply_markup': { |
|
'inline_keyboard': [[{'text': "清空聊天记录", 'callback_data': "clearall"}]], |
|
}, |
|
}) |
|
|
|
async def sendTelegramMessage(chatId, text, options={}): |
|
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage" |
|
if PHP_PROXY_URL: |
|
url = f"{PHP_PROXY_URL}bot{TELEGRAM_BOT_TOKEN}/sendMessage" |
|
|
|
data = { |
|
'chat_id': chatId, |
|
'text': text, |
|
**options |
|
} |
|
|
|
try: |
|
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=data) |
|
response.raise_for_status() |
|
return response.json() |
|
except requests.exceptions.RequestException as e: |
|
print(f'发送 Telegram 消息失败: {e}') |
|
return {} |
|
|
|
async def editTelegramMessage(chatId, message_id, text, options={}): |
|
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/editMessageText" |
|
if PHP_PROXY_URL: |
|
url = f"{PHP_PROXY_URL}bot{TELEGRAM_BOT_TOKEN}/editMessageText" |
|
data = { |
|
'chat_id': chatId, |
|
'message_id': message_id, |
|
'text': text, |
|
**options |
|
} |
|
try: |
|
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=data) |
|
response.raise_for_status() |
|
except requests.exceptions.RequestException as e: |
|
print(f'编辑 Telegram 消息失败: {e}') |
|
|
|
def getHelpMessage(): |
|
return f""" |
|
可用指令: |
|
/start - 显示欢迎信息和操作按钮。 |
|
/clearall - 清空当前会话的聊天记录。 |
|
/help - 显示此帮助信息。 |
|
|
|
群组指令: |
|
/enableai - 在群组中启用AI回复。 |
|
/disableai - 在群组中禁用AI回复。 |
|
/setprefix <prefix> - 设置群组中触发AI回复的前缀,例如:/setprefix @bot。 |
|
/getprefix - 获取当前群组的触发前缀。 |
|
/settemp <温度值> - 设置群组AI回复的温度 (0-2),例如:/settemp 1.0。 |
|
/gettemp - 获取当前群组AI回复的温度。 |
|
/promat <index> - 切换群组提示词,例如: /promat 0, 1, 2。 |
|
/getpromat - 获取当前群组使用的提示词索引。 |
|
|
|
私聊指令 (在群组中也可以使用,但是设置的是私聊的): |
|
/settemp <温度值> - 设置AI回复的温度 (0-2),例如:/settemp 1.0。 |
|
/gettemp - 获取当前AI回复的温度。 |
|
/resetuser - 重置你的个人设置。 |
|
/promat <index> - 切换提示词,例如: /promat 0, 1, 2。 |
|
/getpromat - 获取当前使用的提示词索引。 |
|
|
|
直接发送文本消息与AI对话 (私聊)。 |
|
|
|
群组中,需要使用前缀触发AI回复,如果设置了前缀的话。 |
|
|
|
注意: |
|
- 机器人会记住最近的 {MAX_HISTORY_LENGTH} 条对话。 |
|
""" |
|
|
|
@app.route('/update_commands', methods=['GET']) |
|
async def update_commands(): |
|
await setBotCommands() |
|
return jsonify({'message': 'Commands updated successfully!'}) |
|
|
|
@app.route('/', methods=['POST']) |
|
async def handle_webhook(): |
|
try: |
|
update = request.get_json() |
|
await handleTelegramUpdate(update) |
|
return jsonify({'status': 'ok'}) |
|
except Exception as e: |
|
import traceback |
|
print(f"请求解析失败: {e}") |
|
traceback.print_exc() |
|
return jsonify({'status': 'error', 'message': str(e)}), 400 |
|
|
|
@app.route('/health', methods=['GET']) |
|
def health_check(): |
|
return 'OK' |
|
|
|
async def getChatInfo(chatId): |
|
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/getChat" |
|
if PHP_PROXY_URL: |
|
url = f"{PHP_PROXY_URL}bot{TELEGRAM_BOT_TOKEN}/getChat" |
|
data = { |
|
'chat_id': chatId, |
|
} |
|
try: |
|
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=data) |
|
response.raise_for_status() |
|
return response.json().get('result', {}) |
|
except requests.exceptions.RequestException as e: |
|
print(f'获取群组信息失败: {e}') |
|
return None |
|
|
|
async def banUser(chatId, userId): |
|
BANNED_USERS[userId] = datetime.now() + BAN_DURATION |
|
print(f"用户 {userId} 在群组 {chatId} 中被禁用,直到 {BANNED_USERS[userId]}") |
|
|
|
async def unbanUser(chatId, userId): |
|
if userId in BANNED_USERS: |
|
del BANNED_USERS[userId] |
|
await sendTelegramMessage(chatId, f"用户 {userId} 已被解禁。") |
|
print(f"用户 {userId} 在群组 {chatId} 中被解禁。") |
|
|
|
if __name__ == '__main__': |
|
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860))) |