199 lines
7.9 KiB
Python
199 lines
7.9 KiB
Python
from datetime import datetime, timedelta
|
||
from sanic import response
|
||
from aiogram import Bot, types
|
||
from sqlalchemy import and_
|
||
from app.core.logger import make_log
|
||
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"
|
||
|
||
content_list = request.ctx.db_session.query(StoredContent).filter(
|
||
StoredContent.type.like(store + '%'),
|
||
StoredContent.disabled == False
|
||
).order_by(StoredContent.created.desc()).offset(offset).limit(limit)
|
||
make_log("Content", f"Listed {content_list.count()} contents", level='info')
|
||
result = {}
|
||
for content in content_list.all():
|
||
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
|
||
r_content = StoredContent.from_cid(request.ctx.db_session, content_address)
|
||
content = r_content.open_content(request.ctx.db_session)
|
||
|
||
opts = {
|
||
'content_type': content['content_type'], # возможно с ошибками, нужно переделать на ffprobe
|
||
'content_address': content['encrypted_content'].meta.get('item_address', '')
|
||
}
|
||
if content['encrypted_content'].key_id:
|
||
known_key = request.ctx.db_session.query(KnownKey).filter(
|
||
KnownKey.id == content['encrypted_content'].key_id
|
||
).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 = request.ctx.user.wallet_address(request.ctx.db_session)
|
||
have_access = (
|
||
(content['encrypted_content'].owner_address == user_wallet_address)
|
||
or bool(request.ctx.db_session.query(UserContent).filter_by(owner_address=user_wallet_address, status='active',
|
||
content_id=content['encrypted_content'].id).first()) \
|
||
or bool(request.ctx.db_session.query(UserContent).filter(
|
||
and_(
|
||
StarsInvoice.user_id == request.ctx.user.id,
|
||
StarsInvoice.content_hash == content['encrypted_content'].hash,
|
||
StarsInvoice.paid == True
|
||
)
|
||
))
|
||
)
|
||
if not have_access:
|
||
stars_cost = 1 # TODO: как считать стоимость в звездах?
|
||
|
||
exist_invoice = request.ctx.db_session.query(StarsInvoice).filter(
|
||
and_(
|
||
StarsInvoice.user_id == request.ctx.user.id,
|
||
StarsInvoice.created > datetime.now() - timedelta(minutes=25),
|
||
)
|
||
).first()
|
||
if exist_invoice:
|
||
invoice_url = exist_invoice.invoice_url
|
||
else:
|
||
invoice_id = f"access_{uuid.uuid4().hex}"
|
||
try:
|
||
invoice_url = await Bot(token=CLIENT_TELEGRAM_API_KEY).create_invoice_link(
|
||
'Lifetime access to content',
|
||
'You will receive NFT with lifetime access to content',
|
||
invoice_id, "", "XTR",
|
||
[
|
||
types.LabeledPrice(label='Lifetime access', amount=stars_cost),
|
||
],
|
||
)
|
||
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
|
||
)
|
||
)
|
||
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 = request.ctx.db_session.query(StoredContent).filter(
|
||
StoredContent.hash == converted_content[user_content_option]
|
||
).first()
|
||
if converted_content:
|
||
display_options['content_url'] = converted_content.web_url
|
||
|
||
content_meta = content['encrypted_content'].json_format()
|
||
content_metadata = StoredContent.from_cid(request.ctx.db_session, content_meta.get('metadata_cid') or None)
|
||
with open(content_metadata.filepath, 'r') as f:
|
||
content_metadata_json = json.loads(f.read())
|
||
|
||
display_options['metadata'] = content_metadata_json
|
||
|
||
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 = """
|
||
<html>
|
||
<head>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||
</head>
|
||
<body>
|
||
<table class="table table-striped">
|
||
<thead>
|
||
<tr>
|
||
<th>CID</th>
|
||
<th>Title</th>
|
||
<th>Onchain</th>
|
||
<th>Preview link</th>
|
||
</tr>
|
||
</thead>
|
||
"""
|
||
for content in request.ctx.db_session.query(StoredContent).filter(
|
||
StoredContent.type == 'onchain/content'
|
||
).all():
|
||
metadata_content = StoredContent.from_cid(request.ctx.db_session, content.meta.get('metadata_cid'))
|
||
with open(metadata_content.filepath, 'r') as f:
|
||
metadata = json.loads(f.read())
|
||
|
||
preview_link = 'not ready'
|
||
if content.meta.get('converted_content'):
|
||
preview_link = f"{PROJECT_HOST}/api/v1.5/storage/{content.meta['converted_content']['low_preview']}"
|
||
|
||
result += f"""
|
||
<tr>
|
||
<td>{content.cid}</td>
|
||
<td>{metadata['title']}</td>
|
||
<td>{content.meta.get('item_address')}</td>
|
||
<td><a href="{preview_link}">Preview</a></td>
|
||
</tr>
|
||
"""
|
||
result += """
|
||
</table>
|
||
</body>
|
||
</html>
|
||
"""
|
||
return response.html(result)
|
||
|
||
|
||
|
||
|
||
|