Skip to content

Commit 547fe67

Browse files
committed
feat: deprecation warning on Stop messages
1 parent a60bb6f commit 547fe67

File tree

13 files changed

+170
-44
lines changed

13 files changed

+170
-44
lines changed

Diff for: sample.env

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ USER_RATE_LIMIT_AMOUNT=5
1313
USER_RATE_LIMIT_TIME=1
1414
TYPING_SAFE_LIMIT_TIME=30
1515
INLINE_CACHE_TIME=300
16+
STOP_MESSAGES_DEPRECATION_REMINDER_AFTER_SECONDS=300 # 300s = 5 minutes
17+
STOP_MESSAGES_DEPRECATION_REMINDER_LOOP_DELAY_SECONDS=30
1618

1719
# Bus API
1820
API_URL=http://localhost:5000

Diff for: static/messages.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ stop:
9191
:heavy_plus_sign:Buses
9292
less_buses:
9393
:heavy_minus_sign:Buses
94+
deprecated_warning: :warning:<b>Este mensaje lleva más de 5 minutos desactualizado</b>, pulsa sobre :arrows_counterclockwise:<b>Actualizar</b> para refrescarlo
9495
stop_rename:
9596
request: |-
9697
:pencil2:¿Qué nombre deseas darle a la parada <b>#{stop_id} ({stop_name})</b>?

Diff for: vigobusbot/entrypoint.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@
1111
from vigobusbot.settings_handler import telegram_settings as settings
1212
from vigobusbot.logger import logger
1313

14-
__all__ = ("run",)
15-
1614

1715
def run():
1816
load_static_files()
1917
bot = get_bot()
2018
asyncio.get_event_loop().run_until_complete(bot.set_commands())
19+
asyncio.get_event_loop().run_until_complete(bot.start_background_services())
2120

2221
if settings.method == "webhook":
2322
logger.debug("Starting the bot with the Webhook method...")
24-
pass
23+
raise Exception("Webhook method not yet implemented")
2524
else:
2625
logger.debug("Starting the bot with the Polling method...")
2726
start_polling(bot)

Diff for: vigobusbot/persistence_api/saved_stops/services/encoder.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
22
Functions to encode data from raw (Bot to Persist API)
33
"""
44

5-
# # Native # #
65
import hashlib
76

8-
# # Package # #
97
from .key_generator import *
108

11-
# # Project # #
129
from vigobusbot.persistence_api.saved_stops.entities import SavedStop, SavedStopEncoded
1310
from vigobusbot.settings_handler import persistence_settings as settings
1411

15-
__all__ = ("encode_user_id", "encode_stop", "encode_string")
16-
1712

1813
def encode_user_id(user_id: int) -> str:
1914
sha512 = hashlib.sha512()
@@ -26,10 +21,14 @@ def encode_string(data: str, key: Fernet) -> str:
2621
return key.encrypt(data.encode()).decode()
2722

2823

29-
def encode_stop(raw_stop: SavedStop, user_id: int) -> SavedStopEncoded:
24+
def encode_string_for_user(user_id: int, data: str) -> str:
3025
key = get_user_key(user_id)
26+
return encode_string(data, key)
27+
28+
29+
def encode_stop(raw_stop: SavedStop, user_id: int) -> SavedStopEncoded:
3130
return SavedStopEncoded(
3231
stop_id=raw_stop.stop_id,
3332
user_id=encode_user_id(user_id=user_id),
34-
stop_name=encode_string(key=key, data=raw_stop.stop_name) if raw_stop.stop_name else None
33+
stop_name=encode_string_for_user(user_id, raw_stop.stop_name) if raw_stop.stop_name else None
3534
)

Diff for: vigobusbot/settings_handler/__init__.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22
Load settings from dotenv file or environment variables
33
"""
44

5-
# # Native # #
65
from typing import Optional
76

8-
# # Installed # #
97
import pydantic
108
import aiogram.bot.api
119

12-
# # Package # #
1310
from .helpers import *
1411

