diff --git a/src/config.py b/src/config.py
index d7f1d30d..ba5d7d7a 100644
--- a/src/config.py
+++ b/src/config.py
@@ -80,13 +80,6 @@ class Config(BaseSettings):
TELEGRAM_API_HASH: str | None = None
TELEGRAM_SESSION_STRING: str | None = None
- # @model_validator(mode="after")
- # def validate_sentry_non_local(self) -> "Config":
- # if self.ENVIRONMENT.is_deployed and not self.SENTRY_DSN:
- # raise ValueError("Sentry is not set")
-
- # return self
-
settings = Config()
diff --git a/src/database.py b/src/database.py
index c15f5787..8979eaae 100644
--- a/src/database.py
+++ b/src/database.py
@@ -697,13 +697,9 @@ def _is_transient_connection_error(exc: BaseException) -> bool:
return _is_stale_connection_error(exc) or _is_concurrent_use_error(exc)
-# Shared retry budget for stale / concurrent-use connection errors.
-# Three retries (25 ms + 50 ms + 100 ms backoff) give the event loop enough
-# time to dispose dirty connections under burst webhook concurrency.
-# Under traffic spikes the LIFO pool can hand out several consecutively
-# dirty connections; 2 retries proved insufficient (9 Sentry events on
-# 2026-04-12). Bumping to 3 retries with wider backoff makes exhaustion
-# astronomically unlikely while adding only ~175 ms worst-case latency.
+# The LIFO pool can return consecutive stale/concurrent-use connections under load.
+# Two retries proved insufficient (9 Sentry events on 2026-04-12); three retries
+# allow 25 + 50 + 100 ms of backoff before propagating the failure.
_TRANSIENT_MAX_RETRIES = 3
diff --git a/src/flows/crossposting/meme.py b/src/flows/crossposting/meme.py
index 7e0d81a5..3488f8d9 100644
--- a/src/flows/crossposting/meme.py
+++ b/src/flows/crossposting/meme.py
@@ -136,11 +136,6 @@ def _get_ru_caption_for_crossposting_meme(meme: MemeData, channel: Channel) -> s
cta = random.choice(CTAS)
ref_link = "https://t.me/ffmemesbot?start=sc_{}_{}".format(meme.id, channel.value)
- # emoji = get_random_emoji()
- # referral_html = f"""{emoji} Fast Food Memes"""
- # caption = escape(meme.caption, quote=False) if meme.caption else ""
- # text = caption + "\n\n" + referral_html
-
text = cta + ": " + f"""@ffmemesbot"""
return text
@@ -220,8 +215,7 @@ async def post_meme_to_tgchannelen():
next_meme = MemeData(**meme_data)
logger.info(f"Next meme for TG Channel EN: {next_meme.id}")
- # Persist the ranker decision for retro analysis. Failure must NOT propagate
- # โ Prefect retry would republish the meme.
+ # Ranker logging is best effort so analytics cannot block posting.
if decision:
try:
await log_ranker_decision(**decision)
@@ -280,8 +274,7 @@ async def post_meme_to_tgchannelru():
next_meme = MemeData(**meme_data)
logger.info(f"Next meme for TG Channel RU: {next_meme.id}")
- # Persist the ranker decision for retro analysis. Failure must NOT propagate
- # โ Prefect retry would republish the meme.
+ # Ranker logging is best effort so analytics cannot block posting.
if decision:
try:
await log_ranker_decision(**decision)
diff --git a/src/flows/storage/describe_memes.py b/src/flows/storage/describe_memes.py
index 284e1493..ff1f9c5c 100644
--- a/src/flows/storage/describe_memes.py
+++ b/src/flows/storage/describe_memes.py
@@ -70,7 +70,6 @@ async def describe_single_meme(
file_id = meme_row["telegram_file_id"]
existing_ocr = meme_row["ocr_result"] or {}
- # Download image from Telegram
try:
image_bytes = await download_meme_content_from_tg(file_id)
except Exception as e:
@@ -80,7 +79,6 @@ async def describe_single_meme(
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
- # Call vision model
try:
result = await call_openrouter_vision(image_b64, log, deadline=deadline, keys=keys)
except Exception as e:
diff --git a/src/localizer.py b/src/localizer.py
index 28311c93..3c2de5c9 100644
--- a/src/localizer.py
+++ b/src/localizer.py
@@ -3,7 +3,6 @@
import yaml
-# not sure where to put this const
DEFAULT_LANG = "en"
ALMOST_CIS_LANGUAGES = [
diff --git a/src/main.py b/src/main.py
index 0ddc9f12..485c53f9 100644
--- a/src/main.py
+++ b/src/main.py
@@ -19,21 +19,16 @@
@asynccontextmanager
async def lifespan(_application: FastAPI) -> AsyncGenerator:
- # Startup
tgbot_app.application = tgbot_app.setup_application(settings.ENVIRONMENT.is_deployed)
await tgbot_app.application.initialize()
- # flush all redis keys on startup for debug
- # await redis.redis_client.flushall()
-
- # if is_webhook: # all gunicorn workers will call this and hit rate limit
- # await bot.setup_webhook(bot.application)
+ # Do not register the webhook here: every Gunicorn worker runs lifespan,
+ # so startup registration would repeatedly hit Telegram's rate limit.
yield
if settings.ENVIRONMENT.is_testing:
return
- # Shutdown
await redis.pool.disconnect()
diff --git a/src/storage/etl.py b/src/storage/etl.py
index 5b4ed540..1e89bf25 100644
--- a/src/storage/etl.py
+++ b/src/storage/etl.py
@@ -55,10 +55,6 @@ async def insert_parsed_posts_from_telegram(
*,
discover_candidates: bool = True,
) -> None:
- # 1. find which memes are already in the database
- # 2. update existing memes
- # 3. insert new memes
-
result = await fetch_all(
select(meme_raw_telegram.c.post_id)
.where(meme_raw_telegram.c.meme_source_id == meme_source_id)
@@ -202,10 +198,6 @@ async def etl_memes_from_raw_telegram_posts(
*,
fresh_only: bool = True,
) -> None:
- # get transformed posts
- # find ones that are already in the database
- # create rows and update rows
- #
# Engagement filters before raw posts enter the meme table:
# 1. From each source's latest 10 posts, only promote the top 5 by views.
# Raw rows are still stored, so this threshold can be replayed later.
@@ -325,15 +317,6 @@ async def etl_memes_from_raw_telegram_posts(
},
)
- # find rows which already exist in db by two index columns:
- # meme_source_id and raw_meme_id
- # so we can update existing rows and create new ones
-
- # join two tables meme_raw_telegram and meme to get
- # the meme_source_id and raw_meme_id pairs
- # which are not present in memes table.
- # That will indicate that we need to create new rows
-
memes_not_in_memes_table = await fetch_all(
text(
"""
diff --git a/src/storage/upload.py b/src/storage/upload.py
index 0e071d3b..97839998 100644
--- a/src/storage/upload.py
+++ b/src/storage/upload.py
@@ -78,7 +78,7 @@ async def _image_hash(meme_id: int, meme_type: MemeType, content: bytes, media:
async def _upload_meme_content_to_tg(
meme_id: int,
meme_type: MemeType,
- content: bytes, # ??
+ content: bytes,
) -> dict[str, Any] | None:
file_id = None
media = None
@@ -166,8 +166,6 @@ async def upload_meme_content_to_tg(
*,
observability_context: dict[str, Any] | None = None,
) -> dict[str, Any] | None:
- # just a wrapper to handle retries
-
meme_result = None
max_attempts = 3
content_size = len(content)
diff --git a/src/storage/watermark.py b/src/storage/watermark.py
index db4c35e7..2adc7ee4 100644
--- a/src/storage/watermark.py
+++ b/src/storage/watermark.py
@@ -16,10 +16,8 @@ def draw_text_with_outline(
outline_colour: tuple,
) -> None:
x, y = position
- # Draw outline
for adj in range(-1, 2):
for ops in range(-1, 2):
- # Avoid the center pixel
if adj != 0 or ops != 0:
draw.text((x + adj, y + ops), text, font=font, fill=outline_colour)
draw.text(position, text, font=font, fill=text_colour)
@@ -42,39 +40,27 @@ def find_least_detailed_corner(img: Image, text_bbox: tuple, margin: int) -> tup
- A tuple (x, y) representing the top-left coordinate
of the selected corner for text placement.
"""
- # Convert the original image to grayscale for intensity analysis
gray_img = img.convert("L")
img_w, img_h = img.size
- # Calculate the width and height of the text bounding box
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
- # Define dictionary with the coordinates of each corner with some margin applied
corners = {
"top_left": (margin, margin),
"top_right": (img_w - text_width - margin, margin),
"bottom_left": (margin, img_h - text_height - margin),
"bottom_right": (img_w - text_width - margin, img_h - text_height - margin),
}
- # Initialize with infinity for comparison
least_detailed_score = float("inf")
- # To store the x, y coordinates for watermark
selected_corner = None
selected_wm_colour = None
- # Iterate through each corner to find the one with the least detail background
for _, (x, y) in corners.items():
- # Crop the image around the current corner
- # according to the text bounding box size
sector = gray_img.crop((x, y, x + text_width, y + text_height))
- # Calculate mean intensity of the cropped area
mean_intensity = ImageStat.Stat(sector).mean[0]
- # Determine how close the mean intensity is to either black (0) or white (255)
- # indicates how uniform the background of the selected sector is
+ # Use mean brightness near black or white as a cheap proxy for a quiet background.
distance_to_extreme = min(abs(0 - mean_intensity), abs(255 - mean_intensity))
- # Update if this corner has a lower score (closer to black or white)
if distance_to_extreme < least_detailed_score:
least_detailed_score = distance_to_extreme
selected_corner = (x, y)
- # Selecting watermark colour based on mean intensity
selected_wm_colour = (0, 0, 0) if mean_intensity > 178 else (255, 255, 255)
return selected_corner, selected_wm_colour
@@ -110,7 +96,6 @@ def draw_corner_watermark(
text_position, text_colour = find_least_detailed_corner(base, text_bbox, margin)
outline_colour = (0, 0, 0, 255) if text_colour == (255, 255, 255) else (255, 255, 255, 255)
draw_text_with_outline(d, text_position, text, font, text_colour, outline_colour)
- # text opacity
txt.putalpha(txt.getchannel("A").point(lambda x: x * text_opacity))
return Image.alpha_composite(base, txt).convert("RGB")
diff --git a/src/tgbot/handlers/admin/forward_channel.py b/src/tgbot/handlers/admin/forward_channel.py
index a91fdd84..ae09c93c 100644
--- a/src/tgbot/handlers/admin/forward_channel.py
+++ b/src/tgbot/handlers/admin/forward_channel.py
@@ -70,10 +70,6 @@ async def handle_forwarded_from_tgchannelru(
await update.message.reply_text(f"Forwarding post to {len(users)}")
await log(f"Forwarding post to {len(users)}", context.bot)
-
- # return await update.message.reply_text(
- # f"Can't parse caption: {escape(str(update.message.to_dict()))}"
- # )
else:
url = urls[0]
meme_id_str = url.replace("https://t.me/ffmemesbot?start=sc_", "").split("_")[0]
diff --git a/src/tgbot/handlers/admin/user_info.py b/src/tgbot/handlers/admin/user_info.py
index a057942b..d28b531c 100644
--- a/src/tgbot/handlers/admin/user_info.py
+++ b/src/tgbot/handlers/admin/user_info.py
@@ -71,10 +71,5 @@ async def delete_user_data_confirmation_page(
async def delete_user_data(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Deletes all user data we have for testing purposes"""
- # user = await get_user_info(update.effective_user.id)
- # if user["type"] != UserType.ADMIN:
- # return
-
- # TODO: "are you sure" button + callback
await delete_user(update.effective_user.id)
await update.effective_user.send_message("Ciao ๐\n\n --> press /start to try again ๐พ")
diff --git a/src/tgbot/handlers/deep_link.py b/src/tgbot/handlers/deep_link.py
index 4d88a079..a50f0f85 100644
--- a/src/tgbot/handlers/deep_link.py
+++ b/src/tgbot/handlers/deep_link.py
@@ -29,7 +29,6 @@ async def handle_invited_user(
invitor_user_id = share_link.sharer_user_id
- # get invitor user
invitor_user = await get_user_by_id(invitor_user_id)
if not invitor_user:
return # Invitor doesn't exist
@@ -45,7 +44,6 @@ async def handle_invited_user(
was already invited by #{invited_user["inviter_id"]}"""
)
- # set inviter id
await update_user(invited_user["id"], inviter_id=invitor_user_id)
# Don't reward if the inviter cannot receive the bot notification.
diff --git a/src/tgbot/handlers/upload/upload_meme.py b/src/tgbot/handlers/upload/upload_meme.py
index 2d793c04..c63fd961 100644
--- a/src/tgbot/handlers/upload/upload_meme.py
+++ b/src/tgbot/handlers/upload/upload_meme.py
@@ -271,7 +271,6 @@ async def handle_message_with_meme(update: Update, context: ContextTypes.DEFAULT
raise Exception(f"not supported format: {update.message.to_json()}")
-# callback: RULES_ACCEPTED_CALLBACK_DATA
async def handle_rules_accepted_callback(
update: Update,
_: ContextTypes.DEFAULT_TYPE,
@@ -292,7 +291,6 @@ async def handle_rules_accepted_callback(
)
-# callback_data = LANG_SETTINGS_END_CALLBACK_DATA
async def handle_meme_upload_lang_other(
update: Update,
_: ContextTypes.DEFAULT_TYPE,
@@ -304,7 +302,6 @@ async def handle_meme_upload_lang_other(
)
-# callback: RULES_ACCEPTED_CALLBACK_DATA
async def handle_meme_upload_lang_selected(
update: Update,
context: ContextTypes.DEFAULT_TYPE,
@@ -320,8 +317,6 @@ async def handle_meme_upload_lang_selected(
await log(f"""๐ฅ Meme {meme["id"]} uploaded by #{update.effective_user.id}""", context.bot)
- # TODO: create a meme object from meme_raw_upload
-
await _edit_upload_callback_caption(
update.callback_query.message,
localizer.t("upload.submitted", user_info["interface_lang"]),
diff --git a/src/tgbot/repo/languages.py b/src/tgbot/repo/languages.py
index 2b1b9aad..d03e11ba 100644
--- a/src/tgbot/repo/languages.py
+++ b/src/tgbot/repo/languages.py
@@ -33,8 +33,6 @@ async def add_user_languages(
user_id: int,
language_codes: Sequence[str],
) -> None:
- # Prepare a list of dictionaries where each dictionary represents
- # the values to be inserted for one row.
values_to_insert = [
{"user_id": user_id, "language_code": language_code} for language_code in language_codes
]
diff --git a/src/tgbot/repo/popups.py b/src/tgbot/repo/popups.py
index 3199ebde..4a3ad6af 100644
--- a/src/tgbot/repo/popups.py
+++ b/src/tgbot/repo/popups.py
@@ -50,11 +50,12 @@ async def update_user_popup_log(
user_popup_logs.update()
.where(user_popup_logs.c.user_id == user_id)
.where(user_popup_logs.c.popup_id == popup_id)
- .where(user_popup_logs.c.reacted_at.is_(None)) # not sure abot that
+ # Only the first click may claim the reaction.
+ .where(user_popup_logs.c.reacted_at.is_(None))
.values(reacted_at=datetime.utcnow())
)
res = await execute(update_query)
reaction_is_new = res.rowcount > 0
if not reaction_is_new:
logging.warning(f"User {user_id} already reacted to popup {popup_id}!")
- return reaction_is_new # so I can filter double clicks
+ return reaction_is_new
diff --git a/src/tgbot/senders/alerts.py b/src/tgbot/senders/alerts.py
index 2c7a3ffc..b3db2d5b 100644
--- a/src/tgbot/senders/alerts.py
+++ b/src/tgbot/senders/alerts.py
@@ -1,8 +1,6 @@
from telegram import Bot, Message
from src import localizer
-
-# from src.tgbot.senders.keyboards import queue_empty_alert_keyboard
from src.tgbot.user_info import get_user_info
@@ -11,5 +9,4 @@ async def send_queue_preparing_alert(bot: Bot, user_id: int) -> Message:
return await bot.send_message(
chat_id=user_id,
text=localizer.t("service.out_of_memes", user_info["interface_lang"]),
- # reply_markup=queue_empty_alert_keyboard(),
)
diff --git a/src/tgbot/senders/keyboards.py b/src/tgbot/senders/keyboards.py
index 266b6f49..386cebc5 100644
--- a/src/tgbot/senders/keyboards.py
+++ b/src/tgbot/senders/keyboards.py
@@ -21,8 +21,6 @@
)
from src.tgbot.sharing import build_meme_share_button
-# IDEA: use sometimes another emoji pair like ๐คฃ/๐คฎ
-
HEART_EMOJI = ["โค๏ธ", "โฅ๏ธ", "๐", "๐", "๐", "๐งก", "๐", "๐ฉต"]
@@ -36,7 +34,6 @@ def meme_reaction_keyboard(
):
like = random.choice(HEART_EMOJI)
dislike = "โฌ"
- # like, dislike = "๐", "๐"
def reaction_callback(reaction_id: int) -> str:
callback_data = MEME_BUTTON_CALLBACK_DATA_PATTERN.format(