big update
This commit is contained in:
1 parent
7d05a3f2c0
commit
f45d593a84
32 files changed
+726
-320
No files matched your search
@@ -1,4 +1,3 @@
|
||||
from app.core.models.asset import Asset
|
||||
from app.core.models.base import AlchemyBase
|
||||
from app.core.models.keys import KnownKey
|
||||
from app.core.models.memory import Memory
|
||||
@@ -9,3 +8,5 @@ from app.core.models.wallet_connection import WalletConnection
|
||||
from app.core.models.messages import KnownTelegramMessage
|
||||
from app.core.models.user_activity import UserActivity
|
||||
from app.core.models.content.user_content import UserContent
|
||||
from app.core.models._config import ServiceConfigValue
|
||||
from app.core.models.asset import Asset
|
||||
+26
-52
@@ -1,64 +1,38 @@
|
||||
import string
|
||||
from json import dumps as json_dumps
|
||||
from json import loads as json_loads
|
||||
from random import choice
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from app.core.logger import make_log
|
||||
from app.core.models.base import AlchemyBase
|
||||
from sqlalchemy import Column, BigInteger, Integer, String, ForeignKey, DateTime, JSON, Boolean
|
||||
|
||||
|
||||
class ConfigFile:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
self.values
|
||||
class ServiceConfigValue(AlchemyBase):
|
||||
__tablename__ = 'service_config'
|
||||
|
||||
id = Column(Integer, autoincrement=True, primary_key=True)
|
||||
key = Column(String(128), nullable=False, unique=True)
|
||||
packed_value = Column(JSON, nullable=False, default={})
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
with open(self.filepath, 'r') as file:
|
||||
return json_loads(file.read())
|
||||
def value(self):
|
||||
return self.packed_value['value']
|
||||
|
||||
assert isinstance(self.values, dict)
|
||||
|
||||
class ServiceConfig:
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.values.get(key, default)
|
||||
result = self.session.query(ServiceConfigValue).filter(ServiceConfigValue.key == key).first()
|
||||
return (result.value if result else None) or default
|
||||
|
||||
def set(self, key, value):
|
||||
random_part = choice(string.ascii_lowercase)
|
||||
app_cached_values = self.values
|
||||
app_cached_values[key] = value
|
||||
with open(f"{'/'.join(self.filepath.split('/')[:-1])}/.backup_{self.filepath.split('/')[-1]}_{random_part}", 'w') as file:
|
||||
file.write(
|
||||
json_dumps(
|
||||
app_cached_values,
|
||||
indent=4,
|
||||
sort_keys=True
|
||||
)
|
||||
)
|
||||
|
||||
with open(self.filepath, 'w') as file:
|
||||
file.write(
|
||||
json_dumps(
|
||||
app_cached_values,
|
||||
indent=4,
|
||||
sort_keys=True
|
||||
)
|
||||
)
|
||||
|
||||
make_log("ConfigFile", f"Edited {key}", level="debug")
|
||||
p1 = Popen(["md5sum", self.filepath], stdout=PIPE, stderr=PIPE)
|
||||
p1.wait()
|
||||
out1, err1 = p1.communicate()
|
||||
p2 = Popen(["md5sum", f"{'/'.join(self.filepath.split('/')[:-1])}/.backup_{self.filepath.split('/')[-1]}_{random_part}"], stdout=PIPE, stderr=PIPE)
|
||||
p2.wait()
|
||||
out2, err2 = p2.communicate()
|
||||
if err1 or err2:
|
||||
make_log("ConfigFile", f"Error when editing {key} (check md5sum): {err1} {err2}", level="error")
|
||||
return self.set(key, value)
|
||||
|
||||
fingerprint1 = out1.split()[0].strip()
|
||||
fingerprint2 = out2.split()[0].strip()
|
||||
|
||||
if fingerprint1 != fingerprint2:
|
||||
make_log("ConfigFile", f"Error when editing {key} (check md5sum): {fingerprint1} {fingerprint2}", level="error")
|
||||
config_value = self.session.query(ServiceConfigValue).filter(
|
||||
ServiceConfigValue.key == key
|
||||
).first()
|
||||
if not config_value:
|
||||
config_value = ServiceConfigValue(key=key)
|
||||
self.session.add(config_value)
|
||||
self.session.commit()
|
||||
return self.set(key, value)
|
||||
|
||||
config_value.packed_value = {'value': value}
|
||||
self.session.commit()
|
||||
return
|
||||
@@ -0,0 +1,70 @@
|
||||
from app.core.models.node_storage import StoredContent
|
||||
from app.core.models.content.user_content import UserContent
|
||||
from app.core.logger import make_log
|
||||
from app.core._utils.tg_process_template import tg_process_template
|
||||
from app.core._config import PROJECT_HOST
|
||||
from app.core._keyboards import get_inline_keyboard
|
||||
|
||||
|
||||
class PlayerTemplates:
|
||||
async def send_content(self, content: StoredContent, extra_buttons=None, message_id=None):
|
||||
assert content.type.startswith('onchain/content'), "Invalid nodeStorage content type"
|
||||
inline_keyboard_array = []
|
||||
cd_log = f"Content (SHA256: {content.hash}), Encrypted: {content.encrypted}, TelegramCID: {content.telegram_cid}. "
|
||||
if not content.encrypted:
|
||||
local_content = content
|
||||
else:
|
||||
local_content = content.decrypted_content
|
||||
# TODO: add check decrypted_content by .format_json()['content_cid']
|
||||
if local_content:
|
||||
cd_log += f"Decrypted: {local_content.hash}. "
|
||||
else:
|
||||
cd_log += "Can't decrypt content. "
|
||||
|
||||
if local_content:
|
||||
content_meta = content.json_format()
|
||||
local_content_meta = local_content.json_format()
|
||||
try:
|
||||
content_type, content_encoding = local_content_meta["content_type"].split('/')
|
||||
except:
|
||||
content_type, content_encoding = 'application', 'x-binary'
|
||||
|
||||
try:
|
||||
cover_content = StoredContent.from_cid(self.db_session, content_meta.get('cover_cid') or None)
|
||||
except BaseException as e:
|
||||
cd_log += f"Can't get cover content: {e}. "
|
||||
cover_content = None
|
||||
|
||||
local_content_cid = local_content.cid
|
||||
local_content_cid.content_type = 'audio/mpeg'
|
||||
local_content_url = f"{PROJECT_HOST}/api/v1/storage/{local_content_cid.serialize_v2(include_accept_type=True)}"
|
||||
|
||||
template_kwargs = {}
|
||||
if content_type[0] == 'audio':
|
||||
template_kwargs['title'] = 'title'
|
||||
template_kwargs['performer'] = 'performer'
|
||||
template_kwargs['protect_content'] = True
|
||||
template_kwargs['audio'] = local_content_url
|
||||
if cover_content:
|
||||
template_kwargs['thumbnail'] = cover_content.web_url
|
||||
|
||||
|
||||
else:
|
||||
local_content = None
|
||||
|
||||
if not local_content:
|
||||
text = self.user.translated('p_playerContext_unsupportedContent').format(
|
||||
content_type=content_type,
|
||||
content_encoding=content_encoding
|
||||
)
|
||||
inline_keyboard_array = []
|
||||
extra_buttons = []
|
||||
|
||||
make_log("TG-Player", f"Send content {content_type} ({content_encoding}) to chat {self._chat_id}. {cd_log}")
|
||||
return await tg_process_template(
|
||||
self, text, message_id=message_id, **template_kwargs,
|
||||
keyboard=get_inline_keyboard([*inline_keyboard_array, *extra_buttons]) if inline_keyboard_array else None,
|
||||
message_type=f'content/{content_type}', message_meta={'content_sha256': content_meta['hash']}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,10 +3,16 @@ from datetime import datetime, timedelta
|
||||
|
||||
from app.core.logger import make_log
|
||||
from app.core.models.messages import KnownTelegramMessage
|
||||
from app.core._config import TELEGRAM_API_KEY, CLIENT_TELEGRAM_API_KEY
|
||||
|
||||
from app.core.models._telegram.templates.player import PlayerTemplates
|
||||
|
||||
|
||||
class Wrapped_CBotChat:
|
||||
def __init__(self, api_key: str, chat_id: int = None, db_session=None, **kwargs):
|
||||
class T: pass
|
||||
|
||||
|
||||
class Wrapped_CBotChat(T, PlayerTemplates):
|
||||
def __init__(self, api_key: str, chat_id: int = None, db_session=None, user=None, **kwargs):
|
||||
if isinstance(api_key, Bot):
|
||||
self._bot_key = api_key.token
|
||||
self._bot = api_key
|
||||
@@ -18,6 +24,7 @@ class Wrapped_CBotChat:
|
||||
|
||||
self._chat_id = chat_id
|
||||
self.db_session = db_session
|
||||
self.user = user
|
||||
self.options = kwargs
|
||||
|
||||
def __repr__(self):
|
||||
@@ -26,13 +33,21 @@ class Wrapped_CBotChat:
|
||||
|
||||
return "Bot"
|
||||
|
||||
async def return_result(self, result, message_type='common'):
|
||||
@property
|
||||
def bot_id(self):
|
||||
return {
|
||||
TELEGRAM_API_KEY: 0,
|
||||
CLIENT_TELEGRAM_API_KEY: 1
|
||||
}[self._bot_key]
|
||||
|
||||
async def return_result(self, result, message_type='common', message_meta={}):
|
||||
if self.db_session:
|
||||
if message_type == 'common':
|
||||
ci = 0
|
||||
for oc_msg in self.db_session.query(KnownTelegramMessage).filter(
|
||||
KnownTelegramMessage.type == 'common',
|
||||
KnownTelegramMessage.chat_id == self._chat_id,
|
||||
KnownTelegramMessage.deleted == False
|
||||
).all():
|
||||
await self.delete_message(oc_msg.message_id)
|
||||
ci += 1
|
||||
@@ -45,9 +60,12 @@ class Wrapped_CBotChat:
|
||||
self.db_session.add(
|
||||
KnownTelegramMessage(
|
||||
type=message_type,
|
||||
bot_id=self.bot_id,
|
||||
chat_id=self._chat_id,
|
||||
message_id=message_id,
|
||||
created=datetime.now()
|
||||
from_user=False,
|
||||
created=datetime.now(),
|
||||
meta=message_meta or {}
|
||||
)
|
||||
)
|
||||
self.db_session.commit()
|
||||
@@ -56,7 +74,7 @@ class Wrapped_CBotChat:
|
||||
|
||||
return result
|
||||
|
||||
async def send_message(self, text: str, message_type='common', **kwargs):
|
||||
async def send_message(self, text: str, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Send message to {self._chat_id}. Text len: {len(text)}", level='debug')
|
||||
@@ -67,7 +85,7 @@ class Wrapped_CBotChat:
|
||||
disable_web_page_preview=True,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except BaseException as e:
|
||||
make_log(self, f"Error sending message to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
@@ -104,7 +122,7 @@ class Wrapped_CBotChat:
|
||||
make_log(self, f"Error deleting message {self._chat_id}/{message_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
|
||||
async def send_photo(self, file_id, message_type='common', **kwargs):
|
||||
async def send_photo(self, file_id, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Send photo to {self._chat_id}. File: {file_id}", level='debug')
|
||||
@@ -113,12 +131,12 @@ class Wrapped_CBotChat:
|
||||
file_id,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except Exception as e:
|
||||
make_log(self, f"Error sending photo to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
|
||||
async def send_document(self, file_id, message_type='common', **kwargs):
|
||||
async def send_document(self, file_id, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Send document to {self._chat_id}. File: {file_id}", level='debug')
|
||||
@@ -127,12 +145,12 @@ class Wrapped_CBotChat:
|
||||
file_id,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except Exception as e:
|
||||
make_log(self, f"Error sending document to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
|
||||
async def send_video(self, file_id, message_type='common', **kwargs):
|
||||
async def send_video(self, file_id, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Send video to {self._chat_id}. File: {file_id}", level='debug')
|
||||
@@ -141,12 +159,26 @@ class Wrapped_CBotChat:
|
||||
file_id,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except Exception as e:
|
||||
make_log(self, f"Error sending video to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
|
||||
async def copy_message(self, from_chat_id, message_id, message_type='common', **kwargs):
|
||||
async def send_audio(self, file_id, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Send audio to {self._chat_id}. File: {file_id}", level='debug')
|
||||
r = await self._bot.send_audio(
|
||||
self._chat_id,
|
||||
file_id,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except Exception as e:
|
||||
make_log(self, f"Error sending audio to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
|
||||
async def copy_message(self, from_chat_id, message_id, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Copy message from {from_chat_id}/{message_id} to {self._chat_id}", level='debug')
|
||||
@@ -156,12 +188,12 @@ class Wrapped_CBotChat:
|
||||
message_id,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except Exception as e:
|
||||
make_log(self, f"Error copying message from {from_chat_id}/{message_id} to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
|
||||
async def forward_message(self, from_chat_id, message_id, message_type='common', **kwargs):
|
||||
async def forward_message(self, from_chat_id, message_id, message_type='common', message_meta={}, **kwargs):
|
||||
assert self._chat_id, "No chat_id"
|
||||
try:
|
||||
make_log(self, f"Forward message from {from_chat_id}/{message_id} to {self._chat_id}", level='debug')
|
||||
@@ -171,7 +203,7 @@ class Wrapped_CBotChat:
|
||||
message_id,
|
||||
**kwargs
|
||||
)
|
||||
return await self.return_result(r, message_type=message_type)
|
||||
return await self.return_result(r, message_type=message_type, message_meta=message_meta)
|
||||
except Exception as e:
|
||||
make_log(self, f"Error forwarding message from {from_chat_id}/{message_id} to {self._chat_id}. Error: {e}", level='warning')
|
||||
return None
|
||||
@@ -0,0 +1,44 @@
|
||||
from app.core.models.wallet_connection import WalletConnection
|
||||
from app.core._blockchain.ton.toncenter import toncenter
|
||||
from tonsdk.boc import Cell
|
||||
from base64 import b64decode
|
||||
|
||||
|
||||
def unpack_item_indexator_data(item_get_data_result):
|
||||
result = {}
|
||||
|
||||
assert item_get_data_result['stack'][0][0] == 'num', "Type is not a number"
|
||||
result['type'] = int(item_get_data_result['stack'][0][1], 16)
|
||||
|
||||
result['address'] = Cell.one_from_boc(
|
||||
b64decode(item_get_data_result['stack'][1][1]['bytes'])).begin_parse().read_msg_addr().to_string(1, 1, 1)
|
||||
|
||||
assert item_get_data_result['stack'][2][0] == 'num', "Index is not a number"
|
||||
result['index'] = int(item_get_data_result['stack'][2][1], 16)
|
||||
|
||||
result['platform_address'] = Cell.one_from_boc(
|
||||
b64decode(item_get_data_result['stack'][3][1]['bytes'])).begin_parse().read_msg_addr().to_string(1, 1, 1)
|
||||
|
||||
assert item_get_data_result['stack'][4][0] == 'num', "License type is not a number"
|
||||
result['license_type'] = int(item_get_data_result['stack'][4][1], 16)
|
||||
|
||||
result['owner_address'] = Cell.one_from_boc(
|
||||
b64decode(item_get_data_result['stack'][5][1]["bytes"])).begin_parse().read_msg_addr().to_string(1, 1, 1)
|
||||
result['values'] = Cell.one_from_boc(b64decode(item_get_data_result['stack'][6][1]['bytes']))
|
||||
result['derivates'] = Cell.one_from_boc(b64decode(item_get_data_result['stack'][7][1]['bytes']))
|
||||
result['platform_variables'] = Cell.one_from_boc(b64decode(item_get_data_result['stack'][8][1]['bytes']))
|
||||
result['distribution'] = Cell.one_from_boc(b64decode(item_get_data_result['stack'][9][1]['bytes']))
|
||||
return result
|
||||
|
||||
|
||||
class NodeStorageIndexationMixin:
|
||||
pass # async def fetch_onchain_metadata(self):
|
||||
|
||||
|
||||
|
||||
class UserContentIndexationMixin:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
from sqlalchemy import Column, BigInteger, Integer, String, ForeignKey, DateTime, JSON, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.core.models.base import AlchemyBase
|
||||
from app.core.models.content.indexation_mixins import UserContentIndexationMixin
|
||||
|
||||
|
||||
class UserContent(AlchemyBase):
|
||||
class UserContent(AlchemyBase, UserContentIndexationMixin):
|
||||
__tablename__ = 'users_content'
|
||||
|
||||
id = Column(Integer, autoincrement=True, primary_key=True)
|
||||
type = Column(String(128), nullable=False) # 'license_issuer', 'license_listen'
|
||||
type = Column(String(128), nullable=False) # 'license/issuer', 'license/listen', 'nft/unknown'
|
||||
onchain_address = Column(String(1024), nullable=True) # bind by this
|
||||
owner_address = Column(String(1024), nullable=True)
|
||||
code_hash = Column(String(128), nullable=True)
|
||||
data_hash = Column(String(128), nullable=True)
|
||||
updated = Column(DateTime, nullable=False, default=0)
|
||||
|
||||
content_id = Column(Integer, ForeignKey('node_storage.id'), nullable=True)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from base58 import b58decode
|
||||
from sqlalchemy import Column, Integer, String, DateTime, JSON, BigInteger
|
||||
from sqlalchemy import Column, Integer, String, DateTime, JSON, BigInteger, Boolean
|
||||
|
||||
from .base import AlchemyBase
|
||||
|
||||
@@ -12,6 +12,11 @@ class KnownTelegramMessage(AlchemyBase):
|
||||
|
||||
id = Column(Integer, autoincrement=True, primary_key=True)
|
||||
type = Column(String(64), nullable=True)
|
||||
bot_id = Column(Integer, nullable=False, default=1) # 0 – uploader, 1 – client
|
||||
chat_id = Column(BigInteger, nullable=False)
|
||||
message_id = Column(BigInteger, nullable=False)
|
||||
from_user = Column(Boolean, nullable=False)
|
||||
from_telegram_id = Column(BigInteger, nullable=True)
|
||||
created = Column(DateTime, nullable=False, default=0)
|
||||
deleted = Column(Boolean, nullable=True, default=False)
|
||||
meta = Column(JSON, nullable=False, default={})
|
||||
@@ -4,11 +4,14 @@ from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.logger import make_log
|
||||
from app.core._config import UPLOADS_DIR, PROJECT_HOST
|
||||
import os
|
||||
from app.core.content.content_id import ContentId
|
||||
from app.core.models.content.indexation_mixins import NodeStorageIndexationMixin
|
||||
from .base import AlchemyBase
|
||||
|
||||
|
||||
class StoredContent(AlchemyBase):
|
||||
class StoredContent(AlchemyBase, NodeStorageIndexationMixin):
|
||||
__tablename__ = 'node_storage'
|
||||
|
||||
id = Column(Integer, autoincrement=True, primary_key=True)
|
||||
@@ -50,6 +53,14 @@ class StoredContent(AlchemyBase):
|
||||
accept_type=self.meta.get('content_type', 'image/jpeg')
|
||||
)
|
||||
|
||||
@property
|
||||
def filepath(self) -> str:
|
||||
return os.path.join(UPLOADS_DIR, file_hash)
|
||||
|
||||
@property
|
||||
def web_url(self) -> str:
|
||||
return f"{PROJECT_HOST}/api/v1/storage/{self.cid.serialize_v2()}"
|
||||
|
||||
@property
|
||||
def decrypt_possible(self) -> bool:
|
||||
if self.encrypted is False:
|
||||
@@ -80,7 +91,20 @@ class StoredContent(AlchemyBase):
|
||||
**extra_fields,
|
||||
"hash": self.hash,
|
||||
"cid": self.cid.serialize_v2(),
|
||||
"content_type": self.meta.get('content_type', 'application/x-binary'),
|
||||
"status": self.status,
|
||||
"updated": self.updated.isoformat() if isinstance(self.updated, datetime) else (make_log("Content.json_format", f"Invalid Content.updated: {self.updated} ({type(self.updated)})", level="error") or None),
|
||||
"created": self.created.isoformat() if isinstance(self.created, datetime) else (make_log("Content.json_format", f"Invalid Content.created: {self.created} ({type(self.created)})", level="error") or None),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_cid(cls, db_session, content_id):
|
||||
if isinstance(content_id, str):
|
||||
cid = ContentId.deserialize(content_id)
|
||||
else:
|
||||
cid = content_id
|
||||
|
||||
content = db_session.query(StoredContent).filter(StoredContent.hash == cid.content_hash_b58).first()
|
||||
assert content, "Content not found"
|
||||
return content
|
||||
|
||||
@@ -3,11 +3,12 @@ from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.auth_v1 import AuthenticationMixin as AuthenticationMixin_V1
|
||||
from app.core.models.user.display_mixin import DisplayMixin
|
||||
from app.core.models.user.wallet_mixin import WalletMixin
|
||||
from app.core.translation import TranslationCore
|
||||
from ..base import AlchemyBase
|
||||
|
||||
|
||||
class User(AlchemyBase, DisplayMixin, TranslationCore, AuthenticationMixin_V1):
|
||||
class User(AlchemyBase, DisplayMixin, TranslationCore, AuthenticationMixin_V1, WalletMixin):
|
||||
LOCALE_DOMAIN = 'sanic_telegram_bot'
|
||||
|
||||
__tablename__ = 'users'
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
from app.core.models.content.user_content import UserContent
|
||||
from app.core.models.wallet_connection import WalletConnection
|
||||
from app.core._blockchain.ton.toncenter import toncenter
|
||||
from tonsdk.utils import Address
|
||||
from datetime import datetime, timedelta
|
||||
from app.core.logger import make_log
|
||||
|
||||
|
||||
class WalletMixin:
|
||||
def wallet_connection(self, db_session):
|
||||
return db_session.query(WalletConnection).filter(
|
||||
WalletConnection.user_id == self.id,
|
||||
WalletConnection.invalidated == False
|
||||
).order_by(WalletConnection.created.desc()).first()
|
||||
|
||||
def wallet_address(self, db_session):
|
||||
wallet_connection = self.wallet_connection(db_session)
|
||||
return wallet_connection.wallet_address if wallet_connection else None
|
||||
|
||||
async def scan_owned_user_content(self, db_session):
|
||||
page_id = -1
|
||||
page_size = 100
|
||||
have_next_page = True
|
||||
while have_next_page:
|
||||
page_id += 1
|
||||
nfts_list = await toncenter.get_nft_items(limit=100, offset=page_id * page_size)
|
||||
if len(nfts_list) >= page_size:
|
||||
have_next_page = True
|
||||
|
||||
for nft_item in nfts_list:
|
||||
item_address = Address(nft_item['address']).to_string(1, 1, 1)
|
||||
owner_address = Address(nft_item['owner_address']).to_string(1, 1, 1)
|
||||
|
||||
user_content = db_session.query(UserContent).filter(
|
||||
UserContent.onchain_address == item_address
|
||||
).first()
|
||||
if user_content:
|
||||
continue
|
||||
|
||||
try:
|
||||
nft_content = nft_item['content']['uri']
|
||||
except KeyError:
|
||||
nft_content = None
|
||||
|
||||
user_content = UserContent(
|
||||
type='nft/unknown',
|
||||
onchain_address=item_address,
|
||||
owner_address=owner_address,
|
||||
code_hash=nft_item['code_hash'],
|
||||
data_hash=nft_item['data_hash'],
|
||||
updated=datetime.fromtimestamp(0),
|
||||
content_id=None, # not resolved yet
|
||||
created=datetime.now(),
|
||||
meta={
|
||||
'metadata_uri': nft_content,
|
||||
},
|
||||
user_id=self.id,
|
||||
wallet_connection_id=self.wallet_connection(db_session).id,
|
||||
status="active"
|
||||
)
|
||||
db_session.add(user_content)
|
||||
db_session.commit()
|
||||
|
||||
make_log(self, f"New onchain NFT found: {item_address}", level='info')
|
||||
|
||||
async def get_user_content(self, db_session, limit=100, offset=0):
|
||||
try:
|
||||
await self.scan_owned_user_content(db_session)
|
||||
except BaseException as e:
|
||||
make_log(self, f"Error while scanning user content: {e}", level='error')
|
||||
|
||||
return self.db_session.query(UserContent).filter(
|
||||
UserContent.user_id == self.id
|
||||
).offset(offset).limit(limit).all()
|
||||
Reference in new issue
Block a user