1512
__all__ = ("telegram_settings", "api_settings", "persistence_settings", "mongo_settings", "system_settings")
@@ -37,6 +34,11 @@ class TelegramSettings(BaseBotSettings):
3734
typing_safe_limit_time: float = 30
3835
"""Timeout for a Typing chat action to end"""
3936
inline_cache_time: int = 300
37+
stop_messages_deprecation_reminder_after_seconds: int = 0
38+
"""Time for a Stop message to be considered deprecated, thus update with a warning about it being deprecated.
39+
If 0, disable this feature."""
40+
stop_messages_deprecation_reminder_loop_delay_seconds: int = 60
41+
"""Delay for the loop that checks for deprecated Stop messages."""
4042

4143
def __init__(self, **kwargs):
4244
super().__init__(**kwargs)

Diff for: vigobusbot/telegram_bot/bot.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,17 @@
22
Bot class and bot instance getter and generator
33
"""
44

5-
# # Native # #
5+
import asyncio
66
from typing import Optional
77

8-
# # Installed # #
98
import aiogram
109

11-
# # Package # #
1210
from .handlers import register_handlers
13-
14-
# # Project # #
11+
from vigobusbot.telegram_bot.services.stop_messages_deprecation_reminder import stop_messages_deprecation_reminder_worker
1512
from vigobusbot.settings_handler import telegram_settings as settings
1613
from vigobusbot.static_handler import get_messages
1714
from vigobusbot.logger import logger
1815

19-
__all__ = ("Bot", "get_bot")
20-
2116

2217
class Bot(aiogram.Bot):
2318
def __init__(self, **kwargs):
@@ -55,6 +50,10 @@ async def set_commands(self) -> bool:
5550
logger.opt(exception=True).warning("Bot commands could not be set")
5651
return False
5752

53+
async def start_background_services(self):
54+
# noinspection PyAsyncCall
55+
asyncio.create_task(stop_messages_deprecation_reminder_worker(self))
56+
5857

5958
_bot: Optional[Bot] = None
6059

Diff for: vigobusbot/telegram_bot/handlers/callback_handlers.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
Callback Handlers for the Callback Queries, produced when users press buttons from the inline keyboards on messages
33
"""
44

5-
# # Native # #
65
import asyncio
76

8-
# # Installed # #
97
import aiogram
108

11-
# # Project # #
129
from vigobusbot.telegram_bot.services import request_handler
1310
from vigobusbot.telegram_bot.services.status_sender import start_typing, stop_typing
1411
from vigobusbot.telegram_bot.services.stop_rename_request_handler import StopRenameRequestContext
1512
from vigobusbot.telegram_bot.services.stop_rename_request_handler import register_stop_rename_request
13+
from vigobusbot.telegram_bot.services.sent_messages_persistence import persist_sent_stop_message
1614
from vigobusbot.telegram_bot.services.message_generators import *
1715
from vigobusbot.persistence_api import saved_stops
1816
from vigobusbot.static_handler import get_messages
@@ -53,18 +51,20 @@ async def stop_refresh(callback_query: aiogram.types.CallbackQuery, callback_dat
5351
**callback_data
5452
)
5553

54+
# TODO Review:
5655
# For now, not sending "typing" status, since after message is updated it can still show "typing"...
5756
# await start_typing(bot=callback_query.bot, chat_id=chat_id)
5857

5958
text, markup = await generate_stop_message(context)
6059

