from datetime import datetime, timedelta from sanic import response from sqlalchemy import select, and_, func from aiogram import Bot, types from sqlalchemy import and_ from app.core.logger import make_log from app.core.models._config import ServiceConfig from app.core.models.node_storage import StoredContent from app.core.models.keys import KnownKey from app.core.models import StarsInvoice from app.core.models.content.user_content import UserContent from app.core._config import CLIENT_TELEGRAM_API_KEY, PROJECT_HOST import json import uuid async def s_api_v1_content_list(request): offset = int(request.args.get('offset', 0)) limit = int(request.args.get('limit', 100)) assert 0 <= offset, "Invalid offset" assert 0 < limit <= 1000, "Invalid limit" store = request.args.get('store', 'local') assert store in ('local', 'onchain'), "Invalid store" stmt = ( select(StoredContent) .where( StoredContent.type.like(store + '%'), StoredContent.disabled == False ) .order_by(StoredContent.created.desc()) .offset(offset) .limit(limit) ) rows = (await request.ctx.db_session.execute(stmt)).scalars().all() make_log("Content", f"Listed {len(rows)} contents", level='info') result = {} for content in rows: content_json = content.json_format() result[content_json["cid"]] = content_json return response.json(result) async def s_api_v1_content_view(request, content_address: str): # content_address can be CID or TON address license_exist = (await request.ctx.db_session.execute( select(UserContent).where(UserContent.onchain_address == content_address) )).scalars().first() if license_exist: content_address = license_exist.content.cid.serialize_v2() from app.core.content.content_id import ContentId cid = ContentId.deserialize(content_address) r_content = (await request.ctx.db_session.execute( select(StoredContent).where(StoredContent.hash == cid.content_hash_b58) )).scalars().first() async def open_content_async(session, sc: StoredContent): if not sc.encrypted: decrypted = sc encrypted = (await session.execute(select(StoredContent).where(StoredContent.decrypted_content_id == sc.id))).scalars().first() else: encrypted = sc decrypted = (await session.execute(select(StoredContent).where(StoredContent.id == sc.decrypted_content_id))).scalars().first() assert decrypted and encrypted, "Can't open content" ctype = decrypted.json_format().get('content_type', 'application/x-binary') try: content_type = ctype.split('/')[0] except Exception: content_type = 'application' return {'encrypted_content': encrypted, 'decrypted_content': decrypted, 'content_type': content_type} content = await open_content_async(request.ctx.db_session, r_content) opts = { 'content_type': content['content_type'], # возможно с ошибками, нужно переделать на ffprobe 'content_address': content['encrypted_content'].meta.get('item_address', '') } if content['encrypted_content'].key_id: known_key = (await request.ctx.db_session.execute( select(KnownKey).where(KnownKey.id == content['encrypted_content'].key_id) )).scalars().first() if known_key: opts['key_hash'] = known_key.seed_hash # нахер не нужно на данный момент # чисто болванки, заполнение дальше opts['have_licenses'] = [] opts['invoice'] = None have_access = False if request.ctx.user: user_wallet_address = await request.ctx.user.wallet_address_async(request.ctx.db_session) have_access = ( (content['encrypted_content'].owner_address == user_wallet_address) or bool((await request.ctx.db_session.execute(select(UserContent).where( and_(UserContent.owner_address == user_wallet_address, UserContent.status == 'active', UserContent.content_id == content['encrypted_content'].id) ))).scalars().first()) \ or bool((await request.ctx.db_session.execute(select(StarsInvoice).where( and_( StarsInvoice.user_id == request.ctx.user.id, StarsInvoice.content_hash == content['encrypted_content'].hash, StarsInvoice.paid == True ) ))).scalars().first()) ) if not have_access: current_star_rate = (await ServiceConfig(request.ctx.db_session).get('live_tonPerStar', [0, 0]))[0] if current_star_rate < 0: current_star_rate = 0.00000001 stars_cost = int(int(content['encrypted_content'].meta['license']['resale']['price']) / 1e9 / current_star_rate * 1.2) if request.ctx.user.telegram_id in [5587262915, 6861699286]: stars_cost = 2 invoice_id = f"access_{uuid.uuid4().hex}" exist_invoice = (await request.ctx.db_session.execute(select(StarsInvoice).where( and_( StarsInvoice.user_id == request.ctx.user.id, StarsInvoice.created > datetime.now() - timedelta(minutes=25), StarsInvoice.amount == stars_cost, StarsInvoice.content_hash == content['encrypted_content'].hash, ) ))).scalars().first() if exist_invoice: invoice_url = exist_invoice.invoice_url else: invoice_url = None try: invoice_url = await Bot(token=CLIENT_TELEGRAM_API_KEY).create_invoice_link( 'Неограниченный доступ к контенту', 'Неограниченный доступ к контенту', invoice_id, "XTR", [ types.LabeledPrice(label='Lifetime access', amount=stars_cost), ], provider_token = '' ) request.ctx.db_session.add( StarsInvoice( external_id=invoice_id, type='access', amount=stars_cost, user_id=request.ctx.user.id, content_hash=content['encrypted_content'].hash, invoice_url=invoice_url ) ) await request.ctx.db_session.commit() except BaseException as e: make_log("Content", f"Can't create invoice link: {e}", level='warning') if invoice_url: opts['invoice'] = { 'url': invoice_url, 'amount': stars_cost, } display_options = { 'content_url': None, } if have_access: opts['have_licenses'].append('listen') converted_content = content['encrypted_content'].meta.get('converted_content') if converted_content: user_content_option = 'low_preview' if have_access: user_content_option = 'low' # TODO: подключать high если человек внезапно меломан converted_content = (await request.ctx.db_session.execute(select(StoredContent).where( StoredContent.hash == converted_content[user_content_option] ))).scalars().first() if converted_content: display_options['content_url'] = converted_content.web_url opts['content_ext'] = converted_content.filename.split('.')[-1] content_meta = content['encrypted_content'].json_format() from app.core.content.content_id import ContentId _mcid = content_meta.get('metadata_cid') or None content_metadata = None if _mcid: _cid = ContentId.deserialize(_mcid) content_metadata = (await request.ctx.db_session.execute(select(StoredContent).where(StoredContent.hash == _cid.content_hash_b58))).scalars().first() with open(content_metadata.filepath, 'r') as f: content_metadata_json = json.loads(f.read()) display_options['metadata'] = content_metadata_json opts['downloadable'] = content_metadata_json.get('downloadable', False) if opts['downloadable']: if not ('listen' in opts['have_licenses']): opts['downloadable'] = False return response.json({ **opts, 'encrypted': content['encrypted_content'].json_format(), 'display_options': display_options, }) async def s_api_v1_content_friendly_list(request): # return html table with content list. bootstrap is used result = """
| CID | Title | Onchain | Preview link |
|---|---|---|---|
| {content.cid.serialize_v2()} | {metadata.get('name', "")} | {content.meta.get('item_address')} | """ + (f'Preview' if preview_link else "not ready") + """ |