This commit is contained in:
user 2025-02-28 13:27:37 +03:00
parent 8647352d0f
commit 758defee99
7 changed files with 183 additions and 52 deletions

View File

@ -7,6 +7,7 @@ from aiogram import types, Router
from app.client_bot.routers.home import router as home_router from app.client_bot.routers.home import router as home_router
from app.client_bot.routers.tonconnect import router as tonconnect_router from app.client_bot.routers.tonconnect import router as tonconnect_router
from app.client_bot.routers.content import router as content_router from app.client_bot.routers.content import router as content_router
from app.client_bot.routers.stars import router as stars_router
from app.core.logger import logger from app.core.logger import logger
@ -14,6 +15,7 @@ main_router = Router()
main_router.include_routers(home_router) main_router.include_routers(home_router)
main_router.include_routers(tonconnect_router) main_router.include_routers(tonconnect_router)
main_router.include_routers(content_router) main_router.include_routers(content_router)
main_router.include_routers(stars_router)
closing_router = Router() closing_router = Router()

View File

@ -0,0 +1,25 @@
from aiogram import types, Router, F
from app.core.logger import make_log
from app.core.models import StarsInvoice
router = Router()
async def t_pre_checkout_query_stars_processing(pre_checkout_query: types.PreCheckoutQuery, user=None, db_session=None,
chat_wrap=None, **extra):
invoice_id = pre_checkout_query.invoice_payload
existing_invoice = db_session.query(StarsInvoice).filter(
StarsInvoice.external_id == invoice_id
).first()
if not existing_invoice:
return await pre_checkout_query.answer(ok=False, error_message="Invoice not found")
if existing_invoice.paid:
return await pre_checkout_query.answer(ok=False, error_message="Invoice already paid")
return await pre_checkout_query.answer(ok=True)
router.pre_checkout_query.register(t_pre_checkout_query_stars_processing)

View File

@ -11,7 +11,7 @@ from app.core._blockchain.ton.platform import platform
from app.core._blockchain.ton.toncenter import toncenter from app.core._blockchain.ton.toncenter import toncenter
from app.core._utils.send_status import send_status from app.core._utils.send_status import send_status
from app.core.logger import make_log from app.core.logger import make_log
from app.core.models import UserContent from app.core.models import UserContent, KnownTelegramMessage
from app.core.models.node_storage import StoredContent from app.core.models.node_storage import StoredContent
from app.core._utils.resolve_content import resolve_content from app.core._utils.resolve_content import resolve_content
from app.core.models.wallet_connection import WalletConnection from app.core.models.wallet_connection import WalletConnection
@ -212,6 +212,16 @@ async def indexer_loop(memory, platform_found: bool, seqno: int) -> [bool, int]:
}] }]
]) ])
) )
user_uploader_wrapper = Wrapped_CBotChat(memory._telegram_bot, chat_id=user.telegram_id, user=user, db_session=session)
for hint_message in session.query(KnownTelegramMessage).filter(
and_(
KnownTelegramMessage.user_id == user.id,
KnownTelegramMessage.message_type == 'hint',
KnownTelegramMessage.meta.contains({'encrypted_content_hash': encrypted_stored_content.hash})
)
):
await user_uploader_wrapper.delete_message(hint_message.message_id)
elif encrypted_stored_content.type.startswith('onchain') and encrypted_stored_content.onchain_index == item_index: elif encrypted_stored_content.type.startswith('onchain') and encrypted_stored_content.onchain_index == item_index:
encrypted_stored_content.type = "onchain/content" + ("_unknown" if (encrypted_stored_content.key_id is None) else "") encrypted_stored_content.type = "onchain/content" + ("_unknown" if (encrypted_stored_content.key_id is None) else "")
encrypted_stored_content.owner_address = item_owner_address.to_string(1, 1, 1) encrypted_stored_content.owner_address = item_owner_address.to_string(1, 1, 1)

View File