61-
await callback_query.bot.edit_message_text(
60+
msg = await callback_query.bot.edit_message_text(
6261
text=text,
6362
chat_id=chat_id,
6463
message_id=message_id,
6564
inline_message_id=inline_message_id,
6665
reply_markup=markup
6766
)
67+
await persist_sent_stop_message(msg)
6868

6969
except MessageNotModified:
7070
# MessageNotModified exceptions can be triggered when user presses Update button many times too quickly,
@@ -89,11 +89,12 @@ async def stop_get(callback_query: aiogram.types.CallbackQuery, callback_data: d
8989
await start_typing(bot=callback_query.bot, chat_id=chat_id)
9090
text, markup = await generate_stop_message(context)
9191

92-
await callback_query.bot.send_message(
92+
msg = await callback_query.bot.send_message(
9393
chat_id=chat_id,
9494
text=text,
9595
reply_markup=markup
9696
)
97+
await persist_sent_stop_message(msg)
9798

9899
finally:
99100
stop_typing(chat_id)

Diff for: vigobusbot/telegram_bot/handlers/inline_handlers.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from vigobusbot.telegram_bot.services.message_generators import generate_search_stops_inline_responses
1010
from vigobusbot.telegram_bot.services.message_generators import generate_stop_message
1111
from vigobusbot.telegram_bot.services.message_generators import SourceContext
12+
from vigobusbot.telegram_bot.services.sent_messages_persistence import persist_sent_message, PersistMessageTypes
1213
from vigobusbot.telegram_bot.services import request_handler
1314
from vigobusbot.settings_handler import telegram_settings as settings
1415
from vigobusbot.logger import logger
@@ -42,11 +43,15 @@ async def stop_search_chosen_result(chosen_inline_query: aiogram.types.ChosenInl
4243
)
4344
text, markup = await generate_stop_message(context)
4445

45-
await chosen_inline_query.bot.edit_message_text(
46+
msg = await chosen_inline_query.bot.edit_message_text(
4647
inline_message_id=chosen_inline_query.inline_message_id,
4748
text=text,
4849
reply_markup=markup
4950
)
51+
persist_sent_message(
52+
msg_type=PersistMessageTypes.STOP,
53+
message=msg,
54+
)
5055

5156

5257
def register_handlers(dispatcher: aiogram.Dispatcher):

Diff for: vigobusbot/telegram_bot/handlers/message_handlers.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22
Telegram Bot Message Handlers: functions that handle incoming messages, depending on their command or content.
33
"""
44

5-
# # Native # #
65
import asyncio
76

8-
# # Installed # #
97
import aiogram
108

11-
# # Package # #
129
from . import test_message_handlers
13-
14-
# # Project # #
1510
from vigobusbot.telegram_bot.services import request_handler
1611
from vigobusbot.telegram_bot.services.status_sender import start_typing, stop_typing
1712
from vigobusbot.telegram_bot.services.feedback_request_handler import get_feedback_request_context
@@ -24,6 +19,7 @@
2419
from vigobusbot.telegram_bot.services.message_generators import SourceContext, FeedbackForceReply
2520
from vigobusbot.telegram_bot.services.message_generators import generate_stop_message, generate_saved_stops_message
2621
from vigobusbot.telegram_bot.services.message_generators import generate_search_stops_message
22+
from vigobusbot.telegram_bot.services.sent_messages_persistence import persist_sent_stop_message
2723
from vigobusbot.persistence_api.saved_stops import delete_all_stops
2824
from vigobusbot.settings_handler import system_settings
2925
from vigobusbot.static_handler import get_messages
@@ -128,11 +124,12 @@ async def command_stop(message: Message, *args, **kwargs):
128124
await start_typing(bot=message.bot, chat_id=chat_id)
129125
text, markup = await generate_stop_message(context)
130126

131-
await message.bot.send_message(
127+
msg = await message.bot.send_message(
132128
chat_id=chat_id,
133129
text=text,
134130
reply_markup=markup
135131
)
132+
await persist_sent_stop_message(msg)
136133

137134
except (ValueError, StopIteration):
138135
await message.reply(get_messages().stop.not_valid)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""SENT MESSAGES PERSISTENCE
2+
Persist certain messages sent by the bot to users.
3+
"""
4+
5+
import asyncio
6+
import datetime
7+
from typing import Dict
8+
9+
import pydantic
10+
from aiogram.types.message import Message
11+
12+
from vigobusbot.persistence_api.saved_stops.services.encoder import encode_user_id
13+
from vigobusbot.utils import get_datetime_now_utc
14+
15+
16+
class PersistMessageTypes:
17+
STOP = "stop"
18+
19+
20+
class MessagePersist(pydantic.BaseModel):
21+
message_type: str
22+
message: Message
23+
published_on: datetime.datetime
24+
25+
# Fields generated from complete_fields() method:
26+
message_key: str = ""
27+
chat_id_encoded: str = ""
28+
29+
def __init__(self, **kwargs):
30+
super().__init__(**kwargs)
31+
self.complete_fields()
32+
33+
def complete_fields(self):
34+
user_id = self.message.chat.id
35+
message_id = self.message.message_id
36+
self.chat_id_encoded = encode_user_id(user_id)
37+
self.message_key = f"{self.message_type}_{self.chat_id_encoded}_{message_id}"
38+
39+
def is_expired(self, now: datetime.datetime, ttl_seconds: int) -> bool:
40+
return (now - self.published_on).seconds >= ttl_seconds
41+
42+
class Config:
43+
arbitrary_types_allowed = True
44+
45+
46+
sent_messages_cache: Dict[str, MessagePersist] = dict()
47+
"""`{ message_key : message }`"""
48+
sent_messages_cache_lock = asyncio.Lock()
49+
50+
51+
async def persist_sent_message(msg_type: str, message: Message):
52+
message_persist = MessagePersist(
53+
message_type=msg_type,
54+
message=message,
55+
published_on=get_datetime_now_utc(),
56+
)
57+
async with sent_messages_cache_lock:
58+
sent_messages_cache[message_persist.message_key] = message_persist
59+
60+
61+
async def persist_sent_stop_message(message: Message):
62+
"""When any Stop message is sent or edited, it must be passed to this method.
63+
"""
64+
# noinspection PyAsyncCall
65+
asyncio.create_task(persist_sent_message(msg_type=PersistMessageTypes.STOP, message=message))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""STOP MESSAGES DEPRECATION REMINDER
2+
For the Stop messages sent to users, modify them with a warning/reminder when the messages are outdated for a long time.
3+
"""
4+
5+
import asyncio
6+
7+
import aiogram
8+
9+
from .sent_messages_persistence import sent_messages_cache, sent_messages_cache_lock, MessagePersist, PersistMessageTypes
10+
from vigobusbot.static_handler import get_messages
11+
from vigobusbot.settings_handler import telegram_settings
12+
from vigobusbot.utils import get_datetime_now_utc
13+
from vigobusbot.logger import logger
14+
15+
16+
async def stop_messages_deprecation_reminder_worker(bot: aiogram.Bot):
17+
if telegram_settings.stop_messages_deprecation_reminder_after_seconds <= 0:
18+
logger.info("Stop messages deprecation reminder is disabled")
19+
return
20+
21+
while True:
22+
await asyncio.sleep(telegram_settings.stop_messages_deprecation_reminder_loop_delay_seconds)
23+
24+
now = get_datetime_now_utc()
25+
ttl = telegram_settings.stop_messages_deprecation_reminder_after_seconds
26+
27+
async with sent_messages_cache_lock:
28+
cached_messages = list(sent_messages_cache.values())
29+
30+
for msg in cached_messages:
31+
if msg.message_type == PersistMessageTypes.STOP and msg.is_expired(now=now, ttl_seconds=ttl):
32+
async with sent_messages_cache_lock:
33+
# noinspection PyAsyncCall
34+
asyncio.create_task(_process_deprecated_stop_message(bot, msg))
35+
sent_messages_cache.pop(msg.message_key)
36+
37+
38+
async def _process_deprecated_stop_message(bot: aiogram.Bot, message: MessagePersist):
39+
with logger.contextualize(user_id_encoded=message.chat_id_encoded, message_id=message.message.message_id):
40+
logger.debug("Processing deprecated Stop message...")
41+
text = message.message.html_text
42+
text += "\n" + get_messages().stop.deprecated_warning
43+
44+
# noinspection PyBroadException
45+
try:
46+
await bot.edit_message_text(
47+
chat_id=message.message.chat.id,
48+
message_id=message.message.message_id,
49+
text=text,
50+
reply_markup=message.message.reply_markup,
51+
)
52+
logger.debug("Deprecated Stop message updated with warning")
53+
54+
except aiogram.exceptions.MessageToEditNotFound:
55+
logger.debug("Deprecated Stop message deleted by the user")
56+
57+
except Exception:
58+
logger.opt(exception=True).error("Failed updating deprecated Stop message")

0 commit comments

Comments
 (0)