Skip to content

Commit 3472882

Browse files
committed
v0.761 – user-set notifications & alerts
2 parents 3483c4e + 58b76c8 commit 3472882

17 files changed

+1265
-235
lines changed

.catgitinclude

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ src/modules.py
88
src/text_message_handler.py
99
src/utils.py
1010
config/config.ini
11+
src/reminder_poller.py
12+
src/reminder_handler.py

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
- **🗺 Geolocation and map lookups via MapTiler API**
1010
- (with weather forecasts around the world in all OpenAI API supported languages)
1111
- **🧭 Navigation instructions via Openrouteservice API**
12+
- **🔔 Timed reminders & notifications system**
13+
- Users can schedule alerts that trigger at specific times. Configurable per-user limits and optional listing of past reminders
1214
- **📊 Daily token usage tracking & rate limiting for API usage / cost management**
1315
- **🔍 Perplexity API models alongside OpenAI models**
1416
- Useful for fact-checking and supplementing OpenAI's cutoff dates
@@ -238,7 +240,11 @@ If you run into any issues, consult the logs or reach out on the repository's [I
238240
---
239241

240242
# Changelog
241-
- v0.76 – **Premium mode auto-switching** + usage DB synergy
243+
- v0.761 - **Timed notifications / user reminders**
244+
- Brand-new feature: users can set timed reminders (alerts) by requesting reminders that the bot stores in an SQLite database. A separate poller picks them up as soon as they are due, and the bot automatically notifies the user on set times.
245+
- The custom function calling view action can also list your recently passed or deleted reminders (configurable in `[Reminders]` -> `ShowPastRemindersCount`).
246+
- The bot ensures a max limit of pending reminders per user (set with `MaxAlertsPerUser`; set to 0 for unlimited alerts).
247+
- v0.76 - **Premium mode auto-switching** + usage DB synergy
242248
- Added daily usage-based auto-switch logic between “premium” vs. “mini” models (see `[ModelAutoSwitch]` in `config.ini`).
243249
- Once you exceed the `PremiumTokenLimit`, the bot seamlessly switches to the fallback model.
244250
- If that fallback also goes past `MiniTokenLimit`, the bot can either deny usage or proceed, according to `FallbackLimitAction`.

config/config.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,22 @@ ELASTICSEARCH_PASSWORD =
181181
[HolidaySettings]
182182
EnableHolidayNotification = true
183183

184+
# ~~~~~~~~~~~~~~~~~~~~~~~~~
185+
# User-assignable reminders
186+
# ~~~~~~~~~~~~~~~~~~~~~~~~~
187+
[Reminders]
188+
# Enable or disable the reminder/alert functionality
189+
EnableReminders = True
190+
191+
# Maximum number of pending reminders per user; set to 0 for unlimited
192+
MaxAlertsPerUser = 100
193+
194+
# How often (in seconds) the bot checks for due reminders
195+
PollingIntervalSeconds = 5
196+
197+
# How many old/past reminders to list
198+
ShowPastRemindersCount = 5
199+
184200
# ~~~~~~~~~~~~~~~
185201
# Perplexity API
186202
# ~~~~~~~~~~~~~~~

src/api_get_stock_prices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from datetime import datetime
1616

1717
# Configure logging
18-
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
18+
# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
1919

2020
# Utility function to get API key
2121
def get_api_key():

src/api_get_stock_prices_alphavantage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from datetime import datetime
1616

1717
# Configure logging
18-
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
18+
# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
1919

2020
# Utility function to get API key
2121
def get_api_key():

src/api_get_stock_prices_yfinance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from datetime import datetime
1414

1515
# Configure logging
16-
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
16+
# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
1717

1818
# Search for stock symbol (Using yfinance for direct data fetching)
1919
async def search_stock_symbol(keyword):

src/api_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from config_paths import CONFIG_PATH, API_TOKEN_PATH # Import the centralized CONFIG_PATH
99

1010
# Set up basic logging
11-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11+
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
1212

1313
# Flag to enable or disable fallback to environment variable if the key is not found in the file
1414
ENABLE_KEY_READING_FALLBACK = True

src/bot_token.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from config_paths import CONFIG_PATH, TOKEN_FILE_PATH
99

1010
# Set up basic logging configuration
11-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler(sys.stdout)])
11+
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler(sys.stdout)])
1212

1313
class BotTokenError(Exception):
1414
"""Custom exception for bot token retrieval failures."""

src/config_paths.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
NWS_RETRIES = 0
3535
NWS_RETRY_DELAY = 2
3636

