import logging import aiosqlite from typing import Optional ,Dict ,Callable ,Any ,TYPE_CHECKING ,Tuple from aiogram import Bot from datetime import datetime from config .settings import Settings from db .database import get_user from bot .middlewares .i18n import JsonI18n if TYPE_CHECKING : from .subscription_service import SubscriptionService class ReferralService : def __init__ (self ,db_conn_provider :Callable [[],Any ],settings :Settings ,subscription_service_instance :'SubscriptionService',bot :Bot ,i18n :JsonI18n ): self .db_conn_provider =db_conn_provider self .settings =settings self .subscription_service =subscription_service_instance self .bot =bot self .i18n =i18n async def process_new_user_referral (self ,referee_user_id :int ,inviter_user_id :Optional [int ]): if inviter_user_id and referee_user_id !=inviter_user_id : logging .info (f"Referral link used: User {referee_user_id} was invited by {inviter_user_id}.") pass async def apply_referral_bonuses_for_payment ( self ,referee_user_id :int ,purchased_subscription_months :int , db_conn :aiosqlite .Connection )->Dict [str ,Any ]: """Applies bonuses. Notifies inviter. Returns referee bonus details.""" referee_final_end_date :Optional [datetime ]=None referee_bonus_applied_days :Optional [int ]=None try : referee_user_row =await get_user (referee_user_id ,db_conn =db_conn ) if not referee_user_row or referee_user_row ['referred_by_id']is None : logging .debug (f"User {referee_user_id} not referred or inviter ID missing. No referral bonuses.") return {"referee_bonus_applied_days":None ,"referee_new_end_date":None } inviter_user_id =referee_user_row ['referred_by_id'] inviter_user_row =await get_user (inviter_user_id ,db_conn =db_conn ) referee_name =referee_user_row ['first_name']or f"User {referee_user_id}" default_lang_for_placeholder =getattr (self .settings ,'DEFAULT_LANGUAGE','en') inviter_name =inviter_user_row ['first_name']if inviter_user_row else self .i18n .gettext (default_lang_for_placeholder ,"friend_placeholder") inviter_bonus =self .settings .referral_bonus_inviter .get (purchased_subscription_months ) referee_bonus =self .settings .referral_bonus_referee .get (purchased_subscription_months ) if inviter_bonus and inviter_bonus >0 and inviter_user_row : new_end_date_inviter =await self .subscription_service .extend_subscription_for_referral ( user_id =inviter_user_id ,bonus_days =inviter_bonus ,db_conn =db_conn ) if new_end_date_inviter : logging .info (f"Bonus applied for inviter {inviter_user_id}.") try : inviter_lang =inviter_user_row .get ('language_code',default_lang_for_placeholder ) _i =lambda k ,**kw :self .i18n .gettext (inviter_lang ,k ,**kw ) await self .bot .send_message ( inviter_user_id , _i ("referral_bonus_inviter_notification_extended", days =inviter_bonus , referee_name =referee_name , new_end_date =new_end_date_inviter .strftime ('%Y-%m-%d') ) ) except Exception as e : logging .error (f"Failed to send bonus notification to inviter {inviter_user_id}: {e}") else :logging .warning (f"Failed to apply bonus subscription extension for inviter {inviter_user_id}.") if referee_bonus and referee_bonus >0 : new_end_date_referee =await self .subscription_service .extend_subscription_for_referral ( user_id =referee_user_id ,bonus_days =referee_bonus ,db_conn =db_conn ,is_referee_bonus =True ) if new_end_date_referee : logging .info (f"Bonus applied for referee {referee_user_id}.") referee_final_end_date =new_end_date_referee referee_bonus_applied_days =referee_bonus else :logging .warning (f"Failed to apply bonus subscription extension for referee {referee_user_id}.") return {"referee_bonus_applied_days":referee_bonus_applied_days ,"referee_new_end_date":referee_final_end_date } except Exception as e : logging .error (f"Error in apply_referral_bonuses_for_payment (db_conn: {db_conn is not None}): {e}",exc_info =True ) raise def generate_referral_link (self ,bot_username :str ,inviter_user_id :int )->str : return f"https://t.me/{bot_username}?start=ref_{inviter_user_id}"