TeLLMgramBot 2.1.2__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/LICENSE +1 -1
  2. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/PKG-INFO +7 -11
  3. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/README.md +4 -8
  4. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/TeLLMgramBot.py +168 -101
  5. tellmgrambot-2.3.0/TeLLMgramBot/conversation.py +177 -0
  6. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/initialize.py +52 -54
  7. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/message_handlers.py +17 -13
  8. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/openai_singleton.py +1 -1
  9. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/tokenGPT.py +18 -10
  10. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/utils.py +21 -23
  11. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/web_utils.py +14 -18
  12. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot.egg-info/PKG-INFO +7 -11
  13. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot.egg-info/requires.txt +1 -1
  14. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/setup.py +3 -3
  15. tellmgrambot-2.1.2/TeLLMgramBot/conversation.py +0 -171
  16. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot/__init__.py +0 -0
  17. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
  18. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
  19. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/TeLLMgramBot.egg-info/top_level.txt +0 -0
  20. {tellmgrambot-2.1.2 → tellmgrambot-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Digital Heresy
3
+ Copyright (c) 2023-2026 Digital Heresy
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 2.1.2
3
+ Version: 2.3.0
4
4
  Summary: OpenAI GPT, driven by Telegram
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
7
7
  Author-email: ronin.atx@gmail.com
8
8
  License: MIT
9
- Requires-Python: >=3.10
9
+ Requires-Python: >=3.12
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: openai>2.0
@@ -16,7 +16,7 @@ Requires-Dist: beautifulsoup4
16
16
  Requires-Dist: typing
17
17
  Requires-Dist: validators
18
18
  Requires-Dist: tiktoken>=0.12
19
- Requires-Dist: python-telegram-bot
19
+ Requires-Dist: python-telegram-bot>20.0
20
20
  Dynamic: author
21
21
  Dynamic: author-email
22
22
  Dynamic: description
@@ -54,8 +54,6 @@ Using Telegram as the interface not only solves "exposing" the interface, but gi
54
54
  ## Directories
55
55
  When initializing TeLLMgramBot, the following directories get created:
56
56
  * `configs` - Contains bot configuration files.
57
- * `commands.txt`
58
- * Users can type `/help` interacting with the to see this file's text get displayed in Telegram.
59
57
  * `config.yaml` (can be a different name)
60
58
  * This file sets main OpenAI parameters like naming and GPT models to process.
61
59
  * The parameter `url_model` is to read URL content, different than `chat_model` that the bot normally uses to interact with the user.
@@ -64,7 +62,7 @@ When initializing TeLLMgramBot, the following directories get created:
64
62
  * This important YAML file contains token size parameters for supported OpenAI models.
65
63
  * If the first time, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4o`, and `gpt-4o-mini` get populated, but the user can specify more models with token size parameters as needed.
66
64
  * `prompts` - Contains prompt files for how the bot interacts with any user.
67
- * `test_personality.prmpt`
65
+ * `test_personality.prmpt` (can be a different name)
68
66
  * This is a sample prompt file as a basis to test this library.
69
67
  * The user can create more prompt files as needed for different personalities. See [OpenAI Playground](https://platform.openai.com/playground) to test some ideas.
70
68
  * `url_analysis.prmpt`
@@ -122,12 +120,11 @@ Each file with the associated API key will update its respective environment var
122
120
  This library includes an example script `test_local.py`, which uses files from the folders `configs` and `prompts` for TeLLMgramBot to process. The bot communicates with OpenAI via the **Responses API**, which replaces the older Chat Completions endpoint.
123
121
  1. Ensure the previous sections are followed with the proper API keys and your Telegram bot set.
124
122
  2. Install this library via PIP (`pip install TeLLMgramBot`) and then import into your project.
125
- 3. Instantiate the bot by passing in various configuration pieces needed below:
123
+ 3. Instantiate the bot by passing in various configuration pieces needed below.
124
+ Note the Telegram bot's full name and username auto-populate before startup.
126
125
  ```
127
126
  telegram_bot = TeLLMgramBot.TelegramBot(
128
- bot_username = <Bot username like 'friendly_bot'>,
129
127
  bot_owner = <Bot owner's Telegram username>,
130
- bot_name = <Bot name like 'Friendly Bot'>,
131
128
  bot_nickname = <Bot nickname like 'Botty'>,
132
129
  bot_initials = <Bot initials like 'FB'>,
133
130
  chat_model = <Conversation model like 'gpt-4o-mini'>,
@@ -142,8 +139,7 @@ This library includes an example script `test_local.py`, which uses files from t
142
139
  telegram_bot.start_polling()
143
140
  ```
144
141
  Once you see `TeLLMgramBot polling...`, the bot is online in Telegram.
145
- 5. Typing `/help` shows all available commands reported by the `configs/commands.txt` file.
146
- 6. Only as owner, type `/start` directly to the bot to initiate user conversations.
142
+ 5. Converse! Type `/help` for all available commands.
147
143
 
148
144
  ## Resources
149
145
  * GitHub repository [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) has guides to create a Telegram bot.
@@ -24,8 +24,6 @@ Using Telegram as the interface not only solves "exposing" the interface, but gi
24
24
  ## Directories
25
25
  When initializing TeLLMgramBot, the following directories get created:
26
26
  * `configs` - Contains bot configuration files.
27
- * `commands.txt`
28
- * Users can type `/help` interacting with the to see this file's text get displayed in Telegram.
29
27
  * `config.yaml` (can be a different name)
30
28
  * This file sets main OpenAI parameters like naming and GPT models to process.
31
29
  * The parameter `url_model` is to read URL content, different than `chat_model` that the bot normally uses to interact with the user.
@@ -34,7 +32,7 @@ When initializing TeLLMgramBot, the following directories get created:
34
32
  * This important YAML file contains token size parameters for supported OpenAI models.
35
33
  * If the first time, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4o`, and `gpt-4o-mini` get populated, but the user can specify more models with token size parameters as needed.
36
34
  * `prompts` - Contains prompt files for how the bot interacts with any user.
37
- * `test_personality.prmpt`
35
+ * `test_personality.prmpt` (can be a different name)
38
36
  * This is a sample prompt file as a basis to test this library.
39
37
  * The user can create more prompt files as needed for different personalities. See [OpenAI Playground](https://platform.openai.com/playground) to test some ideas.
40
38
  * `url_analysis.prmpt`
@@ -92,12 +90,11 @@ Each file with the associated API key will update its respective environment var
92
90
  This library includes an example script `test_local.py`, which uses files from the folders `configs` and `prompts` for TeLLMgramBot to process. The bot communicates with OpenAI via the **Responses API**, which replaces the older Chat Completions endpoint.
93
91
  1. Ensure the previous sections are followed with the proper API keys and your Telegram bot set.
94
92
  2. Install this library via PIP (`pip install TeLLMgramBot`) and then import into your project.
95
- 3. Instantiate the bot by passing in various configuration pieces needed below:
93
+ 3. Instantiate the bot by passing in various configuration pieces needed below.
94
+ Note the Telegram bot's full name and username auto-populate before startup.
96
95
  ```
97
96
  telegram_bot = TeLLMgramBot.TelegramBot(
98
- bot_username = <Bot username like 'friendly_bot'>,
99
97
  bot_owner = <Bot owner's Telegram username>,
100
- bot_name = <Bot name like 'Friendly Bot'>,
101
98
  bot_nickname = <Bot nickname like 'Botty'>,
102
99
  bot_initials = <Bot initials like 'FB'>,
103
100
  chat_model = <Conversation model like 'gpt-4o-mini'>,
@@ -112,8 +109,7 @@ This library includes an example script `test_local.py`, which uses files from t
112
109
  telegram_bot.start_polling()
113
110
  ```
114
111
  Once you see `TeLLMgramBot polling...`, the bot is online in Telegram.
115
- 5. Typing `/help` shows all available commands reported by the `configs/commands.txt` file.
116
- 6. Only as owner, type `/start` directly to the bot to initiate user conversations.
112
+ 5. Converse! Type `/help` for all available commands.
117
113
 
118
114
  ## Resources
119
115
  * GitHub repository [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) has guides to create a Telegram bot.
@@ -2,63 +2,93 @@
2
2
  import os
3
3
  import re
4
4
  import json
5
+ import asyncio
5
6
  from tempfile import gettempdir
6
7
  from math import floor
7
- from telegram import Update
8
+ from telegram import Bot, Update, Message, Chat, User
9
+ from telegram.constants import MessageLimit
8
10
  from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
9
11
  import openai
10
12
 
11
13
  from .openai_singleton import OpenAIClientSingleton
12
- from .initialize import INIT_BOT_CONFIG, init_structure, init_bot_config, init_bot_prompt, init_bot_commands
14
+ from .initialize import INIT_BOT_CONFIG, init_structure, init_bot_config, init_bot_prompt
13
15
  from .conversation import Conversation
14
16
  from .tokenGPT import TokenGPT
15
17
  from .message_handlers import handle_greetings, handle_common_queries, handle_url_ask
16
18
  from .utils import read_yaml, read_text, exact_word_match, generate_error_path, log_error
17
19
 
18
-
19
- # noinspection PyMethodMayBeStatic,PyUnusedLocal
20
20
  class TelegramBot:
21
- # Show all the commands available by the bot using the TelegramCommands.txt file
21
+ """
22
+ The bridge between the Telegram interface and Large Langage Model (LLM) using OpenAI's GPT models.
23
+ - Tokens help retain conversation messages between the Telegram bot assistant and the user.
24
+ - URLs in [square brackets] can be supplied on how the bot should interpret it.
25
+ - For more information, see README or: https://github.com/Digital-Heresy/TeLLMgramBot
26
+ """
27
+ async def _tele_info(self):
28
+ """Fetch the Telegram bot's information before running application."""
29
+ try:
30
+ bot = await Bot(token=os.environ['TELLMGRAMBOT_TELEGRAM_API_KEY']).get_me()
31
+ self.telegram['username'] = bot.username
32
+ self.telegram['name'] = bot.full_name
33
+ except Exception as e:
34
+ # Fail fast if the bot's identity cannot be fetched to avoid
35
+ # continuing with an uninitialized self.telegram['username'].
36
+ log_error(e, error_type='Telegram', error_filename=self.error_log)
37
+ raise
38
+
22
39
  async def tele_commands(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
23
- reply = ''
24
- with open(init_bot_commands('commands.txt'), "r", encoding="utf-8") as file:
25
- for line in file:
26
- reply += line
27
- await update.message.reply_text(reply)
40
+ """Show all the commands available when the user sends `/help` to the bot."""
41
+ await update.message.reply_text("TeLLMgramBot commands:\n"
42
+ "/nick - Set your nickname (e.g. \"/nick B0b #2\").\n"
43
+ "/forget - Clear all of your conversations.\n"
44
+ "/help - Display this usage information.\n\n"
45
+ "You must have a username to converse (for now).\n"
46
+ "Chat history will still show in your Telegram client.\n\n"
47
+ "Administrator-only commands:\n"
48
+ "/start - Go online to receive new responses (default).\n"
49
+ "/stop - Go offline to prevent new responses.\n"
50
+ )
28
51
 
29
- # Start command only runs by the 'bot_owner' specified in configuration file
30
52
  async def tele_start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
53
+ """A bot_owner-only command to go online and handle any new messages."""
31
54
  uname = update.message.from_user.username
32
55
  if uname == self.telegram['owner']:
56
+ self._online = True
33
57
  greeting_text = f"Oh, hello {update.message.from_user.first_name}! Let me get to work!"
34
58
  await update.message.reply_text(greeting_text)
35
- self.started = True
36
- self.GPTOnline = True
37
59
  else:
38
- await update.message.reply_text("Sorry, but I'm off the clock at the moment.")
60
+ await update.message.reply_text("Sorry, I can't do that for you.")
39
61
 
40
- # Stop command formally closes out the polling loop
41
62
  async def tele_stop_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
63
+ """A bot_owner-only command to go offline and not handle any new messages."""
42
64
  uname = update.message.from_user.username
43
65
  if uname == self.telegram['owner']:
44
- self.GPTOnline = False
66
+ self._online = False
45
67
  await update.message.reply_text("Sure thing boss, cutting out!")
46
68
  else:
47
69
  await update.message.reply_text("Sorry, I can't do that for you.")
48
70
 
49
- # Let bot know to call the user by a different name other than the Telegram user name
50
- # noinspection RegExpRedundantEscape,PyArgumentList
51
71
  async def tele_nick_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
52
- # Must follow the format of "/nick <nickname>" using regular expression
53
- result = re.search(r'^\/nick[ \n]+([ \S]+)', update.message.text)
54
- if result is not None:
72
+ """
73
+ Let bot assistant know to call the user by a different name other than the Telegram username.
74
+ Must follow the format of "/nick(@<bot name>)? <nickname>", checked by a regular expression.
75
+ """
76
+ validated = await self.tele_validate(update)
77
+ if not validated:
78
+ return
79
+ (msg, chat, user) = validated
80
+
81
+ # If it's a group conversation, need to handle commands directly to bot with @<bot name> also
82
+ pattern = re.compile(fr"^\s*/nick(?:@{re.escape(self.telegram['username'])})?\s+(.+?)\s*$", re.IGNORECASE)
83
+ result = pattern.search(msg.text or "")
84
+ if result:
55
85
  prompt = f"Please refer to me by my nickname, {result.group(1).strip()}, rather than my user name."
56
- await update.message.reply_text(await self.tele_handle_response(text=prompt, update=update))
86
+ await msg.reply_text(await self.tele_handle_response(prompt, msg))
57
87
  else:
58
- await update.message.reply_text("Please provide a valid nickname after the command like \"/nick B0b #2\".")
88
+ await msg.reply_text("Please provide a valid nickname after the command like \"/nick B0b #2\".")
59
89
 
60
- # Remove the whole conversation including past session logs for peace of mind
61
90
  async def tele_forget_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
91
+ """Remove the whole conversation including past session logs for peace of mind."""
62
92
  uname = update.message.from_user.username
63
93
  if uname in self.conversations:
64
94
  self.conversations[uname].clear_interaction()
@@ -70,17 +100,24 @@ class TelegramBot:
70
100
  " already asked me to forget about you. Either way, nice to meet you!"
71
101
  )
72
102
 
73
- # Responses
74
- async def tele_handle_response(self, text: str, update: Update) -> str:
75
- # Before we handle messages, ensure a user has /started us
103
+ async def tele_handle_response(self, text: str, msg: Message) -> str:
104
+ """
105
+ Primary function for handling any response including Generative AI, ensuring:
106
+ - The owner started up the bot assistant for user interactions.
107
+ - User has conversed with the bot assistant before.
108
+ - Depending on regular interaction or URL request, formulate message based
109
+ on the Generative AI model (e.g. OpenAI GPT) and token count checks.
110
+ """
76
111
  # Starting ensures we get some kind of user account details for logging
77
- not_started_reply = "I'd love to chat, but please wait as I haven't started up yet!"
78
- if not self.started:
79
- return not_started_reply
112
+ if not self._online:
113
+ return "I'd love to chat, but I am offline at the moment!"
114
+
115
+ # Print information of the user sending message for conversation
116
+ uname = msg.from_user.username
117
+ print(f"User {uname} in {msg.chat.type} chat ID {str(msg.chat.id)}")
80
118
 
81
119
  # For a new session, track if the user has conversed with the Telegram bot before
82
120
  # Username is consistent across restarts and different conversation instances
83
- uname = update.message.from_user.username
84
121
  if uname not in self.conversations:
85
122
  self.conversations[uname] = Conversation(
86
123
  uname,
@@ -101,16 +138,16 @@ class TelegramBot:
101
138
 
102
139
  # Form the assistant's message based on low level easy stuff or send to GPT
103
140
  # OpenAI's Responses API relies on the maximum number of tokens the selected model can support
104
- reply = not_started_reply
141
+ reply = "Sorry, I couldn't process your message! Please contact my creator."
105
142
  if handle_greetings(text):
106
143
  reply = handle_greetings(text)
107
144
  elif handle_common_queries(text):
108
145
  reply = handle_common_queries(text)
109
146
  elif url_match:
110
147
  # URL content is passed into another model to summarize (GPT-4 preferred)
111
- await update.message.reply_text("Sure, give me a moment to look at that URL...")
148
+ await msg.reply_text("Sure, give me a moment to look at that URL...")
112
149
  reply = await handle_url_ask(text, self.chatgpt['url_model'])
113
- elif self.GPTOnline:
150
+ elif self._online:
114
151
  # This is essentially the transition point between quick Telegram replies and GPT
115
152
  response = await self.gpt_completion(uname)
116
153
  reply = response.choices[0].message.content
@@ -121,10 +158,10 @@ class TelegramBot:
121
158
  # If the user is getting closer to the bot's token threshold, warn the first time
122
159
  if token_count > self.chatgpt['prune_back_to'] and uname not in self.token_warning:
123
160
  reply += ("\n\n"
124
- "By the way, our conversation will soon reach my token limit, so I may"
125
- " start forgetting some of our older exchanges. Would you like me to"
126
- " summarize our conversation so far to keep the main points alive?"
127
- )
161
+ "By the way, our conversation will soon reach my token limit, so I may"
162
+ " start forgetting some of our older exchanges. Would you like me to"
163
+ " summarize our conversation so far to keep the main points alive?"
164
+ )
128
165
  self.token_warning[uname] = True
129
166
 
130
167
  # Add assistant's message to the user's conversation
@@ -136,53 +173,81 @@ class TelegramBot:
136
173
 
137
174
  return reply
138
175
 
139
- # Handles the Telegram side of the message, discerning between Private and Group conversation
140
- async def tele_handle_message(self, update: Update, context=ContextTypes.DEFAULT_TYPE):
141
- message_type: str = update.message.chat.type # PM or Group Chat
142
- message_text: str = update.message.text
143
- message_print = f"User {update.message.from_user.username} in {message_type} chat ID {update.message.chat.id}"
176
+ async def tele_handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
177
+ """Handles the Telegram side of the message, discerning between Private and Group conversation."""
178
+ validated = await self.tele_validate(update)
179
+ if not validated:
180
+ return
181
+ (msg, chat, user) = validated
144
182
 
145
183
  # If it's a group text, only reply if the bot is named
146
184
  # The real magic of how the bot behaves is in tele_handle_response()
147
- if message_type == 'supergroup' or message_type == 'group':
148
- if exact_word_match(self.telegram['username'], message_text):
149
- print(message_print)
150
- new_text: str = message_text.replace(self.telegram['username'], '').strip()
151
- response: str = await self.tele_handle_response(text=new_text, update=update)
185
+ response = "Sorry, I couldn't process your message! Please contact my creator."
186
+ if chat.type == 'supergroup' or chat.type == 'group':
187
+ if exact_word_match(self.telegram['username'], msg.text):
188
+ pattern = r'@?\b' + re.escape(self.telegram['username']) + r'\b'
189
+ new_text = re.sub(pattern, '', msg.text).strip()
190
+ response = await self.tele_handle_response(new_text, msg)
152
191
  elif (
153
- exact_word_match(self.telegram['nickname'], message_text) or
154
- exact_word_match(self.telegram['initials'], message_text)
192
+ exact_word_match(self.telegram['nickname'], msg.text) or
193
+ exact_word_match(self.telegram['initials'], msg.text)
155
194
  ):
156
- print(message_print)
157
- response: str = await self.tele_handle_response(text=message_text, update=update)
195
+ response = await self.tele_handle_response(msg.text, msg)
158
196
  else:
159
197
  return
160
- elif message_type == 'private':
161
- print(message_print)
162
- response: str = await self.tele_handle_response(text=message_text, update=update)
198
+ elif chat.type == 'private':
199
+ response = await self.tele_handle_response(msg.text, msg)
163
200
  else:
164
201
  return
165
- await update.message.reply_text(response)
166
202
 
167
- # Handle errors caused on the Telegram side
168
- async def tele_error(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
169
- if update.message.from_user.username is None:
170
- # If the user doesn't have a Telegram username, we can't really do anything
171
- await update.message.reply_text('Add a username to your Telegram account so that I can talk to you!')
203
+ # Split into smaller chunks since Telegram messages have a maximum text length (likely 4096)
204
+ chunk_length = MessageLimit.MAX_TEXT_LENGTH - 1
205
+ for chunk in [response[i:i+chunk_length] for i in range(0, len(response), chunk_length)]:
206
+ await msg.reply_text(chunk)
207
+ await asyncio.sleep(0.12) # small pause to reduce risk of rate limiting
208
+
209
+ async def tele_validate(self, update: Update) -> tuple[Message, Chat, User] | None:
210
+ """
211
+ Check if any update through Telegram has a complete message, chat, and user.
212
+ This uses python-telegram-bot's Update `effective` methods for all three.
213
+ """
214
+ if update is None:
215
+ return None
216
+
217
+ # Any python-telegram-bot 'effective' method helps ensure there are no "None" types
218
+ # Non-user updates like Inline queries or chat member updates makes either one undefined
219
+ msg = update.effective_message
220
+ chat = update.effective_chat
221
+ user = update.effective_user
222
+
223
+ # If there is no chat or valid message of the unique conversation, no reason to reply
224
+ if chat is None or msg is None:
225
+ return None
226
+ # chat.type => 'private', 'group', 'supergroup', 'channel'
227
+ # chat.id = user.id if 'private'
228
+
229
+ # Handle the user sending the message
230
+ if user:
231
+ # This ensures conversations don't overlap (for now, until user.id is implemented)
232
+ uname = getattr(user, "username", None)
233
+ if uname is None:
234
+ await msg.reply_text('Add a username to your Telegram account so that I can talk to you!')
235
+ return None
172
236
  else:
173
- log_error(context.error, error_type='Telegram', error_filename=self.error_log)
174
- await update.message.reply_text("Sorry, I ran into an error! Please contact my creator.")
175
-
176
- # Read the GPT Conversation so far
177
- @staticmethod
178
- def gpt_read_interactions(file_path: str):
179
- with open(file_path, 'r') as interaction_log:
180
- lines = interaction_log.readlines()
181
- formatted_messages = [json.loads(line) for line in lines]
182
- return formatted_messages
183
-
184
- # Get the OpenAI response based on bot configuration
237
+ # If not the user chatting directly to the bot, don't bother
238
+ return None
239
+
240
+ return (msg, chat, user)
241
+
242
+ async def tele_error(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
243
+ """Handle any conversation error caused on the Telegram side."""
244
+ log_error(context.error, error_type='Telegram', error_filename=self.error_log)
245
+ msg = update.effective_message if update else None
246
+ if msg:
247
+ await msg.reply_text("Sorry, I ran into an error! Please contact my creator.")
248
+
185
249
  async def gpt_completion(self, uname: str):
250
+ """Get the OpenAI response based on bot configuration."""
186
251
  try:
187
252
  client = OpenAIClientSingleton.get_instance()
188
253
  response = await client.chat.completions.create(
@@ -190,7 +255,6 @@ class TelegramBot:
190
255
  messages=self.conversations[uname].messages,
191
256
  )
192
257
  return response
193
-
194
258
  except openai.AuthenticationError as e:
195
259
  # Handle authentication error
196
260
  log_error(e, error_type='OpenAI-Authentication', error_filename=self.error_log)
@@ -211,47 +275,51 @@ class TelegramBot:
211
275
  # Catch any other unexpected exceptions
212
276
  log_error(e, error_type='Other', error_filename=self.error_log)
213
277
 
214
- # The main polling "loop" the user interacts with via Telegram
215
278
  def start_polling(self):
279
+ """The main polling "loop" the user interacts with via Telegram."""
216
280
  print(f"TeLLMgramBot {self.telegram['username']} polling...")
217
281
  self.telegram['app'].run_polling(poll_interval=self.telegram['pollinterval'])
218
282
  print(f"TeLLMgramBot {self.telegram['username']} polling ended.")
219
283
 
220
284
  # Initialization
221
285
  def __init__(self,
222
- bot_username = INIT_BOT_CONFIG['bot_username'],
223
- bot_owner = INIT_BOT_CONFIG['bot_owner'],
224
- bot_name = INIT_BOT_CONFIG['bot_name'],
225
- bot_nickname = INIT_BOT_CONFIG['bot_nickname'],
226
- bot_initials = INIT_BOT_CONFIG['bot_initials'],
227
- chat_model = INIT_BOT_CONFIG['chat_model'],
228
- url_model = INIT_BOT_CONFIG['url_model'],
229
- token_limit = INIT_BOT_CONFIG['token_limit'],
230
- persona_temp = INIT_BOT_CONFIG['persona_temp'],
231
- persona_prompt = INIT_BOT_CONFIG['persona_prompt']
232
- ):
286
+ bot_owner = INIT_BOT_CONFIG['bot_owner'],
287
+ bot_nickname = INIT_BOT_CONFIG['bot_nickname'],
288
+ bot_initials = INIT_BOT_CONFIG['bot_initials'],
289
+ chat_model = INIT_BOT_CONFIG['chat_model'],
290
+ url_model = INIT_BOT_CONFIG['url_model'],
291
+ token_limit = INIT_BOT_CONFIG['token_limit'],
292
+ persona_temp = INIT_BOT_CONFIG['persona_temp'],
293
+ persona_prompt = INIT_BOT_CONFIG['persona_prompt']
294
+ ):
295
+ # Starting to initialize, not online yet
296
+ self._online = False
297
+
233
298
  # First provide the main structure if not already there
234
299
  init_structure()
235
300
 
236
- # Set up our variables
301
+ # Initialize some variables
237
302
  self.error_log = generate_error_path()
238
- self.started = False
239
- self.GPTOnline = False
240
- self.token_warning = {} # Determines whether user has reached token limit by OpenAI model
303
+ self.token_warning = {} # Determines whether user has reached token limit by AI model
241
304
  self.conversations = {} # Provides Conversation class per user based on bot response
242
-
243
- # Get Telegram Spun Up
244
- # noinspection PyDictCreation
245
305
  self.telegram = {
246
306
  'owner' : bot_owner,
247
- 'username' : bot_username,
248
307
  'nickname' : bot_nickname,
249
308
  'initials' : bot_initials,
250
309
  'pollinterval' : 3
251
310
  }
252
- self.telegram['app'] = Application.builder().token(os.environ['TELLMGRAMBOT_TELEGRAM_API_KEY']).build()
311
+ # Check for event running loops before getting the bot's information
312
+ try:
313
+ loop = asyncio.get_running_loop()
314
+ except RuntimeError:
315
+ # No running event loop; safe to run synchronously
316
+ asyncio.run(self._tele_info())
317
+ else:
318
+ # Already in an event loop; schedule as a background task
319
+ loop.create_task(self._tele_info())
253
320
 
254
- # Add our handlers for Commands, Messages, and Errors
321
+ # Build our application with handlers for Commands, Messages, and Errors
322
+ self.telegram['app'] = Application.builder().token(os.environ['TELLMGRAMBOT_TELEGRAM_API_KEY']).build()
255
323
  self.telegram['app'].add_handler(CommandHandler('help', self.tele_commands))
256
324
  self.telegram['app'].add_handler(CommandHandler('start', self.tele_start_command))
257
325
  self.telegram['app'].add_handler(CommandHandler('stop', self.tele_stop_command))
@@ -263,7 +331,6 @@ class TelegramBot:
263
331
  # Get our LLM spun up with defaults if not defined by user input
264
332
  # Tokens as integers measure the length of conversation messages
265
333
  self.chatgpt = {
266
- 'name' : bot_name,
267
334
  'prompt' : persona_prompt,
268
335
  'chat_model' : chat_model,
269
336
  'url_model' : url_model,
@@ -276,9 +343,11 @@ class TelegramBot:
276
343
  self.chatgpt['prune_threshold'] = floor(0.95 * self.chatgpt['token_limit'])
277
344
  self.chatgpt['prune_back_to'] = max(0, self.chatgpt['prune_threshold'] - 500)
278
345
 
279
- # Sets TeLLMgramBot object based on its YAML configuration and prompt files
280
- # noinspection PyMethodParameters
346
+ # Bot is now ready and active by default
347
+ self._online = True
348
+
281
349
  def set(config_file='config.yaml', prompt_file='test_personality.prmpt'):
350
+ """Set TeLLMgramBot object based on its YAML configuration and prompt files."""
282
351
  # First provide the main structure if not already there
283
352
  init_structure()
284
353
 
@@ -301,9 +370,7 @@ class TelegramBot:
301
370
 
302
371
  # Apply parameters to bot:
303
372
  return TelegramBot(
304
- bot_username = config['bot_username'],
305
373
  bot_owner = config['bot_owner'],
306
- bot_name = config['bot_name'],
307
374
  bot_nickname = config['bot_nickname'],
308
375
  bot_initials = config['bot_initials'],
309
376
  chat_model = config['chat_model'],