import os import sys import traceback from aiogram import types, Router, F from app.core._keyboards import get_inline_keyboard from app.core.models.node_storage import StoredContent import json from app.core.logger import make_log from app.core.models.content.user_content import UserAction from app.client_bot.routers.home import router as home_router from app.client_bot.routers.tonconnect import router as tonconnect_router from app.core._config import CLIENT_TELEGRAM_BOT_USERNAME, PROJECT_HOST from app.core.logger import logger from app.core.content.content_id import ContentId import base58 from datetime import datetime, timedelta import asyncio import urllib from app.core._blockchain.ton.connect import TonConnect, wallet_obj_by_name router = Router() CACHE_CHAT_ID = -1002390124789 async def t_callback_purchase_node_content(query: types.CallbackQuery, memory=None, user=None, db_session=None, chat_wrap=None, **extra): content_oid = int(query.data.split('_')[1]) is_cancel_request = query.data.split('_')[2] == 'cancel' if len(query.data.split('_')) > 2 else False content = db_session.query(StoredContent).filter_by(id=content_oid).first() if not content: return await query.answer(user.translated('error_contentNotFound'), show_alert=True) if not is_cancel_request: make_log("OwnedContent", f"{user} Try to purchase content: {content_oid}", level='info') license_price = content.meta.get('license', {}).get('listen', {}).get('price') license_price_num = int(license_price) if license_price_num < 1: return await query.answer(user.translated('error_contentPrice'), show_alert=True) ton_connect, ton_connection = TonConnect.by_user(db_session, user, callback_fn=()) await ton_connect.restore_connection() assert ton_connect.connected, "No connected wallet" user_wallet_address = user.wallet_address(db_session) memory._app.add_task(ton_connect._sdk_client.send_transaction({ 'valid_until': int(datetime.now().timestamp() + 300), 'messages': [ { 'address': content.meta['item_address'], 'amount': license_price } ] })) new_action = UserAction( type='purchase', user_id=user.id, content_id=content.id, telegram_message_id=query.message.message_id, from_address=user_wallet_address, to_address=content.meta['item_address'], status='requested', meta={ 'confirmation_url': wallet_obj_by_name(ton_connection.wallet_key.split('==')[0])['universal_url'] }, created=datetime.now() ) db_session.add(new_action) else: make_log("OwnedContent", f"{user} Try to cancel purchase: {content_oid}", level='info') action = db_session.query(UserAction).filter_by( type='purchase', content_id=content_oid, user_id=user.id, status='requested' ).first() if not action: return await query.answer() action.status = 'canceled' db_session.commit() await chat_wrap.send_content(db_session, content, message_id=query.message.message_id) router.callback_query.register(t_callback_purchase_node_content, F.data.startswith('PC_')) async def t_inline_query_node_content(query: types.InlineQuery, memory=None, user=None, db_session=None, chat_wrap=None, **extra): make_log("OwnedContent", f"Inline query: {query.query}", level='info') try: args = query.query[1:] cid = ContentId.deserialize(args) content_list = [] content = db_session.query(StoredContent).filter_by(hash=cid.content_hash_b58).first() content_prod = content.open_content(db_session) # Get both encrypted and decrypted content objects encrypted_content = content_prod['encrypted_content'] decrypted_content = content_prod['decrypted_content'] decrypted_content_meta = decrypted_content.json_format() try: metadata_content = StoredContent.from_cid(db_session, content.json_format()['metadata_cid']) with open(metadata_content.filepath, 'r') as f: metadata_content_json = json.loads(f.read()) except BaseException as e: make_log("OwnedContent", f"Can't get metadata content: {e}", level='warning') return await query.answer(content_list, cache_time=1) audio_title = metadata_content_json.get('name', "").split(' - ') title, performer = None, None if len(audio_title) > 1: performer = audio_title[0].strip() audio_title = audio_title[1:] title = audio_title[0].strip() result_kwargs = {} try: cover_content = StoredContent.from_cid(db_session, decrypted_content_meta.get('cover_cid') or None) except BaseException as e: cover_content = None if cover_content: result_kwargs['thumb_url'] = cover_content.web_url content_type_declared = decrypted_content_meta.get('content_type', 'application/x-binary').split('/')[0] hashtags_str = (' '.join(f"#{_h}" for _h in metadata_content_json.get('hashtags', []))).strip() if hashtags_str: hashtags_str = hashtags_str + '\n' # Upload preview of decrypted content (limit to 30 seconds) if content_type_declared in ('audio', 'video'): if not decrypted_content.meta.get('telegram_file_cache_preview'): try: # Construct URL for trimmed preview using decrypted content preview_content = db_session.query(StoredContent).filter_by( hash=content.meta.get('converted_content', {}).get('low_preview') ).first() preview_url = preview_content.web_url if content_type_declared == 'video': preview_message = await query.bot.send_video( chat_id=CACHE_CHAT_ID, # Cache chat id defined in configuration video=types.URLInputFile(preview_url), # Upload video using URL caption="Preview upload", # English caption supports_streaming=True ) preview_file_id = preview_message.video.file_id else: preview_message = await query.bot.send_audio( chat_id=CACHE_CHAT_ID, # Cache chat id defined in configuration audio=types.URLInputFile(preview_url), # Upload audio using URL caption="Preview upload", # English caption title=title, performer=performer, thumbnail=result_kwargs.get('thumb_url'), ) preview_file_id = preview_message.audio.file_id # Save the preview file_id in decrypted_content.meta for future use decrypted_content.meta = { **decrypted_content.meta, 'telegram_file_cache_preview': preview_file_id } db_session.commit() except Exception as e: make_log("OwnedContent", f"Error uploading preview {content_type_declared}: {e}", level='error') content_share_link = { 'text': user.translated('p_shareLinkContext').format(title=' – '.join(audio_title)), 'url': f"https://t.me/{CLIENT_TELEGRAM_BOT_USERNAME}/content?startapp={content.cid.serialize_v2()}" } # Create inline query result using decrypted content's file_id if content_type_declared == 'audio': content_list.append( types.InlineQueryResultCachedAudio( id=f"NC_{content.id}_{int(datetime.now().timestamp() // 60)}", audio_file_id=decrypted_content.meta['telegram_file_cache_preview'], caption=hashtags_str + user.translated('p_playerContext_preview'), parse_mode='html', reply_markup=get_inline_keyboard([ [ { 'text': { 'audio': user.translated('shareTrack_button'), 'video': user.translated('shareVideo_button'), }[content_type_declared], 'switch_inline_query': f"C{content.cid.serialize_v2()}" }, { 'text': user.translated('shareLink_button'), 'url': f"https://t.me/share/url?text={urllib.parse.quote(content_share_link['text'])}&url={urllib.parse.quote(content_share_link['url'])}" } ], [{ 'text': user.translated('viewTrack_button'), 'url': f"https://t.me/MY_Web3Bot/content?startapp={content.cid.serialize_v2()}" }] ]), **result_kwargs ) ) elif content_type_declared == 'video': content_list.append( types.InlineQueryResultCachedVideo( id=f"NC_{content.id}_{int(datetime.now().timestamp() // 60)}", video_file_id=decrypted_content.meta['telegram_file_cache_preview'], title=title, caption=hashtags_str + user.translated('p_playerContext_preview'), parse_mode='html', reply_markup=get_inline_keyboard([ [ { 'text': user.translated('shareVideo_button'), 'switch_inline_query': f"C{content.cid.serialize_v2()}" }, { 'text': user.translated('shareLink_button'), 'url': f"https://t.me/share/url?text={urllib.parse.quote(content_share_link['text'])}&url={urllib.parse.quote(content_share_link['url'])}" } ], [{ 'text': user.translated('viewTrack_button'), 'url': f"https://t.me/MY_Web3Bot/content?startapp={content.cid.serialize_v2()}" }] ]), **result_kwargs ) ) return await query.answer(content_list, cache_time=1) except BaseException as e: logger.error(f"Error in t_inline_query_node_content: {e}") traceback.print_exc() return await query.answer([], cache_time=1) async def t_chosen_inline_result_node_content(query: types.ChosenInlineResult, memory=None, user=None, db_session=None, chat_wrap=None, **extra): make_log("OwnedContent", f"Chosen inline result: {query.result_id}", level='info') # return await query.answer([]) router.inline_query.register(t_inline_query_node_content, F.query.startswith('C')) router.chosen_inline_result.register(t_chosen_inline_result_node_content, lambda: True)