uploader-bot/app/core/background/license_service.py

181 lines
9.4 KiB
Python

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()