Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/storage/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class MemeData(CustomModel):
type: MemeType
telegram_file_id: str
caption: str | None
language_code: str | None = None
recommended_by: str | None = None
nlikes: int = 0

Expand Down
16 changes: 8 additions & 8 deletions src/tgbot/handlers/deep_link.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from datetime import datetime

from telegram import Bot
Expand All @@ -13,8 +12,9 @@
get_user_by_id,
update_user,
)
from src.tgbot.sharing import parse_meme_share_deep_link

LINK_UNDER_MEME_PATTERN = r"s_\d+_\d+"
LINK_UNDER_MEME_PATTERN = r"^s_\d+_\d+$"


async def handle_invited_user(
Expand All @@ -23,11 +23,11 @@ async def handle_invited_user(
invited_user_name: str,
deep_link: str | None,
):
if not deep_link or not re.match(LINK_UNDER_MEME_PATTERN, deep_link):
share_link = parse_meme_share_deep_link(deep_link)
if share_link is None:
return

_, user_id, _ = deep_link.split("_")
invitor_user_id = int(user_id)
invitor_user_id = share_link.sharer_user_id

# get invitor user
invitor_user = await get_user_by_id(invitor_user_id)
Expand Down Expand Up @@ -81,11 +81,11 @@ async def handle_shared_meme_reward(
clicked_user_id: int,
deep_link: str | None,
):
if not deep_link or not re.match(LINK_UNDER_MEME_PATTERN, deep_link):
share_link = parse_meme_share_deep_link(deep_link)
if share_link is None:
return

_, user_id, _ = deep_link.split("_")
invitor_user_id = int(user_id)
invitor_user_id = share_link.sharer_user_id

if clicked_user_id == invitor_user_id:
return # don't reward clicking on your links
Expand Down
85 changes: 72 additions & 13 deletions src/tgbot/handlers/inline.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from telegram import (
InlineQueryResultCachedGif,
InlineQueryResultCachedPhoto,
InlineQueryResultCachedVideo,
InlineQueryResultsButton,
Update,
)
from telegram.constants import ParseMode
from telegram.ext import ContextTypes

from src.config import settings
from src.localizer import t
from src.storage.constants import MemeType
from src.tgbot.constants import (
INLINE_SEARCH_REQUEST_DEEPLINK,
)
Expand All @@ -16,8 +18,10 @@
from src.tgbot.service import (
create_inline_chosen_result_log,
create_inline_search_log,
get_shareable_meme_by_id,
search_memes_for_inline_query,
)
from src.tgbot.sharing import get_meme_share_link
from src.tgbot.user_info import get_user_info

MIN_SEARCH_QUERY_LENGTH = 3
Expand All @@ -27,8 +31,7 @@


def get_inline_result_ref_link(user_id: int, meme_id: int):
deep_link = f"ir_{user_id}_{meme_id}" # inline result
return f"https://t.me/{settings.TELEGRAM_BOT_USERNAME}?start={deep_link}"
return get_meme_share_link(user_id, meme_id)


def get_inline_result_caption(meme, user_info):
Expand All @@ -42,6 +45,62 @@ def get_inline_result_caption(meme, user_info):
return caption


def parse_exact_meme_inline_query(query: str) -> int | None:
if not query.startswith("#"):
return None

meme_id = query[1:]
if not meme_id.isdigit():
return None

return int(meme_id)


def build_inline_meme_result(meme: dict, user_info: dict):
caption = get_inline_result_caption(meme, user_info)
meme_type = MemeType(meme["type"])
if meme_type == MemeType.IMAGE:
return InlineQueryResultCachedPhoto(
id=str(meme["id"]),
photo_file_id=meme["telegram_file_id"],
caption=caption,
parse_mode=ParseMode.HTML,
)
if meme_type == MemeType.VIDEO:
return InlineQueryResultCachedVideo(
id=str(meme["id"]),
video_file_id=meme["telegram_file_id"],
title="Fast Food Memes",
caption=caption,
parse_mode=ParseMode.HTML,
)
if meme_type == MemeType.ANIMATION:
return InlineQueryResultCachedGif(
id=str(meme["id"]),
gif_file_id=meme["telegram_file_id"],
caption=caption,
parse_mode=ParseMode.HTML,
)
return None


async def answer_exact_meme_inline_query(update: Update, user_info: dict, meme_id: int) -> None:
meme = await get_shareable_meme_by_id(meme_id)
result = build_inline_meme_result(meme, user_info) if meme else None
results = [result] if result else []
await update.inline_query.answer(
results,
cache_time=INLINE_SEARCH_RESULT_CACHE_SECONDS,
is_personal=True,
)

await create_inline_search_log(
user_id=update.effective_user.id,
query=update.inline_query.query.strip().lower(),
chat_type=update.inline_query.chat_type,
)


async def search_inline(update: Update, _: ContextTypes.DEFAULT_TYPE):
try:
user_info = await get_user_info(update.effective_user.id)
Expand All @@ -56,6 +115,10 @@ async def search_inline(update: Update, _: ContextTypes.DEFAULT_TYPE):

query = update.inline_query.query.strip().lower()

exact_meme_id = parse_exact_meme_inline_query(query)
if exact_meme_id is not None:
return await answer_exact_meme_inline_query(update, user_info, exact_meme_id)

if len(query) == 0:
# TODO: show trending / recommended memes
return await update.inline_query.answer(
Expand Down Expand Up @@ -92,17 +155,13 @@ async def search_inline(update: Update, _: ContextTypes.DEFAULT_TYPE):
await update.inline_query.answer([], button=no_results_button)
return

results = [
InlineQueryResultCachedPhoto(
id=str(meme["id"]),
photo_file_id=meme["telegram_file_id"],
caption=get_inline_result_caption(meme, user_info),
parse_mode=ParseMode.HTML,
)
for meme in memes
]
results = [result for meme in memes if (result := build_inline_meme_result(meme, user_info))]

await update.inline_query.answer(results, cache_time=INLINE_SEARCH_RESULT_CACHE_SECONDS)
await update.inline_query.answer(
results,
cache_time=INLINE_SEARCH_RESULT_CACHE_SECONDS,
is_personal=True,
)

await create_inline_search_log(
user_id=update.effective_user.id,
Expand Down
9 changes: 8 additions & 1 deletion src/tgbot/handlers/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
update_user_meme_reaction,
)
from src.tgbot.handlers.moderator.invite import maybe_send_moderator_invite
from src.tgbot.handlers.onboarding import onboarding_flow
from src.tgbot.senders.next_message import next_message
from src.tgbot.sharing import MEME_REACTION_CONTEXT_ONBOARD
from src.tgbot.user_info import update_user_info_counters


Expand All @@ -25,7 +27,9 @@ def _fire_and_forget(coro):

async def handle_reaction(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
user_id = update.effective_user.id
meme_id, reaction_id = update.callback_query.data[2:].split(":")
callback_parts = update.callback_query.data[2:].split(":")
meme_id, reaction_id = callback_parts[:2]
reaction_context = callback_parts[2] if len(callback_parts) > 2 else None
logging.info(f"🛜 reaction: user_id={user_id}, meme_id={meme_id}, reaction_id={reaction_id}")

# do that in sync since we'll use counters in next_message
Expand All @@ -41,6 +45,9 @@ async def handle_reaction(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
)

if reaction_is_new:
if reaction_context == MEME_REACTION_CONTEXT_ONBOARD:
return await onboarding_flow(update, context.bot)

return await next_message(
context.bot,
user_id,
Expand Down
81 changes: 73 additions & 8 deletions src/tgbot/handlers/start.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import asyncio
import re

from telegram import Bot, Update
from telegram.ext import ContextTypes

from src.tgbot.handlers.deep_link import (
LINK_UNDER_MEME_PATTERN,
handle_invited_user,
handle_shared_meme_reward,
)
from src.recommendations.meme_queue import clear_meme_queue_for_user
from src.recommendations.service import user_meme_reaction_exists
from src.storage.schemas import MemeData
from src.tgbot.handlers.deep_link import handle_invited_user, handle_shared_meme_reward
from src.tgbot.handlers.language import (
handle_language_settings,
init_user_languages_from_tg_user,
Expand All @@ -17,14 +15,21 @@
handle_show_kitchen,
)
from src.tgbot.logs import log
from src.tgbot.senders.meme import send_meme_to_user
from src.tgbot.senders.next_message import next_message
from src.tgbot.service import (
add_user_language,
create_or_update_user,
get_shareable_meme_by_id,
get_tg_user_by_id,
get_user_languages,
log_user_deep_link,
save_tg_user,
)
from src.tgbot.sharing import (
MEME_REACTION_CONTEXT_ONBOARD,
parse_meme_share_deep_link,
)
from src.tgbot.user_info import update_user_info_cache


Expand All @@ -42,7 +47,7 @@ async def save_user_data(user_id: int, update: Update, deep_link: str | None) ->
deep_link=deep_link
if tg_user is None
or tg_user["deep_link"] is None
or not re.match(LINK_UNDER_MEME_PATTERN, tg_user["deep_link"])
or parse_meme_share_deep_link(tg_user["deep_link"]) is None
else None,
)

Expand Down Expand Up @@ -81,6 +86,47 @@ def _is_blocked_acquisition_channel(deep_link: str | None) -> bool:
return deep_link in BLOCKED_ACQUISITION_CHANNELS or deep_link.startswith("tapps")


async def _add_meme_language_from_share_click(user_id: int, meme_row: dict) -> None:
language_code = meme_row.get("language_code")
if not language_code:
return

user_languages = await get_user_languages(user_id)
if language_code in user_languages:
return

await add_user_language(user_id, language_code)
await clear_meme_queue_for_user(user_id)
await update_user_info_cache(user_id)


async def _send_shared_meme_from_deep_link(
bot: Bot,
user_id: int,
deep_link: str | None,
reaction_context: str | None = None,
) -> bool:
share_link = parse_meme_share_deep_link(deep_link)
if share_link is None:
return False

meme_row = await get_shareable_meme_by_id(share_link.meme_id)
if meme_row is None:
return False

if await user_meme_reaction_exists(user_id, share_link.meme_id):
return False

await _add_meme_language_from_share_click(user_id, meme_row)
await send_meme_to_user(
bot,
user_id,
MemeData(**meme_row),
reaction_context=reaction_context,
)
return True


async def handle_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
# Side effects every /start MUST run, regardless of deep_link branch:
# 1. user_tg + user upsert (save_user_data)
Expand Down Expand Up @@ -133,7 +179,12 @@ async def handle_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
return await handle_wrapped(update, context)

if created: # new user:
await handle_language_settings(update, context)
shared_meme_sent = await _send_shared_meme_from_deep_link(
context.bot,
user_id,
deep_link,
reaction_context=MEME_REACTION_CONTEXT_ONBOARD,
)

await handle_invited_user(
context.bot,
Expand All @@ -142,6 +193,11 @@ async def handle_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
deep_link,
)

if shared_meme_sent:
return

await handle_language_settings(update, context)

# handle giveaway after onboarding so user_language rows exist
if deep_link and deep_link.startswith("giveaway_"):
from src.tgbot.handlers.treasury.giveaway import handle_giveaway
Expand All @@ -154,6 +210,15 @@ async def handle_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No

return await handle_giveaway(update, context, deep_link)

shared_meme_sent = await _send_shared_meme_from_deep_link(
context.bot,
user_id,
deep_link,
)
if shared_meme_sent:
await handle_shared_meme_reward(context.bot, user_id, deep_link)
return

await next_message(
context.bot,
user_id,
Expand Down
Loading
Loading