37+
# read the reminders db
38+
data_directory_name = 'data' # Default name for data directory
39+
REMINDERS_DB_FILENAME = 'reminders.db' # Default name for the reminders DB file
40+
3741
# Attempt to read the configuration file
3842
if CONFIG_PATH.exists():
3943
try:
@@ -48,7 +52,10 @@
4852

4953
# Ensure the logs directory exists
5054
LOGS_DIR.mkdir(parents=True, exist_ok=True)
51-
55+
56+
# Read data directory name from config
57+
data_directory_name = config['DEFAULT'].get('DataDirectory', 'data')
58+
5259
# Update log file paths
5360
LOG_FILE_PATH = LOGS_DIR / config['DEFAULT'].get('LogFile', 'bot.log')
5461
CHAT_LOG_FILE_PATH = LOGS_DIR / config['DEFAULT'].get('ChatLogFile', 'chat.log')
@@ -106,6 +113,18 @@
106113
# CHAT_LOG_MAX_SIZE already set to 10 MB
107114
# Elasticsearch settings already set to defaults
108115

116+
# Define the Data Directory path
117+
DATA_DIR = BASE_DIR / data_directory_name
118+
# Ensure the data directory exists
119+
try:
120+
DATA_DIR.mkdir(parents=True, exist_ok=True)
121+
except OSError as e:
122+
logger.error(f"Could not create data directory {DATA_DIR}: {e}")
123+
124+
# Path for the reminders database
125+
REMINDERS_DB_PATH = DATA_DIR / REMINDERS_DB_FILENAME
126+
logger.info(f"Reminders database path set to: {REMINDERS_DB_PATH}")
127+
109128
# Define paths for token files
110129
TOKEN_FILE_PATH = BASE_DIR / 'config' / 'bot_token.txt'
111130
API_TOKEN_PATH = BASE_DIR / 'config' / 'api_token.txt'

src/custom_functions.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,25 @@
55
# you can add your own custom bot functionalities via OpenAI's API function calls with this.
66

77
import logging
8+
import configparser
9+
from config_paths import CONFIG_PATH
810

911
# from api_get_openrouteservice import get_route, get_directions_from_addresses
1012
# from elasticsearch_handler import search_es # Import the Elasticsearch search function
1113

14+
# load and use logger
15+
logger = logging.getLogger(__name__)
16+
logger.setLevel(logging.INFO)
17+
18+
# Read the config for enabled/disabled function calls
19+
config = configparser.ConfigParser()
20+
config.read(CONFIG_PATH)
21+
try: # Use try-except for safety
22+
enable_reminders = config.getboolean('Reminders', 'EnableReminders', fallback=False)
23+
except (configparser.NoSectionError, configparser.NoOptionError):
24+
enable_reminders = False
25+
26+
# silently observe the chat
1227
async def observe_chat():
1328
# Log observation or perform any silent monitoring if needed
1429
logging.info("Bot is currently observing the chat.")
@@ -149,6 +164,150 @@ async def observe_chat():
149164
}
150165
})
151166

