import asyncio from base64 import b64decode from datetime import datetime, timedelta from base58 import b58encode from sqlalchemy import and_, or_, select, desc from tonsdk.boc import Cell from tonsdk.utils import Address from app.core._blockchain.ton.platform import platform from app.core._blockchain.ton.toncenter import toncenter from app.core._utils.send_status import send_status from app.core.logger import make_log from app.core.models.node_storage import StoredContent from app.core.models.content.user_content import UserContent, UserAction from app.core._utils.resolve_content import resolve_content from app.core.models.wallet_connection import WalletConnection from app.core._keyboards import get_inline_keyboard from app.core.models._telegram import Wrapped_CBotChat from app.core.storage import db_session from app.core._config import CLIENT_TELEGRAM_API_KEY, CLIENT_TELEGRAM_BOT_USERNAME, PROJECT_HOST from app.core.models.user import User from app.core.models import StarsInvoice from app.core.events.service import record_event from app.core._secrets import hot_pubkey from base58 import b58encode import os import traceback async def license_index_loop(memory, platform_found: bool, seqno: int) -> [bool, int]: make_log("LicenseIndex", "Service running", level="debug") async with db_session() as session: async def check_telegram_stars_transactions(): # Проверка звездных telegram транзакций, обновление paid offset = {'desc': 'Статичное число заранее известного количества транзакций, которое даже не знает наш бот', 'value': 1}['value'] + \ 0 # session.query(StarsInvoice).count() limit = 100 while True: make_log("StarsProcessing", f"Star payments: offset={offset}, limit={limit}", level="DEBUG") star_payments = (await memory._client_telegram_bot.get_star_transactions(offset, limit)).transactions if not star_payments: make_log("StarsProcessing", "No more star payments", level="DEBUG") return for star_payment in star_payments: if star_payment.receiver: continue try: existing_invoice = (await session.execute(select(StarsInvoice).where( StarsInvoice.external_id == star_payment.source.invoice_payload ))).scalars().first() if not existing_invoice: continue if star_payment.amount == existing_invoice.amount: if not existing_invoice.paid: user = (await session.execute(select(User).where(User.id == existing_invoice.user_id))).scalars().first() existing_invoice.paid = True existing_invoice.paid_at = datetime.utcnow() existing_invoice.telegram_id = getattr(user, 'telegram_id', None) existing_invoice.payment_tx_id = getattr(star_payment, 'id', None) existing_invoice.payment_node_id = b58encode(hot_pubkey).decode() existing_invoice.payment_node_public_host = PROJECT_HOST existing_invoice.bot_username = CLIENT_TELEGRAM_BOT_USERNAME existing_invoice.is_remote = False await record_event( session, 'stars_payment', { 'invoice_id': existing_invoice.external_id, 'content_hash': existing_invoice.content_hash, 'amount': existing_invoice.amount, 'user_id': existing_invoice.user_id, 'telegram_id': existing_invoice.telegram_id, 'bot_username': CLIENT_TELEGRAM_BOT_USERNAME, 'type': existing_invoice.type, 'payment_node': { 'public_key': b58encode(hot_pubkey).decode(), 'public_host': PROJECT_HOST, }, 'paid_at': existing_invoice.paid_at.isoformat() + 'Z' if existing_invoice.paid_at else None, 'payment_tx_id': existing_invoice.payment_tx_id, }, origin_host=PROJECT_HOST, ) await session.commit() licensed_content = (await session.execute(select(StoredContent).where(StoredContent.hash == existing_invoice.content_hash))).scalars().first() if user and user.telegram_id and licensed_content: await (Wrapped_CBotChat(memory._client_telegram_bot, chat_id=user.telegram_id, user=user, db_session=session)).send_content( session, licensed_content ) except BaseException as e: make_log("StarsProcessing", f"Local error: {e}" + '\n' + traceback.format_exc(), level="error") offset += limit try: await check_telegram_stars_transactions() except BaseException as e: make_log("StarsProcessing", f"Error: {e}" + '\n' + traceback.format_exc(), level="error") # Проверка кошельков пользователей на появление новых NFT, добавление их в базу как неопознанные users = (await session.execute(select(User).where( User.last_use > datetime.now() - timedelta(hours=4) ).order_by(User.updated.asc()))).scalars().all() for user in users: user_wallet_address = await user.wallet_address_async(session) if not user_wallet_address: make_log("LicenseIndex", f"User {user.id} has no wallet address", level="debug") continue make_log("LicenseIndex", f"User {user.id} has wallet address {user_wallet_address}", level="debug") last_updated_licenses = user.meta.get('last_updated_licenses') must_skip = last_updated_licenses and (datetime.now() - datetime.fromisoformat(last_updated_licenses)) < timedelta(minutes=1) make_log("LicenseIndex", f"User: {user.id}, last_updated_licenses: {last_updated_licenses}, must_skip: {must_skip}", level="debug") if must_skip: continue try: await user.scan_owned_user_content(session) user.meta = {**user.meta, 'last_updated_licenses': datetime.now().isoformat()} await session.commit() except BaseException as e: make_log("LicenseIndex", f"Error: {e}" + '\n' + traceback.format_exc(), level="error") # Проверка NFT на актуальность данных, в том числе уже проверенные process_content = (await session.execute(select(UserContent).where( and_( UserContent.type.startswith('nft/'), UserContent.type != 'nft/ignored', UserContent.updated < (datetime.now() - timedelta(minutes=60)), ) ).order_by(UserContent.updated.asc()))).scalars().first() if process_content: make_log("LicenseIndex", f"Syncing content with blockchain: {process_content.id}", level="info") try: await process_content.sync_with_chain(session) except BaseException as e: make_log("LicenseIndex", f"Error: {e}" + '\n' + traceback.format_exc(), level="error") finally: process_content.updated = datetime.now() await session.commit() return platform_found, seqno async def main_fn(memory, ): make_log("LicenseIndex", "Service started", level="info") platform_found = True seqno = 0 while True: try: rid = __import__('uuid').uuid4().hex[:8] try: from app.core.log_context import ctx_rid ctx_rid.set(rid) except BaseException: pass make_log("LicenseIndex", f"Loop start", level="debug", rid=rid) platform_found, seqno = await license_index_loop(memory, platform_found, seqno) if platform_found: await send_status("licenses", f"working (seqno={seqno})") except BaseException as e: make_log("LicenseIndex", f"Error: {e}" + '\n' + traceback.format_exc(), level="error", rid=locals().get('rid')) await asyncio.sleep(1) seqno += 1 try: from app.core.log_context import ctx_rid ctx_rid.set(None) except BaseException: pass # if __name__ == '__main__': # loop = asyncio.get_event_loop() # loop.run_until_complete(main()) # loop.close()