@ -20,6 +20,7 @@ from app.core.models._telegram import Wrapped_CBotChat
from app.core.storage import db_session from app.core.storage import db_session
from app.core._config import CLIENT_TELEGRAM_API_KEY from app.core._config import CLIENT_TELEGRAM_API_KEY
from app.core.models.user import User from app.core.models.user import User
from app.core.models import StarsInvoice
import os import os
import traceback import traceback
@ -27,6 +28,51 @@ import traceback
async def license_index_loop(memory, platform_found: bool, seqno: int) -> [bool, int]: async def license_index_loop(memory, platform_found: bool, seqno: int) -> [bool, int]:
make_log("LicenseIndex", "Service running", level="debug") make_log("LicenseIndex", "Service running", level="debug")
with db_session() as session: with db_session() as session:
async def check_telegram_stars_transactions():
# Проверка звездных telegram транзакций, обновление paid
offset = {'desc': 'Статичное число заранее известного количества транзакций, которое даже не знает наш бот', 'value': 1}['value'] + \
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 = session.query(StarsInvoice).filter(
StarsInvoice.external_id == star_payment.source.invoice_payload
).first()
if not existing_invoice:
continue
if star_payment.amount == existing_invoice.amount:
if not existing_invoice.paid:
existing_invoice.paid = True
session.commit()
licensed_content = session.query(StoredContent).filter(StoredContent.hash == existing_invoice.content_hash).first()
user = session.query(User).filter(User.id == existing_invoice.user_id).first()
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, добавление их в базу как неопознанные
for user in session.query(User).filter( for user in session.query(User).filter(
User.last_use > datetime.now() - timedelta(minutes=10) User.last_use > datetime.now() - timedelta(minutes=10)
).all(): ).all():
@ -47,6 +93,7 @@ async def license_index_loop(memory, platform_found: bool, seqno: int) -> [bool,
except BaseException as e: except BaseException as e:
make_log("LicenseIndex", f"Error: {e}" + '\n' + traceback.format_exc(), level="error") make_log("LicenseIndex", f"Error: {e}" + '\n' + traceback.format_exc(), level="error")
# Проверка NFT на актуальность данных, в том числе уже проверенные
process_content = session.query(UserContent).filter( process_content = session.query(UserContent).filter(
and_( and_(
UserContent.type.startswith('nft/'), UserContent.type.startswith('nft/'),

View File

@ -1,3 +1,4 @@
from sqlalchemy import and_
from app.core.models.node_storage import StoredContent from app.core.models.node_storage import StoredContent
from app.core.models.content.user_content import UserContent, UserAction from app.core.models.content.user_content import UserContent, UserAction
from app.core.logger import make_log from app.core.logger import make_log
@ -7,6 +8,9 @@ from app.core._keyboards import get_inline_keyboard
from app.core.models.messages import KnownTelegramMessage from app.core.models.messages import KnownTelegramMessage
from aiogram.types import URLInputFile, Message from aiogram.types import URLInputFile, Message
import json import json
import urllib
from app.core.models.transaction import StarsInvoice
class PlayerTemplates: class PlayerTemplates:
@ -55,8 +59,25 @@ class PlayerTemplates:
local_content.meta['cover_cid'] = cover_content.cid.serialize_v2() if cover_content else None local_content.meta['cover_cid'] = cover_content.cid.serialize_v2() if cover_content else None
local_content_cid = local_content.cid local_content_cid = local_content.cid
local_content_cid.content_type = 'audio/mpeg' local_content_url = f"{PROJECT_HOST}/api/v1.5/storage/{local_content_cid.serialize_v2()}"
local_content_url = f"{PROJECT_HOST}/api/v1/storage/{local_content_cid.serialize_v2(include_accept_type=True)}" converted_content = content.meta.get('converted_content')
if not converted_content:
r = await tg_process_template(
self, self.user.translated('p_playerContext_contentNotReady'),
message_id=message_id,
message_type='common'
)
return r
content_share_link = {
'text': self.user.translated('p_shareLinkContext').format(title=content_metadata_json.get('name', "")),
'url': f"https://t.me/{CLIENT_TELEGRAM_BOT_USERNAME}?start=C{content.cid.serialize_v2()}"
}
preview_content = db_session.query(StoredContent).filter(
StoredContent.hash == converted_content['low_preview']
).first()
local_content_preview_url = preview_content.web_url
if content_type == 'audio': if content_type == 'audio':
audio_title = content_metadata_json.get('name', "").split(' - ') audio_title = content_metadata_json.get('name', "").split(' - ')
if len(audio_title) > 1: if len(audio_title) > 1:
@ -65,15 +86,21 @@ class PlayerTemplates:
template_kwargs['title'] = audio_title[0].strip() template_kwargs['title'] = audio_title[0].strip()
template_kwargs['protect_content'] = True template_kwargs['protect_content'] = True
template_kwargs['audio'] = URLInputFile(local_content_url) template_kwargs['audio'] = URLInputFile(local_content_preview_url)
if cover_content: if cover_content:
template_kwargs['thumbnail'] = URLInputFile(cover_content.web_url) template_kwargs['thumbnail'] = URLInputFile(cover_content.web_url)
if self.bot_id == 1: if self.bot_id == 1:
inline_keyboard_array.append([{ inline_keyboard_array.append([
{
'text': self.user.translated('shareTrack_button'), 'text': self.user.translated('shareTrack_button'),
'switch_inline_query': f"C{content.cid.serialize_v2()}", 'switch_inline_query': f"C{content.cid.serialize_v2()}",
}]) },
{
'text': self.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'])}"
}
])
inline_keyboard_array.append([{ inline_keyboard_array.append([{
'text': self.user.translated('openTrackInApp_button'), 'text': self.user.translated('openTrackInApp_button'),
'url': f"https://t.me/{CLIENT_TELEGRAM_BOT_USERNAME}/content?startapp={content.cid.serialize_v2()}" 'url': f"https://t.me/{CLIENT_TELEGRAM_BOT_USERNAME}/content?startapp={content.cid.serialize_v2()}"
@ -90,7 +117,7 @@ class PlayerTemplates:
elif content_type == 'video': elif content_type == 'video':
# Processing video # Processing video
video_title = content_metadata_json.get('name', "") video_title = content_metadata_json.get('name', "")
template_kwargs['video'] = URLInputFile(local_content_url) template_kwargs['video'] = URLInputFile(local_content_preview_url)
template_kwargs['protect_content'] = True template_kwargs['protect_content'] = True
if cover_content: if cover_content:
@ -99,10 +126,16 @@ class PlayerTemplates:
if self.bot_id == 1: if self.bot_id == 1:
# Buttons for sharing and opening in app # Buttons for sharing and opening in app
inline_keyboard_array.append([{ inline_keyboard_array.append([
{
'text': self.user.translated('shareVideo_button'), 'text': self.user.translated('shareVideo_button'),
'switch_inline_query': f"C{content.cid.serialize_v2()}", 'switch_inline_query': f"C{content.cid.serialize_v2()}",
}]) },
{
'text': self.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'])}"
}
])
inline_keyboard_array.append([{ inline_keyboard_array.append([{
'text': self.user.translated('openTrackInApp_button'), 'text': self.user.translated('openTrackInApp_button'),
'url': f"https://t.me/{CLIENT_TELEGRAM_BOT_USERNAME}/content?startapp={content.cid.serialize_v2()}" 'url': f"https://t.me/{CLIENT_TELEGRAM_BOT_USERNAME}/content?startapp={content.cid.serialize_v2()}"
@ -134,14 +167,24 @@ class PlayerTemplates:
have_access = ( have_access = (
(content.owner_address == user_wallet_address) (content.owner_address == user_wallet_address)
or bool(self.db_session.query(UserContent).filter_by(owner_address=user_wallet_address, status='active', content_id=content.id).first()) or bool(self.db_session.query(UserContent).filter_by(owner_address=user_wallet_address, status='active', content_id=content.id).first())
or bool(self.db_session.query(UserContent).filter(
and_(
StarsInvoice.user_id == self.user,
StarsInvoice.content_hash == content['encrypted_content'].hash,
StarsInvoice.paid == True
) )
if not have_access: ))
)
if have_access:
full_content = self.db_session.query(StoredContent).filter_by(
hash=content.meta.get('converted_content', {}).get('low') # TODO: support high quality
).first()
if content_type == 'audio': if content_type == 'audio':
# Restrict audio to 30 seconds if user does not have access # Restrict audio to 30 seconds if user does not have access
template_kwargs['audio'] = URLInputFile(local_content_url + '?seconds_limit=30') template_kwargs['audio'] = URLInputFile(full_content.web_url)
elif content_type == 'video': elif content_type == 'video':
# Restrict video to 30 seconds if user does not have access # Restrict video to 30 seconds if user does not have access
template_kwargs['video'] = URLInputFile(local_content_url + '?seconds_limit=30') template_kwargs['video'] = URLInputFile(full_content.web_url)
make_log("TG-Player", f"Send content {content_type} ({content_encoding}) to chat {self._chat_id}. {cd_log}") make_log("TG-Player", f"Send content {content_type} ({content_encoding}) to chat {self._chat_id}. {cd_log}")
for kmsg in self.db_session.query(KnownTelegramMessage).filter_by( for kmsg in self.db_session.query(KnownTelegramMessage).filter_by(

View File

@ -21,21 +21,21 @@ msgstr ""
#: app/client_bot/routers/home.py:15 app/client_bot/routers/home.py:17 #: app/client_bot/routers/home.py:15 app/client_bot/routers/home.py:17
msgid "home_menu" msgid "home_menu"
msgstr "" msgstr ""
"Hi, <b>{name}</b>!\n" "Hi, <b>{name}</b>! 😊\n"
"\n" "\n"
"Here you can upload your content to the blockchain and manage it.\n" "Here you can upload your content to the blockchain and manage it 🚀.\n"
"\n" "\n"
"You logged as <code>{wallet_address}</code>" "You're logged in as <code>{wallet_address}</code>"
#: app/bot/routers/home.py:20 app/client_bot/routers/home.py:20 #: app/bot/routers/home.py:20 app/client_bot/routers/home.py:20
#: app/client_bot/routers/home.py:22 #: app/client_bot/routers/home.py:22
msgid "ownedContent_button" msgid "ownedContent_button"
msgstr "📊 My content" msgstr "📊 My Content"
#: app/bot/routers/home.py:24 app/client_bot/routers/home.py:24 #: app/bot/routers/home.py:24 app/client_bot/routers/home.py:24
#: app/client_bot/routers/home.py:26 #: app/client_bot/routers/home.py:26
msgid "disconnectWallet_button" msgid "disconnectWallet_button"
msgstr "🔌 Disconnect wallet" msgstr "🔌 Disconnect Wallet"
#: app/bot/routers/home.py:35 app/client_bot/routers/home.py:35 #: app/bot/routers/home.py:35 app/client_bot/routers/home.py:35
#: app/client_bot/routers/home.py:37 #: app/client_bot/routers/home.py:37
@ -43,25 +43,25 @@ msgid "connectWalletsList_menu"
msgstr "" msgstr ""
"<b>/</b> Welcome to MY [🔴]\n" "<b>/</b> Welcome to MY [🔴]\n"
"\n" "\n"
"Please select the wallet you want to connect to the bot:" "Please select the wallet you want to connect to the bot 😊:"
#: app/bot/routers/index.py:23 app/bot/routers/index.py:22 #: app/bot/routers/index.py:23 app/bot/routers/index.py:22
#: app/client_bot/routers/index.py:18 app/client_bot/routers/index.py:23 #: app/client_bot/routers/index.py:18 app/client_bot/routers/index.py:23
msgid "error_unknownCommand" msgid "error_unknownCommand"
msgstr "Unknown command, please try again or press /start" msgstr "Unknown command, please try again or press /start"
#: app/bot/routers/content.py:16 app/bot/routers/content.py:12 #: app/bot/routers/content.py:16 app/bot/routers/content.py:12
#: app/bot/routers/content.py:21 #: app/bot/routers/content.py:21
msgid "ownedContent_menu" msgid "ownedContent_menu"
msgstr "" msgstr ""
"📊 <b>My content</b>\n" "📊 <b>My Content</b>\n"
"\n" "\n"
"Here you can see the list of your content." "Here you can view the list of your content 📋."
#: app/bot/routers/content.py:21 app/bot/routers/content.py:17 #: app/bot/routers/content.py:21 app/bot/routers/content.py:17
#: app/bot/routers/content.py:47 #: app/bot/routers/content.py:47
msgid "webApp_uploadContent_button" msgid "webApp_uploadContent_button"
msgstr "📤 Upload content" msgstr "📤 Upload Content"
#: app/bot/routers/content.py:27 app/bot/routers/content.py:23 #: app/bot/routers/content.py:27 app/bot/routers/content.py:23
#: app/bot/routers/content.py:53 app/bot/routers/content.py:68 #: app/bot/routers/content.py:53 app/bot/routers/content.py:68
@ -88,7 +88,7 @@ msgstr ""
#: app/core/models/_telegram/templates/player.py:108 #: app/core/models/_telegram/templates/player.py:108
#: app/api/routes/_blockchain.py:145 #: app/api/routes/_blockchain.py:145
msgid "gotoWallet_button" msgid "gotoWallet_button"
msgstr "Open wallet" msgstr "🔓 Open Wallet"
#: app/api/routes/_blockchain.py:150 app/bot/routers/tonconnect.py:90 #: app/api/routes/_blockchain.py:150 app/bot/routers/tonconnect.py:90
#: app/core/background/indexer_service.py:141 app/bot/routers/tonconnect.py:92 #: app/core/background/indexer_service.py:141 app/bot/routers/tonconnect.py:92
@ -99,78 +99,82 @@ msgstr "◀️ Back"
#: app/bot/routers/tonconnect.py:86 app/client_bot/routers/tonconnect.py:88 #: app/bot/routers/tonconnect.py:86 app/client_bot/routers/tonconnect.py:88
msgid "tonconnectOpenWallet_button" msgid "tonconnectOpenWallet_button"
msgstr "[Connect wallet]" msgstr "[Connect Wallet]"
#: app/core/background/indexer_service.py:134 #: app/core/background/indexer_service.py:134
#: app/core/background/indexer_service.py:149 #: app/core/background/indexer_service.py:149
msgid "p_contentWasIndexed" msgid "p_contentWasIndexed"
msgstr "" msgstr ""
"🎉 <b>Your new content was uploaded successfully!</b>\n" "🎉 <b>Your new content has been uploaded successfully!</b>\n"
"\n" "\n"
"🔗 <a href=\"https://tonviewer.com/{item_address}\">{item_address}</a> on " "🔗 <a href=\"https://tonviewer.com/{item_address}\">{item_address}</a> on the blockchain\n"
"the blockchain\n"
"\n" "\n"
"Now you can manage it in <b>My content</b> section" "Now you can manage it in the <b>My Content</b> section"
#: app/core/models/_telegram/templates/player.py:74 #: app/core/models/_telegram/templates/player.py:74
#: app/client_bot/routers/content.py:129 #: app/client_bot/routers/content.py:129
msgid "shareTrack_button" msgid "shareTrack_button"
msgstr "Share track" msgstr "🎶 Share Track"
msgid "shareVideo_button" msgid "shareVideo_button"
msgstr "Share video" msgstr "🎬 Share Video"
#: app/core/models/_telegram/templates/player.py:79 #: app/core/models/_telegram/templates/player.py:79
msgid "viewTrackAsClient_button" msgid "viewTrackAsClient_button"
msgstr "Open in player" msgstr "▶️ Open in Player"
#: app/core/models/_telegram/templates/player.py:86 #: app/core/models/_telegram/templates/player.py:86
msgid "p_playerContext_unsupportedContent" msgid "p_playerContext_unsupportedContent"
msgstr "⚠️ <b>Unsupported content</b>" msgstr "⚠️ <b>Unsupported Content</b>"
#: app/core/models/_telegram/templates/player.py:111 #: app/core/models/_telegram/templates/player.py:111
msgid "p_playerContext_purchaseRequested" msgid "p_playerContext_purchaseRequested"
msgstr "Confirm the purchase in your wallet. After that, you will be able to " msgstr "💳 Please confirm the purchase in your wallet. After that, you'll be able to listen to the full version of the track.\n\n"
"listen to the full track version.\n\n" "<b>This may take a few minutes ⏳</b>"
"<b>This can take up to few minutes</b>"
#: app/core/models/_telegram/templates/player.py:114 #: app/core/models/_telegram/templates/player.py:114
msgid "buyTrackListenLicense_button" msgid "buyTrackListenLicense_button"
msgstr "Buy license ({price} TON)" msgstr "💶 Buy License ({price} TON)"
#: app/core/models/_telegram/templates/player.py:117 #: app/core/models/_telegram/templates/player.py:117
msgid "p_playerContext_preview" msgid "p_playerContext_preview"
msgstr "<i>It's a preview version of the track. To listen to the full version, " msgstr "<i>This is a preview version of the track. To listen to the full version, you need to buy a license 🎧.</i>"
"you need to buy a license.</i>"
#: app/client_bot/routers/content.py:32 #: app/client_bot/routers/content.py:32
msgid "error_contentNotFound" msgid "error_contentNotFound"
msgstr "Error: content not found" msgstr "❗ Error: Content not found"
#: app/client_bot/routers/content.py:37 #: app/client_bot/routers/content.py:37
msgid "error_contentPrice" msgid "error_contentPrice"
msgstr "Error: content price is not set" msgstr "❗ Error: Content price is not set"
#: app/client_bot/routers/content.py:133 #: app/client_bot/routers/content.py:133
msgid "viewTrack_button" msgid "viewTrack_button"
msgstr "Open in mini-app" msgstr "▶️ Open in Mini-App"
#: app/client_bot/routers/content.py:138 #: app/client_bot/routers/content.py:138
msgid "cancelPurchase_button" msgid "cancelPurchase_button"
msgstr "Cancel purchase" msgstr "❌ Cancel Purchase"
msgid "openContractPage_button" msgid "openContractPage_button"
msgstr "Open smartcontract" msgstr "🔗 Open Smart Contract"
msgid "noWalletConnected" msgid "noWalletConnected"
msgstr "No wallet connected" msgstr "No wallet connected"
msgid "openTrackInApp_button" msgid "openTrackInApp_button"
msgstr "Open in app" msgstr "📱 Open in App"
msgid "p_licenseWasBought" msgid "p_licenseWasBought"
msgstr "💶 {username} just purchased a <a href={nft_address}>license</a> for <b>{content_title}</b>. " msgstr ""
"👏 Your content is gaining more appreciation! Keep creating and inspiring! 🚀" "💶 {username} just purchased a <a href={nft_address}>license</a> for <b>{content_title}</b>. 👏\n"
"Your content is gaining popularity! Keep creating and inspiring 🚀."
msgid "p_uploadContentTxRequested" msgid "p_uploadContentTxRequested"
msgstr "Transaction for upload {title} requested. Confirm that and wait notification of transaction result. Convert content may be 20 min" msgstr "⏳ Transaction for uploading {title} has been requested. Please confirm the transaction and wait for the notification. Content conversion may take up to 20 minutes."
msgid "shareLink_button"
msgstr "🔗 Share Link"
msgid "p_shareLinkContext"
msgstr "🎉 Enjoy {title} on MY!"