167+
# ~~~~~~~~~~~~~~~~~~~~~~
168+
# reminders (if enabled)
169+
# ~~~~~~~~~~~~~~~~~~~~~~
170+
171+
if enable_reminders:
172+
manage_reminder_function = {
173+
'name': 'manage_reminder',
174+
'description': """Manages user reminders (alerts); use if the user requests alerts, a timed notification, etc (hälytys, muistutus, ajastettu viesti..) Specify the action: 'add' to create, 'view' to list pending, 'delete' to remove by ID, or 'edit' to modify by ID.
175+
If you're unsure of the user's time zone, ask it first, and set the UTC alert accordingly. For instance, Finland is UTC+2 in winter and UTC+3 during summertime. Add the reminder in the user's own language! Suomeksi, jos käyttäjä puhuu suomea! Always ask the user's timezone if unsure.
176+
- For 'add': requires 'reminder_text' and exact 'due_time_utc' (ISO 8601 format, e.g., '2025-04-04T10:00:00Z'). Calculate UTC from user input based on current system UTC time. Notifications can be up to around 4000 characters or more.
177+
- For 'view': no other parameters needed.
178+
- For 'delete': requires 'reminder_id'.
179+
- For 'edit': requires 'reminder_id' and at least one of 'reminder_text' or 'due_time_utc'.""",
180+
'parameters': {
181+
'type': 'object',
182+
'properties': {
183+
'action': {
184+
'type': 'string',
185+
'enum': ['add', 'view', 'delete', 'edit'],
186+
'description': "The operation: 'add', 'view', 'delete', or 'edit'."
187+
},
188+
'reminder_text': {
189+
'type': 'string',
190+
'description': "Text of the reminder, in the user's requested language. You can be a bit creative, like: 'Hey! You asked me to remind you... etc, emojis and basic Telegram HTML formatting is allowed.'. Required for 'add', optional for 'edit'."
191+
},
192+
'due_time_utc': {
193+
'type': 'string',
194+
'description': "Due time in UTC ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). Required for 'add', optional for 'edit'."
195+
},
196+
'reminder_id': {
197+
'type': 'integer',
198+
'description': "ID of the reminder. Required for 'delete' and 'edit'."
199+
}
200+
},
201+
'required': ['action']
202+
}
203+
}
204+
custom_functions.append(manage_reminder_function) # Directly append if enabled
205+
logger.info("Reminder function 'manage_reminder' appended to custom_functions list.")
206+
else:
207+
logger.info("Reminders disabled in config.ini => 'manage_reminder' function not added.")
208+
209+
# # original reminder method (tryout)
210+
# if enable_reminders:
211+
# # 1) Add a reminder
212+
# custom_functions.append({
213+
# 'name': 'add_reminder',
214+
# 'description': (
215+
# "[Use if the user wants to set a reminder for a future time.] "
216+
# "Accept user text and a date/time in UTC (YYYY-MM-DDTHH:MM:SSZ). "
217+
# "If user says 'in 5 minutes', parse that to a UTC time. "
218+
# "Return success/failure, and the ID of the reminder if successful."
219+
# ),
220+
# 'parameters': {
221+
# 'type': 'object',
222+
# 'properties': {
223+
# 'due_time_utc': {
224+
# 'type': 'string',
225+
# 'description': (
226+
# "The date/time in UTC, e.g. 2025-01-02T13:00:00Z. "
227+
# "If user says something like 'in 5 minutes', parse into UTC. "
228+
# "If date/time is missing, ask user for clarification."
229+
# )
230+
# },
231+
# 'reminder_text': {
232+
# 'type': 'string',
233+
# 'description': (
234+
# "What does the user want to be reminded of? E.g. 'Take out the trash'."
235+
# )
236+
# }
237+
# },
238+
# 'required': ['due_time_utc', 'reminder_text']
239+
# }
240+
# })
241+
242+
# # 2) View all pending reminders
243+
# custom_functions.append({
244+
# 'name': 'view_reminders',
245+
# 'description': (
246+
# "[Use if the user wants to see their current/pending reminders.] "
247+
# "No arguments needed."
248+
# ),
249+
# 'parameters': {
250+
# 'type': 'object',
251+
# 'properties': {},
252+
# 'required': []
253+
# }
254+
# })
255+
256+
# # 3) Delete a reminder
257+
# custom_functions.append({
258+
# 'name': 'delete_reminder',
259+
# 'description': (
260+
# "[Use if the user wants to delete/cancel an existing reminder by ID.] "
261+
# "Reminders are typically identified by an integer ID."
262+
# ),
263+
# 'parameters': {
264+
# 'type': 'object',
265+
# 'properties': {
266+
# 'reminder_id': {
267+
# 'type': 'integer',
268+
# 'description': (
269+
# "The ID number of the reminder to delete. "
270+
# "If user doesn't know the ID, prompt them to /viewreminders first or if they ask for you to show them their reminders."
271+
# )
272+
# }
273+
# },
274+
# 'required': ['reminder_id']
275+
# }
276+
# })
277+
278+
# # 4) Edit a reminder (optional)
279+
# custom_functions.append({
280+
# 'name': 'edit_reminder',
281+
# 'description': (
282+
# "[Use if user wants to update an existing reminder. Provide the ID plus new text/time.] "
283+
# "Either 'due_time_utc' or 'reminder_text' or both can be changed."
284+
# ),
285+
# 'parameters': {
286+
# 'type': 'object',
287+
# 'properties': {
288+
# 'reminder_id': {
289+
# 'type': 'integer',
290+
# 'description': "The ID of the reminder to edit."
291+
# },
292+
# 'due_time_utc': {
293+
# 'type': 'string',
294+
# 'description': (
295+
# "The new date/time in UTC, e.g. 2025-01-02T13:00:00Z. "
296+
# "If user says 'tomorrow 10am', parse that into a UTC string."
297+
# )
298+
# },
299+
# 'reminder_text': {
300+
# 'type': 'string',
301+
# 'description': "The updated reminder text."
302+
# }
303+
# },
304+
# 'required': ['reminder_id']
305+
# }
306+
# })
307+
308+
# else:
309+
# logging.info("Reminders are disabled in config.ini => not adding reminder functions.")
310+
152311
# # jul 26 / 2024
153312
# custom_functions.append({
154313
# 'name': 'get_rss_feed',

0 commit comments

Comments
 (0)