1
0
notamon/python/notamon_viewer/app.py

196 lines
5.0 KiB
Python

import flask
import pbc_client
import base64
import dataclasses
import requests_cache
from collections.abc import Iterator
import logging
HTML_INDEX = """
<!doctype html>
<html>
<head>
<style>
html {
background-color: #222;
color: white;
font-family: sans;
}
body {
width: 80%;
margin: auto;
}
.notamon-grid {
display: grid;
grid-template-columns: repeat(10, 1fr);
justify-items: center;
text-align: center;
gap: 4em 1em;
}
.notamon h2, h3 {
margin: 0;
}
</style>
<title>Hello from Flask</title>
</head>
<body>
<div class="notamon-grid">
{% for notamon in notamons %}
<div class="notamon">
<img src="{{ notamon.image_src }}" style="{{ notamon.effect_css }}">
<h2>{{ notamon.nickname }}</h2>
<h3>{{ notamon.species_name }}</h3>
</div>
{% endfor %}
</div>
</body>
</html>
"""
@dataclasses.dataclass(frozen=True)
class Notamon:
image_src: str
effect_css: str
nickname: str
species_name: str
@dataclasses.dataclass(frozen=True)
class NotamonNFTData:
nickname: str
owner: str
species_id: int
skin_id: int
effect_id: int
stat_hp: int
stat_attack: int
stat_defense: int
@dataclasses.dataclass(frozen=True)
class AssetVariable:
variable_id: int
asset_id: int
opened: bytes | None
app = flask.Flask(__name__)
TEST_NOTAMON = Notamon(
image_src = 'https://img.pokemondb.net/sprites/ruby-sapphire/normal/mudkip.png',
effect_css = '',
nickname = 'Dude',
species_name = 'Mudkip',
)
ADDRESS_NFTS = '02b82d36784146bd29415dbaa623a5f1cf6e6e1108'
ADDRESS_ASSETS = '036080dc8a15e496e5b970f65112b4fe4ac022e62c'
ASSET_ID_OFFSET_SPECIES = 0x0100_0000
ASSET_ID_OFFSET_SKIN = 0x0200_0000
ASSET_ID_OFFSET_EFFECT = 0x0300_0000
SESSION = requests_cache.CachedSession()
def compress(b: bytes) -> bytes:
l = []
for i in range(0, len(b)//8):
derp = 0
for j in range(0, 8):
derp |= b[i*8+j] << j
del j
l.append(derp)
del i, derp
return bytes(l)
def decode_opened_variable(value: str) -> bytes:
return compress(base64.b64decode(value))[4:]
def get_asset_variables(client, address) -> Iterator[AssetVariable]:
asset_contract_state, _ = client.get_contract_state(ADDRESS_ASSETS)
for variable in asset_contract_state['variables']:
asset_id = int.from_bytes(base64.b64decode(variable['value']['information']['data']))
opened = decode_opened_variable(variable['value']['openValue']['data']) if 'openValue' in variable['value'] else None
yield AssetVariable(
variable_id = variable['key'],
asset_id = asset_id,
opened = opened,
)
def to_base64_png(b: bytes) -> str:
return f'data:image/png;base64,{base64.b64encode(b)}'
def get_notamon_nfts(client: pbc_client.PbcClient) -> Iterator[NotamonNFTData]:
asset_contract_state, _ = client.get_typed_contract_state(ADDRESS_NFTS)
owners, _ = client.get_typed_contract_avl_tree(ADDRESS_NFTS, asset_contract_state['owners'])
attributes, _ = client.get_typed_contract_avl_tree(ADDRESS_NFTS, asset_contract_state['notamon_attributes'])
for notamon_id in owners:
attr = attributes.get(notamon_id)
yield NotamonNFTData(
nickname = 'Dude',
owner =owners.get(notamon_id),
species_id= attr['species_id']['id'],
skin_id = attr['skin_id']['id'],
effect_id = attr['effect_id']['id'],
stat_hp = attr['stat_hp'],
stat_attack = attr['stat_attack'],
stat_defense = attr['stat_defense'],
)
def get_asset(assets, asset_type_id, asset_type_offset) -> bytes | None:
asset_id = asset_type_id | asset_type_offset
asset = assets.get(asset_id)
if asset is None:
app.logger.error('Unknown asset with id %s = %s | %s', asset_id, asset_type_id, asset_type_offset)
return None
asset = asset.opened
if asset is None:
app.logger.warning('Secret asset with id %s = %s | %s', asset_id, asset_type_id, asset_type_offset)
return asset
def nft_to_notamon_view(nft: NotamonNFTData, assets) -> Notamon | None:
species_image_bytes = get_asset(assets, nft.species_id, ASSET_ID_OFFSET_SPECIES)
effect_str = get_asset(assets, nft.effect_id, ASSET_ID_OFFSET_EFFECT)
if species_image_bytes is None or effect_str is None:
return None
return Notamon(
image_src = to_base64_png(species_image_bytes),
effect_css = effect_str,
nickname = nft.nickname,
species_name = 'Mudkip',
)
def select_notamons():
client = pbc_client.PbcClient(SESSION, pbc_client.HOSTNAME_TESTNET)
nfts = list(get_notamon_nfts(client))
assets = {v.asset_id: v for v in get_asset_variables(client, ADDRESS_ASSETS)}
notamon_views = [nft_to_notamon_view(nft, assets) for nft in nfts]
return [n for n in notamon_views if n is not None]
@app.route("/")
def hello_world():
return flask.render_template_string(HTML_INDEX, notamons = select_notamons())