├── .gitignore ├── LICENSE ├── README.md ├── bot.py ├── config.json.example └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | __pycache__ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ukenn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPTelegramBot 2 | 3 | 4 | ## 功能 5 | 6 | - [x] 在 Telegram 上与 ChatGPT 对话 7 | - [x] 重置对话 8 | - [x] 私人/群组对话 `(在群组或者私人每个人都是单独的一个会话,多个会话不会相互干扰)` 9 | 10 | ## 使用方法 11 | 12 | - 安装 [Redis](https://redis.io/) 13 | 14 | 您可以参考 [Redis 安装教程](https://www.google.com/search?q=Redis%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B) 15 | 16 | - 修改文件后缀 `config.json.example` 为 `config.json` 17 | 18 | 根据文件内提示修改 `config.json` 配置文件 19 | 20 | - 安装依赖 21 | ``` 22 | pip3 install -r requirements.txt 23 | ``` 24 | 25 | - 运行 Telegram Bot 模块 26 | 27 | ``` 28 | python3 bot.py 29 | ``` 30 | 31 | ## 命令列表 32 | 33 | ``` 34 | start - 开始 35 | help - 使用帮助 36 | rechat - 重置对话 37 | addwhite - 添加白名单 38 | ``` 39 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import logging 4 | import time 5 | 6 | import openai 7 | import redis 8 | import telebot 9 | from telebot.async_telebot import AsyncTeleBot 10 | import aiohttp 11 | 12 | telebot.logger.setLevel(logging.ERROR) 13 | logging.getLogger().setLevel(logging.INFO) 14 | 15 | logging.basicConfig( 16 | format='[%(levelname)s]%(asctime)s: %(message)s', 17 | ) 18 | 19 | with open('config.json', "r") as f: config = json.load(f) 20 | redis_pool = redis.Redis(host=config.get('redis_host'), port=config.get('redis_port'), db=config.get('redis_db')) 21 | bot = AsyncTeleBot(config.get('bot_token')) 22 | openai.api_key = config.get('api_key') 23 | 24 | async def balance_check() -> bool: 25 | s = aiohttp.ClientSession( 26 | headers={"authorization": "Bearer " + config.get('api_key')}, 27 | ) 28 | try: 29 | async with s.get('https://api.openai.com/dashboard/billing/credit_grants') as resp: 30 | data = await resp.json() 31 | data = data['grants']['data'][0] 32 | if data['grant_amount'] - data['used_amount'] < config.get('balance_limit'): 33 | logging.warning('OpenAI API 余额到达预设阈值') 34 | return True 35 | else: 36 | return False 37 | finally: 38 | await s.close() 39 | 40 | 41 | @bot.message_handler(commands=['start', 'help'], chat_types=['private']) 42 | async def start_message(message): 43 | text = ( 44 | '*欢迎使用 ChatGPT 机器人*\n\n' 45 | '我是一个语言模型,被训练来回答各种问题。我能够理解和回答许多不同类型的问题,并尽力为您提供准确和有用的信息。\n' 46 | '我的知识涵盖了各种领域,包括文学、历史、科学、技术、艺术、娱乐等等。我能够理解和回答许多不同语言的问题,包括英语、日语、中文等。\n' 47 | '我不能上网,所以我无法获取当前最新的信息。我的知识截止于2021年,所以如果您问我一些过于新鲜的问题,我可能无法回答。如果您有任何问题,请随时问我。我会尽力回答您的问题。\n\n' 48 | '*使用方法:*\n' 49 | '1\\. 在私聊中直接与我对话\n' 50 | '2\\. 在群组中使用 `ai\\+空格\\+内容` 与我对话\n' 51 | '3\\. 使用 \\/rechat 重置对话\n\n' 52 | '*注意:*\n' 53 | '1\\. 请勿频繁使用 \\/rechat 重置对话,否则会被限制使用\n' 54 | '2\\. 请勿频繁使用机器人,否则会被限制使用\n\n' 55 | '*开源项目:[ChatGPTelegramBot](https://github.com/Ukenn2112/ChatGPTelegramBot)*\n' 56 | ) 57 | return await bot.reply_to(message, text, parse_mode='MarkdownV2') 58 | 59 | # 重置对话 60 | @bot.message_handler(commands=['rechat']) 61 | async def rechat_message(message): 62 | chat_data = redis_pool.get(f"chatgpt:{message.from_user.id}") 63 | if not chat_data: return await bot.reply_to(message, '没有属于你的对话') 64 | redis_pool.delete(f"chatgpt:{message.from_user.id}") 65 | return await bot.reply_to(message, '已重置对话') 66 | 67 | # 添加白名单 68 | @bot.message_handler(commands=['addwhite'], 69 | func=lambda m: m.from_user.id==config.get('admin_id')) 70 | async def addwhite_message(message): 71 | if not message.reply_to_message: 72 | data = message.text.split(' ') 73 | if len(data) == 1: 74 | return await bot.reply_to(message, '请回复需要加入白名单的用户消息,或者直接输入对话 ID') 75 | add_white_id = int(data[1]) 76 | else: 77 | add_white_id = message.reply_to_message.from_user.id 78 | if add_white_id in config.get('white_list'): 79 | return await bot.reply_to(message, '该对话 ID 已经在白名单中了') 80 | config['white_list'].append(add_white_id) 81 | with open('config.json', "w") as f: json.dump(config, f, indent=4) 82 | return await bot.reply_to(message, '已添加到白名单') 83 | 84 | # 私聊 85 | @bot.message_handler(content_types=['text'], chat_types=['private'], 86 | white_list=True) 87 | async def echo_message_private(message): 88 | start_time = time.time() 89 | chat_data = redis_pool.get(f"chatgpt:{message.from_user.id}") 90 | if chat_data: 91 | messages: list = json.loads(chat_data) 92 | messages.append({"role": "user", "content": message.text}) 93 | else: 94 | messages = [{"role": "user", "content": message.text}] 95 | from_user = f'{message.from_user.username or message.from_user.first_name or message.from_user.last_name}[{message.from_user.id}]' 96 | logging.info(f'{from_user}-->ChatGPT: {message.text}') 97 | if await balance_check(): 98 | return await bot.reply_to(message, 'OpenAI API 余额预设阈值 停止使用') 99 | completion_resp = await openai.ChatCompletion.acreate( 100 | model="gpt-3.5-turbo", 101 | messages=messages, 102 | frequency_penalty=1, 103 | presence_penalty=1, 104 | ) 105 | back_message = completion_resp.choices[0].message 106 | end_time = time.time() 107 | elapsed_time = end_time - start_time 108 | logging.info(f"ChatGPT-->{from_user}: {back_message.content.encode('utf-8').decode()}" + '\n运行时间 {:.3f} 秒'.format(elapsed_time) + f'共消耗{completion_resp.usage.total_tokens}个令牌') 109 | messages.append(back_message) 110 | redis_pool.set(f"chatgpt:{message.from_user.id}", json.dumps(messages), ex=3600) 111 | try: 112 | return await bot.reply_to(message, back_message.content, parse_mode='Markdown') 113 | except: 114 | return await bot.reply_to(message, back_message.content) 115 | 116 | # 群组 117 | @bot.message_handler(content_types=['text'], chat_types=['supergroup'], 118 | func=lambda m: m.text.startswith('ai '), 119 | white_list=True) 120 | async def echo_message_supergroup(message): 121 | start_time = time.time() 122 | chat_data = redis_pool.get(f"chatgpt:{message.from_user.id}") 123 | if chat_data: 124 | messages: list = json.loads(chat_data) 125 | messages.append({"role": "user", "content": message.text[3:]}) 126 | else: 127 | messages = [{"role": "user", "content": message.text[3:]}] 128 | 129 | from_user = f'{message.from_user.username or message.from_user.first_name or message.from_user.last_name}[{message.from_user.id}]' 130 | logging.info(f'[Group] {from_user}-->ChatGPT: {message.text[3:]}') 131 | if await balance_check(): 132 | return await bot.reply_to(message, 'OpenAI API 余额预设阈值 停止使用') 133 | completion_resp = await openai.ChatCompletion.acreate( 134 | model="gpt-3.5-turbo", 135 | messages=messages, 136 | ) 137 | back_message = completion_resp.choices[0].message 138 | end_time = time.time() 139 | elapsed_time = end_time - start_time 140 | logging.info(f"[Group] ChatGPT-->{from_user}: {back_message.content.encode('utf-8').decode()}" + '\n运行时间 {:.3f} 秒'.format(elapsed_time) + f'共消耗{completion_resp.usage.total_tokens}个令牌') 141 | messages.append(back_message) 142 | redis_pool.set(f"chatgpt:{message.from_user.id}", json.dumps(messages), ex=3600) 143 | try: 144 | return await bot.reply_to(message, back_message.content, parse_mode='Markdown') 145 | except: 146 | return await bot.reply_to(message, back_message.content) 147 | 148 | class WhiteList(telebot.asyncio_filters.SimpleCustomFilter): 149 | """白名单过滤器""" 150 | key='white_list' 151 | @staticmethod 152 | async def check(message: telebot.types.Message): 153 | if not config.get('white_list'): return True 154 | elif message.chat.id in config.get('white_list'): 155 | return True 156 | else: 157 | await bot.reply_to(message, ('*由于 ChatGPT API 性能的限制,该机器人现已开启白名单对话限制\n' 158 | '由于该对话或群组不在白名单中,无法使用此机器人~\n' 159 | '如果您想继续使用请尝试自建机器人~\n' 160 | 'Github\\: [ChatGPTelegramBot](https://github.com/Ukenn2112/ChatGPTelegramBot)*'), parse_mode='MarkdownV2') 161 | return False 162 | bot.add_custom_filter(WhiteList()) 163 | 164 | if __name__ == '__main__': 165 | asyncio.run(bot.polling(non_stop=True, request_timeout=90)) -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "", 3 | "bot_token": "", 4 | "admin_id": 0, 5 | "balance_limit": 10, 6 | "white_list": [], 7 | "redis_host": "localhost", 8 | "redis_port": 6379, 9 | "redis_db": 0 10 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyTelegramBotAPI>=4.8.0 2 | redis>=4.3.4 3 | aiohttp>=3.8.1 4 | httpx >= 0.23.1 5 | --------------------------------------------------------------------------------