generated from nathanwoodburn/python-webserver-template
Compare commits
23 Commits
c04121ed42
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
9a6748b156
|
|||
|
90de6042b1
|
|||
|
eea558361c
|
|||
|
b6662f400a
|
|||
|
1c51e97354
|
|||
|
206b323be6
|
|||
|
400897319f
|
|||
|
a36e467bd4
|
|||
|
f70454c9a4
|
|||
|
3d5d203831
|
|||
|
16d7b9f942
|
|||
|
513a3ebd57
|
|||
|
adfa0fd4a0
|
|||
|
994b5bc5bf
|
|||
|
99bf4a6a1a
|
|||
|
7c1c6a3a2a
|
|||
|
8f3a455fbb
|
|||
|
6a8df7d661
|
|||
|
88c3cb9280
|
|||
|
edd076e722
|
|||
|
6acdce438b
|
|||
|
7d72c2de0e
|
|||
|
7cb71baf00
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ __pycache__/
|
||||
.env
|
||||
.vs/
|
||||
.venv/
|
||||
fireexplorer.db
|
||||
|
||||
@@ -2,7 +2,7 @@ FROM --platform=$BUILDPLATFORM python:3.13-alpine
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
# Install curl for healthcheck
|
||||
RUN apk add --no-cache curl
|
||||
RUN apk add --no-cache curl openssl
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
@@ -22,6 +22,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
|
||||
# Add mount point for data volume
|
||||
ENV BASE_DIR=/data
|
||||
ENV DATABASE_PATH=/data/fireexplorer.db
|
||||
VOLUME /data
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
@@ -5,10 +5,11 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"cryptography>=46.0.3",
|
||||
"flask>=3.1.2",
|
||||
"gunicorn>=23.0.0",
|
||||
"python-dotenv>=1.2.1",
|
||||
"requests>=2.32.5",
|
||||
"requests-doh>=1.0.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
||||
151
requirements.txt
151
requirements.txt
@@ -1,5 +1,9 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --frozen --output-file=requirements.txt
|
||||
anyio==4.11.0 \
|
||||
--hash=sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc \
|
||||
--hash=sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4
|
||||
# via httpx
|
||||
blinker==1.9.0 \
|
||||
--hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \
|
||||
--hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc
|
||||
@@ -7,7 +11,47 @@ blinker==1.9.0 \
|
||||
certifi==2025.11.12 \
|
||||
--hash=sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b \
|
||||
--hash=sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316
|
||||
# via requests
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
cffi==2.0.0 ; platform_python_implementation != 'PyPy' \
|
||||
--hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \
|
||||
--hash=sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b \
|
||||
--hash=sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f \
|
||||
--hash=sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9 \
|
||||
--hash=sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c \
|
||||
--hash=sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 \
|
||||
--hash=sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e \
|
||||
--hash=sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25 \
|
||||
--hash=sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b \
|
||||
--hash=sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91 \
|
||||
--hash=sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592 \
|
||||
--hash=sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1 \
|
||||
--hash=sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529 \
|
||||
--hash=sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca \
|
||||
--hash=sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4 \
|
||||
--hash=sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b \
|
||||
--hash=sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205 \
|
||||
--hash=sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27 \
|
||||
--hash=sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512 \
|
||||
--hash=sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d \
|
||||
--hash=sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c \
|
||||
--hash=sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8 \
|
||||
--hash=sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9 \
|
||||
--hash=sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775 \
|
||||
--hash=sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc \
|
||||
--hash=sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13 \
|
||||
--hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \
|
||||
--hash=sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b \
|
||||
--hash=sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6 \
|
||||
--hash=sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c \
|
||||
--hash=sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef \
|
||||
--hash=sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad \
|
||||
--hash=sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3 \
|
||||
--hash=sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2 \
|
||||
--hash=sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5
|
||||
# via cryptography
|
||||
cfgv==3.5.0 \
|
||||
--hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 \
|
||||
--hash=sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132
|
||||
@@ -56,10 +100,62 @@ colorama==0.4.6 ; sys_platform == 'win32' \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via click
|
||||
cryptography==46.0.3 \
|
||||
--hash=sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217 \
|
||||
--hash=sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d \
|
||||
--hash=sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc \
|
||||
--hash=sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71 \
|
||||
--hash=sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971 \
|
||||
--hash=sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a \
|
||||
--hash=sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926 \
|
||||
--hash=sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc \
|
||||
--hash=sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d \
|
||||
--hash=sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20 \
|
||||
--hash=sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044 \
|
||||
--hash=sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3 \
|
||||
--hash=sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715 \
|
||||
--hash=sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4 \
|
||||
--hash=sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506 \
|
||||
--hash=sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f \
|
||||
--hash=sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0 \
|
||||
--hash=sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683 \
|
||||
--hash=sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3 \
|
||||
--hash=sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21 \
|
||||
--hash=sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91 \
|
||||
--hash=sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c \
|
||||
--hash=sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8 \
|
||||
--hash=sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df \
|
||||
--hash=sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb \
|
||||
--hash=sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7 \
|
||||
--hash=sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04 \
|
||||
--hash=sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db \
|
||||
--hash=sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459 \
|
||||
--hash=sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914 \
|
||||
--hash=sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac \
|
||||
--hash=sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec \
|
||||
--hash=sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1 \
|
||||
--hash=sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb \
|
||||
--hash=sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac \
|
||||
--hash=sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665 \
|
||||
--hash=sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e \
|
||||
--hash=sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5 \
|
||||
--hash=sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936 \
|
||||
--hash=sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de \
|
||||
--hash=sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372 \
|
||||
--hash=sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54 \
|
||||
--hash=sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422 \
|
||||
--hash=sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849 \
|
||||
--hash=sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963 \
|
||||
--hash=sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018
|
||||
# via python-webserver-template
|
||||
distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
dnspython==2.6.1 \
|
||||
--hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \
|
||||
--hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc
|
||||
# via requests-doh
|
||||
filelock==3.20.0 \
|
||||
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||
@@ -72,6 +168,32 @@ gunicorn==23.0.0 \
|
||||
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
|
||||
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
|
||||
# via python-webserver-template
|
||||
h11==0.16.0 \
|
||||
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
|
||||
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
|
||||
# via httpcore
|
||||
h2==4.3.0 \
|
||||
--hash=sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1 \
|
||||
--hash=sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd
|
||||
# via dnspython
|
||||
hpack==4.1.0 \
|
||||
--hash=sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496 \
|
||||
--hash=sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca
|
||||
# via h2
|
||||
httpcore==1.0.9 \
|
||||
--hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
|
||||
--hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
|
||||
# via
|
||||
# dnspython
|
||||
# httpx
|
||||
httpx==0.28.1 \
|
||||
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
|
||||
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
|
||||
# via dnspython
|
||||
hyperframe==6.1.0 \
|
||||
--hash=sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5 \
|
||||
--hash=sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08
|
||||
# via h2
|
||||
identify==2.6.15 \
|
||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
|
||||
@@ -79,7 +201,10 @@ identify==2.6.15 \
|
||||
idna==3.11 \
|
||||
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \
|
||||
--hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902
|
||||
# via requests
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
itsdangerous==2.2.0 \
|
||||
--hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
|
||||
--hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173
|
||||
@@ -153,6 +278,14 @@ platformdirs==4.5.0 \
|
||||
pre-commit==4.4.0 \
|
||||
--hash=sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813 \
|
||||
--hash=sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15
|
||||
pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' \
|
||||
--hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \
|
||||
--hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934
|
||||
# via cffi
|
||||
pysocks==1.7.1 \
|
||||
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
|
||||
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
|
||||
# via requests
|
||||
python-dotenv==1.2.1 \
|
||||
--hash=sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6 \
|
||||
--hash=sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61
|
||||
@@ -188,9 +321,13 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
|
||||
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6
|
||||
# via pre-commit
|
||||
requests==2.32.5 \
|
||||
--hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
|
||||
--hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
|
||||
requests==2.32.3 \
|
||||
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
||||
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
|
||||
# via requests-doh
|
||||
requests-doh==1.0.0 \
|
||||
--hash=sha256:6ce8bc96245030a198ef20d2100b4dcb3b120a05a58df703f8be121a79f8f2fb \
|
||||
--hash=sha256:eea6583b792b7d3dfde74fd28eedc2b95d6ea896368119eede31f0d6ff2c838c
|
||||
# via python-webserver-template
|
||||
ruff==0.14.5 \
|
||||
--hash=sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68 \
|
||||
@@ -212,6 +349,10 @@ ruff==0.14.5 \
|
||||
--hash=sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2 \
|
||||
--hash=sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151 \
|
||||
--hash=sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72
|
||||
sniffio==1.3.1 \
|
||||
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
|
||||
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
|
||||
# via anyio
|
||||
urllib3==2.5.0 \
|
||||
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
||||
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
||||
|
||||
218
server.py
218
server.py
@@ -4,16 +4,55 @@ from flask import (
|
||||
render_template,
|
||||
send_from_directory,
|
||||
send_file,
|
||||
jsonify,
|
||||
g,
|
||||
request,
|
||||
)
|
||||
import os
|
||||
import requests
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import dotenv
|
||||
from tools import hip2, wallet_txt
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
DATABASE = os.getenv("DATABASE_PATH", "fireexplorer.db")
|
||||
|
||||
|
||||
def get_db():
|
||||
db = getattr(g, "_database", None)
|
||||
if db is None:
|
||||
db = g._database = sqlite3.connect(DATABASE)
|
||||
db.row_factory = sqlite3.Row
|
||||
return db
|
||||
|
||||
|
||||
@app.teardown_appcontext
|
||||
def close_connection(exception):
|
||||
db = getattr(g, "_database", None)
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS names (
|
||||
namehash TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
db.commit()
|
||||
|
||||
|
||||
init_db()
|
||||
|
||||
|
||||
def find(name, path):
|
||||
for root, dirs, files in os.walk(path):
|
||||
@@ -131,13 +170,182 @@ def catch_all(path: str):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region API routes
|
||||
@app.route("/api/v1/namehash/<namehash>")
|
||||
def namehash_api(namehash):
|
||||
db = get_db()
|
||||
cur = db.execute("SELECT * FROM names WHERE namehash = ?", (namehash,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
# Get namehash from hsd.hns.au
|
||||
req = requests.get(f"https://hsd.hns.au/api/v1/namehash/{namehash}")
|
||||
if req.status_code == 200:
|
||||
name = req.json().get("result")
|
||||
if not name:
|
||||
return jsonify({"name": "Error", "namehash": namehash})
|
||||
# Insert into database
|
||||
db.execute(
|
||||
"INSERT OR REPLACE INTO names (namehash, name) VALUES (?, ?)",
|
||||
(namehash, name),
|
||||
)
|
||||
db.commit()
|
||||
return jsonify({"name": name, "namehash": namehash})
|
||||
return jsonify(dict(row))
|
||||
|
||||
|
||||
@app.route("/api/v1/status")
|
||||
def api_status():
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "FireExplorer",
|
||||
"version": "1.0.0",
|
||||
}
|
||||
# Count number of names in database
|
||||
db = get_db()
|
||||
cur = db.execute("SELECT COUNT(*) as count FROM names")
|
||||
row = cur.fetchone()
|
||||
name_count = row["count"] if row else 0
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "FireExplorer",
|
||||
"version": "1.0.0",
|
||||
"names_cached": name_count,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/v1/hip02/<domain>")
|
||||
def hip02(domain: str):
|
||||
hip2_record = hip2(domain)
|
||||
if hip2_record:
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"address": hip2_record,
|
||||
"method": "hip02",
|
||||
"name": domain,
|
||||
}
|
||||
)
|
||||
|
||||
wallet_record = wallet_txt(domain)
|
||||
if wallet_record:
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"address": wallet_record,
|
||||
"method": "wallet_txt",
|
||||
"name": domain,
|
||||
}
|
||||
)
|
||||
return jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"name": domain,
|
||||
"error": "No HIP02 or WALLET record found for this domain",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/v1/covenant", methods=["POST"])
|
||||
def covenant_api():
|
||||
data = request.get_json()
|
||||
|
||||
if isinstance(data, list):
|
||||
covenants = data
|
||||
results = []
|
||||
|
||||
# Collect all namehashes needed
|
||||
namehashes = set()
|
||||
for cov in covenants:
|
||||
items = cov.get("items", [])
|
||||
if items:
|
||||
namehashes.add(items[0])
|
||||
|
||||
# Batch DB lookup
|
||||
db = get_db()
|
||||
known_names = {}
|
||||
if namehashes:
|
||||
placeholders = ",".join("?" for _ in namehashes)
|
||||
cur = db.execute(
|
||||
f"SELECT namehash, name FROM names WHERE namehash IN ({placeholders})",
|
||||
list(namehashes),
|
||||
)
|
||||
for row in cur:
|
||||
known_names[row["namehash"]] = row["name"]
|
||||
|
||||
# Identify missing namehashes
|
||||
missing_hashes = [nh for nh in namehashes if nh not in known_names]
|
||||
|
||||
# Fetch missing from HSD
|
||||
session = requests.Session()
|
||||
for nh in missing_hashes:
|
||||
try:
|
||||
req = session.get(f"https://hsd.hns.au/api/v1/namehash/{nh}")
|
||||
if req.status_code == 200:
|
||||
name = req.json().get("result")
|
||||
if name:
|
||||
known_names[nh] = name
|
||||
# Update DB
|
||||
db.execute(
|
||||
"INSERT OR REPLACE INTO names (namehash, name) VALUES (?, ?)",
|
||||
(nh, name),
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error fetching namehash {nh}: {e}")
|
||||
|
||||
db.commit()
|
||||
|
||||
# Build results
|
||||
for cov in covenants:
|
||||
action = cov.get("action")
|
||||
items = cov.get("items", [])
|
||||
|
||||
if not action:
|
||||
results.append({"covenant": cov, "display": "Unknown"})
|
||||
continue
|
||||
|
||||
display = f"{action}"
|
||||
if items:
|
||||
nh = items[0]
|
||||
if nh in known_names:
|
||||
name = known_names[nh]
|
||||
display += f' <a href="/name/{name}">{name}</a>'
|
||||
|
||||
results.append({"covenant": cov, "display": display})
|
||||
|
||||
return jsonify(results)
|
||||
|
||||
# Get the covenant data
|
||||
action = data.get("action")
|
||||
items = data.get("items", [])
|
||||
|
||||
if not action:
|
||||
return jsonify({"success": False, "data": data})
|
||||
|
||||
display = f"{action}"
|
||||
if len(items) > 0:
|
||||
name_hash = items[0]
|
||||
# Lookup name from database
|
||||
db = get_db()
|
||||
cur = db.execute("SELECT * FROM names WHERE namehash = ?", (name_hash,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
name = row["name"]
|
||||
display += f' <a href="/name/{name}">{name}</a>'
|
||||
else:
|
||||
req = requests.get(f"https://hsd.hns.au/api/v1/namehash/{name_hash}")
|
||||
if req.status_code == 200:
|
||||
name = req.json().get("result")
|
||||
if name:
|
||||
display += f" {name}"
|
||||
# Insert into database
|
||||
db.execute(
|
||||
"INSERT OR REPLACE INTO names (namehash, name) VALUES (?, ?)",
|
||||
(name_hash, name),
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return jsonify({"success": True, "data": data, "display": display})
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/assets/css/404.css">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,20 +1,45 @@
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%);
|
||||
--bg-color: #0f172a;
|
||||
--text-primary: #f8fafc;
|
||||
--accent-color: #8b5cf6;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
h1 {
|
||||
font-size: 50px;
|
||||
background-color: var(--bg-color);
|
||||
background-image:
|
||||
radial-gradient(at 0% 0%, rgba(59, 130, 246, 0.15) 0px, transparent 50%),
|
||||
radial-gradient(at 100% 0%, rgba(236, 72, 153, 0.15) 0px, transparent 50%);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0 0 1rem 0;
|
||||
background: var(--primary-gradient);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.centre {
|
||||
margin-top: 10%;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ffffff;
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #a78bfa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
:root {
|
||||
--primary-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%);
|
||||
--bg-color: #0f172a;
|
||||
--card-bg: rgba(30, 41, 59, 0.7);
|
||||
--card-border: rgba(148, 163, 184, 0.1);
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #94a3b8;
|
||||
--accent-color: #8b5cf6;
|
||||
--success-color: #10b981;
|
||||
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -5,9 +17,12 @@
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
|
||||
color: #e0e0e0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
background-image:
|
||||
radial-gradient(at 0% 0%, rgba(59, 130, 246, 0.15) 0px, transparent 50%),
|
||||
radial-gradient(at 100% 0%, rgba(236, 72, 153, 0.15) 0px, transparent 50%);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -20,24 +35,56 @@ body {
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background: linear-gradient(135deg, #8b2f0a 0%, #6b3d0a 100%);
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 20px rgba(255, 107, 53, 0.3);
|
||||
margin: 0 0 2rem 0;
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
padding: 1.5rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 3rem;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
background: var(--primary-gradient);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
header a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.5rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
display: none; /* Hidden on mobile, shown on desktop */
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.subtitle {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
@@ -45,26 +92,47 @@ main {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
section {
|
||||
scroll-margin-top: 140px;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border-radius: 12px;
|
||||
background: var(--card-bg);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 107, 53, 0.2);
|
||||
box-shadow: var(--glass-shadow);
|
||||
border: 1px solid var(--card-border);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
opacity: 0; /* Start hidden for animation */
|
||||
min-width: 0; /* Prevent grid overflow */
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: #ff6b35;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.8rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
color: #f7931e;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Status Section */
|
||||
@@ -77,25 +145,31 @@ main {
|
||||
|
||||
.status-card {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
color: #b0b0b0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* Info Grid */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 0.5rem;
|
||||
background: rgba(20, 20, 20, 0.5);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 107, 53, 0.15);
|
||||
padding: 1rem;
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--card-border);
|
||||
min-width: 0; /* Prevent grid overflow */
|
||||
}
|
||||
|
||||
.info-item.no-border {
|
||||
@@ -109,86 +183,127 @@ main {
|
||||
}
|
||||
|
||||
.info-item strong {
|
||||
color: #ff6b35;
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
color: var(--text-secondary);
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
font-size: 0.9rem;
|
||||
word-break: break-all;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.view-all-btn {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.6rem 1.2rem;
|
||||
background: var(--primary-gradient);
|
||||
color: #ffffff !important;
|
||||
text-decoration: none !important;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.view-all-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.4);
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
|
||||
}
|
||||
|
||||
/* Mempool Transaction List */
|
||||
.mempool-txs-container {
|
||||
margin-top: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mempool-header {
|
||||
padding: 0.75rem;
|
||||
background: rgba(255, 107, 53, 0.1);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #ff6b35;
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tx-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
.tx-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.tx-list::-webkit-scrollbar-track {
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.tx-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.tx-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
background: rgba(20, 20, 20, 0.5);
|
||||
border: 1px solid rgba(255, 107, 53, 0.15);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0.5rem;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.75rem;
|
||||
transition: all 0.2s ease;
|
||||
animation: staggerFade 0.4s ease forwards;
|
||||
cursor: pointer;
|
||||
min-width: 0; /* Prevent flex overflow */
|
||||
}
|
||||
|
||||
.tx-item:hover {
|
||||
border-color: rgba(139, 92, 246, 0.3);
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tx-hash {
|
||||
flex: 1;
|
||||
font-size: 0.8rem;
|
||||
color: #b0b0b0;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0; /* Prevent flex overflow */
|
||||
}
|
||||
|
||||
.tx-view-btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
color: var(--accent-color);
|
||||
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tx-view-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.4);
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Transaction Modal */
|
||||
@@ -198,23 +313,39 @@ main {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
backdrop-filter: blur(8px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.tx-modal.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.tx-modal-content {
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
border-radius: 12px;
|
||||
background: #1e293b;
|
||||
border-radius: 16px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
border: 1px solid rgba(255, 107, 53, 0.3);
|
||||
max-height: 85vh;
|
||||
border: 1px solid var(--card-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
transform: translateY(20px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tx-modal.active .tx-modal-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tx-modal-header {
|
||||
@@ -222,33 +353,30 @@ main {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(255, 107, 53, 0.2);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.tx-modal-header h3 {
|
||||
margin: 0;
|
||||
color: #ff6b35;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tx-modal-close {
|
||||
background: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #e0e0e0;
|
||||
font-size: 2rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tx-modal-close:hover {
|
||||
background: rgba(255, 107, 53, 0.2);
|
||||
color: #ff6b35;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tx-modal-body {
|
||||
@@ -258,80 +386,90 @@ main {
|
||||
}
|
||||
|
||||
.tx-modal-body pre {
|
||||
color: #b0b0b0;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Transaction Details */
|
||||
.tx-details {
|
||||
color: #e0e0e0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tx-section {
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.tx-section h4 {
|
||||
color: #ff6b35;
|
||||
color: var(--accent-color);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.tx-io-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tx-io-item {
|
||||
background: rgba(20, 20, 20, 0.5);
|
||||
border: 1px solid rgba(255, 107, 53, 0.15);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
min-width: 0; /* Prevent overflow */
|
||||
}
|
||||
|
||||
.tx-io-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.tx-io-index {
|
||||
color: #ff6b35;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tx-io-value {
|
||||
color: #f7931e;
|
||||
color: var(--success-color);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.tx-io-address {
|
||||
color: #b0b0b0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tx-io-hash {
|
||||
color: #808080;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
word-break: break-all;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.tx-covenant {
|
||||
color: #ff6b35;
|
||||
color: var(--accent-color);
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
font-style: italic;
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--card-border);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
@@ -340,38 +478,53 @@ main {
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
padding: 0.5rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: rgba(50, 50, 50, 0.5);
|
||||
color: #e0e0e0;
|
||||
border: 1px solid rgba(255, 107, 53, 0.3);
|
||||
padding: 0.75rem 1.5rem;
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: none;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
background: rgba(255, 107, 53, 0.2);
|
||||
border-color: #ff6b35;
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
|
||||
background: var(--primary-gradient);
|
||||
color: #ffffff;
|
||||
border-color: #ff6b35;
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Search Box */
|
||||
.search-box {
|
||||
display: flex;
|
||||
@@ -383,39 +536,42 @@ main {
|
||||
.search-box input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 0.75rem 1rem;
|
||||
background: rgba(20, 20, 20, 0.8);
|
||||
border: 1px solid rgba(255, 107, 53, 0.3);
|
||||
border-radius: 8px;
|
||||
color: #e0e0e0;
|
||||
padding: 0.8rem 1.2rem;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 10px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: #ff6b35;
|
||||
box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2);
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.15), 0 0 20px rgba(139, 92, 246, 0.2);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
}
|
||||
|
||||
.search-box input::placeholder {
|
||||
color: rgba(224, 224, 224, 0.5);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.search-box button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
|
||||
padding: 0.8rem 1.5rem;
|
||||
background: var(--primary-gradient);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-box button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.4);
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
|
||||
}
|
||||
|
||||
.search-box button:active {
|
||||
@@ -423,30 +579,32 @@ main {
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(50, 50, 50, 0.8);
|
||||
color: #ff6b35;
|
||||
border: 1px solid #ff6b35;
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem 1.5rem;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.secondary-btn:hover {
|
||||
background: rgba(255, 107, 53, 0.2);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Result Box */
|
||||
.result-box {
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
border: 1px solid rgba(255, 107, 53, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
min-height: 100px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.result-box:empty {
|
||||
@@ -454,16 +612,31 @@ main {
|
||||
}
|
||||
|
||||
.result-box pre {
|
||||
color: #b0b0b0;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.result-box .error {
|
||||
color: #ff4444;
|
||||
color: #ef4444;
|
||||
font-weight: 600;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: var(--success-color);
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
@@ -472,58 +645,55 @@ main {
|
||||
}
|
||||
|
||||
.result-box::-webkit-scrollbar-track {
|
||||
background: rgba(20, 20, 20, 0.5);
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.result-box::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 107, 53, 0.5);
|
||||
background: rgba(148, 163, 184, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.result-box::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 107, 53, 0.7);
|
||||
background: rgba(148, 163, 184, 0.5);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background: rgba(20, 20, 20, 0.8);
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
border-top: 1px solid rgba(255, 107, 53, 0.2);
|
||||
margin-top: 4rem;
|
||||
border-top: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
footer p {
|
||||
color: rgba(224, 224, 224, 0.7);
|
||||
color: var(--text-secondary);
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(224, 224, 224, 0.5);
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: #ff6b35;
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #f7931e;
|
||||
color: #a78bfa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -542,6 +712,31 @@ a:hover {
|
||||
.status-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.tx-io-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading Animation */
|
||||
@@ -557,4 +752,87 @@ a:hover {
|
||||
.status-content:empty::after {
|
||||
content: 'Loading...';
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
color: var(--text-secondary);
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(139, 92, 246, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--accent-color);
|
||||
animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.05em;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* New Animations */
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes staggerFade {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply animations */
|
||||
.card {
|
||||
animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
opacity: 0; /* Start hidden for animation */
|
||||
}
|
||||
|
||||
.search-section .card {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.status-section .card:nth-child(1) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.status-section .card:nth-child(2) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.additional-section .card {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.tx-item {
|
||||
animation: staggerFade 0.4s ease forwards;
|
||||
}
|
||||
|
||||
BIN
templates/assets/img/og.png
Normal file
BIN
templates/assets/img/og.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -4,37 +4,43 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Fire Explorer - Handshake Blockchain Explorer</title>
|
||||
<title>Fire Explorer</title>
|
||||
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||
<link rel="stylesheet" href="/assets/css/index.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<meta name="description" content="A hot new Handshake Blockchain Explorer">
|
||||
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<meta property="og:url" content="https://explorer.hns.au/">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Fire Explorer">
|
||||
<meta property="og:description" content="A hot new Handshake Blockchain Explorer">
|
||||
<meta property="og:image" content="https://explorer.hns.au/assets/img/og.png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
|
||||
<!-- Twitter Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:domain" content="explorer.hns.au">
|
||||
<meta property="twitter:url" content="https://explorer.hns.au/">
|
||||
<meta name="twitter:title" content="Fire Explorer">
|
||||
<meta name="twitter:description" content="A hot new Handshake Blockchain Explorer">
|
||||
<meta name="twitter:image" content="https://explorer.hns.au/assets/img/og.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1><img src="/assets/img/favicon.png" alt="Fire Icon" style="height: 1em; vertical-align: middle;"> Fire Explorer</h1>
|
||||
<p class="subtitle">Handshake Blockchain Explorer</p>
|
||||
<div class="brand">
|
||||
<a href="/"><h1><img src="/assets/img/favicon.png" alt="Fire Icon" style="height: 1.2em; vertical-align: middle;"> Fire Explorer</h1></a>
|
||||
<span class="subtitle">Handshake Blockchain Explorer</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- Status Cards -->
|
||||
<section class="status-section">
|
||||
<div class="card status-card">
|
||||
<h3>Node Status</h3>
|
||||
<div id="node-status" class="status-content">Loading...</div>
|
||||
</div>
|
||||
<div class="card status-card">
|
||||
<h3>Chain Info</h3>
|
||||
<div id="chain-status" class="status-content">Loading...</div>
|
||||
</div>
|
||||
<div class="card status-card">
|
||||
<h3>Mempool</h3>
|
||||
<div id="mempool-status" class="status-content">Loading...</div>
|
||||
<div id="mempool-txs" class="mempool-txs-container"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Search Section -->
|
||||
<section class="search-section">
|
||||
<div class="card">
|
||||
@@ -72,6 +78,9 @@
|
||||
<button onclick="searchAddressTx()">Get Transactions</button>
|
||||
<button onclick="searchAddressCoins()">Get Coins</button>
|
||||
</div>
|
||||
<p style="font-size: 0.85rem; color: var(--text-secondary); margin-top: -0.5rem; margin-bottom: 1.5rem; padding-left: 0.5rem;">
|
||||
<span style="color: var(--accent-color);">Tip:</span> Use <span class="mono" style="color: var(--text-primary);">@name</span> to search via HIP02 alias
|
||||
</p>
|
||||
<div id="address-result" class="result-box"></div>
|
||||
</div>
|
||||
|
||||
@@ -92,16 +101,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Coin Lookup -->
|
||||
<section class="additional-section">
|
||||
<div class="card">
|
||||
<h2>Coin Lookup</h2>
|
||||
<div class="search-box">
|
||||
<input type="text" id="coin-hash" placeholder="Coin hash" onkeypress="if(event.key === 'Enter') searchCoin()">
|
||||
<input type="text" id="coin-index" placeholder="Index" onkeypress="if(event.key === 'Enter') searchCoin()">
|
||||
<button onclick="searchCoin()">Get Coin Info</button>
|
||||
</div>
|
||||
<div id="coin-result" class="result-box"></div>
|
||||
<!-- Status Cards -->
|
||||
<section class="status-section">
|
||||
<div class="card status-card">
|
||||
<h3>Chain Info</h3>
|
||||
<div id="chain-status" class="status-content">Loading...</div>
|
||||
</div>
|
||||
<div class="card status-card">
|
||||
<h3>Mempool</h3>
|
||||
<div id="mempool-status" class="status-content">Loading...</div>
|
||||
<div id="mempool-txs" class="mempool-txs-container"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
@@ -169,11 +178,6 @@
|
||||
searchName();
|
||||
break;
|
||||
}
|
||||
} else if (parts.length === 3 && parts[0] === 'coin') {
|
||||
// Handle coin URLs: /coin/hash/index
|
||||
document.getElementById('coin-hash').value = parts[1];
|
||||
document.getElementById('coin-index').value = parts[2];
|
||||
searchCoin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +201,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Convert unicode/emoji to punycode for Handshake names
|
||||
function toPunycode(name) {
|
||||
// Check if name contains non-ASCII characters
|
||||
if (!/[^\x00-\x7F]/.test(name)) {
|
||||
return name; // Already ASCII, no conversion needed
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the browser's built-in URL API to convert to punycode
|
||||
const url = new URL(`http://${name}`);
|
||||
return url.hostname;
|
||||
} catch {
|
||||
// Fallback: return original if conversion fails
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve HIP02 alias
|
||||
async function resolveHip02(input) {
|
||||
if (!input.startsWith('@')) return { success: true, address: input };
|
||||
|
||||
const name = input.substring(1);
|
||||
try {
|
||||
// Try HTTPS first
|
||||
const response = await fetch(`/api/v1/hip02/${name}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (e) {
|
||||
return { success: false, error: 'Failed to resolve HIP02 alias. ' + e.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Format chain data nicely
|
||||
function formatChainData(chain) {
|
||||
return `
|
||||
@@ -213,6 +249,13 @@
|
||||
`;
|
||||
}
|
||||
|
||||
// Open transaction in search tab
|
||||
function openTx(txId) {
|
||||
document.getElementById('tx-input').value = txId;
|
||||
document.querySelector('[data-tab="tx"]').click();
|
||||
searchTx();
|
||||
}
|
||||
|
||||
// Format mempool data nicely
|
||||
function formatMempoolData(mempool) {
|
||||
// Check if mempool is an array of transaction IDs
|
||||
@@ -228,9 +271,8 @@
|
||||
</div>
|
||||
<div class="tx-list">
|
||||
${mempool.map(txId => `
|
||||
<div class="tx-item">
|
||||
<div class="tx-item" onclick="openTx('${txId}')">
|
||||
<span class="tx-hash mono">${txId}</span>
|
||||
<button class="tx-view-btn" onclick="window.location.href='/tx/${txId}'">View</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -427,7 +469,7 @@
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.85rem; color: #b0b0b0;">
|
||||
<span>Height: ${coin.height.toLocaleString()}</span>
|
||||
<span>Coinbase: ${coin.coinbase ? 'Yes' : 'No'}</span>
|
||||
<span>Covenant: ${coin.covenant.action}</span>
|
||||
<span>Covenant: <span data-covenant-action="${coin.covenant.action}" data-covenant="${encodeURIComponent(JSON.stringify(coin.covenant))}">${coin.covenant.action}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
@@ -479,7 +521,7 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Covenant:</label>
|
||||
<span>${coin.covenant.action}</span>
|
||||
<span data-covenant-action="${coin.covenant.action}" data-covenant="${encodeURIComponent(JSON.stringify(coin.covenant))}">${coin.covenant.action}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -522,6 +564,256 @@
|
||||
return html;
|
||||
}
|
||||
|
||||
// Format name info nicely
|
||||
function formatNameInfo(data) {
|
||||
if (!data || data.error) {
|
||||
return `<div class="error">Error: ${data.error || 'Invalid name data'}</div>`;
|
||||
}
|
||||
|
||||
const info = data.info;
|
||||
const start = data.start;
|
||||
const formatValue = (value) => (value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
|
||||
const formatDate = (timestamp) => new Date(timestamp * 1000).toLocaleString();
|
||||
|
||||
// If name doesn't exist (no info), show error
|
||||
if (!info) {
|
||||
return `<div class="error">Name not found</div>`;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<div class="tx-details">
|
||||
<div class="tx-section">
|
||||
<h4>Name: ${info.name}</h4>
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><strong>State:</strong> <span style="color: #f7931e;">${info.state}</span></div>
|
||||
<div class="info-item"><strong>Registered:</strong> ${info.registered ? 'Yes' : 'No'}</div>
|
||||
<div class="info-item"><strong>Height:</strong> ${info.height.toLocaleString()}</div>
|
||||
<div class="info-item"><strong>Value:</strong> ${formatValue(info.value)}</div>
|
||||
<div class="info-item"><strong>Highest Bid:</strong> ${formatValue(info.highest)}</div>
|
||||
<div class="info-item"><strong>Renewals:</strong> ${info.renewals}</div>
|
||||
${info.stats.renewalPeriodEnd ? `
|
||||
<div class="info-item"><strong>Expiry Block:</strong> ${info.stats.renewalPeriodEnd.toLocaleString()}</div>
|
||||
` : ''}
|
||||
|
||||
<div class="info-item"><strong>Expired:</strong> ${info.expired ? 'Yes' : 'No'}</div>
|
||||
<div class="info-item"><strong>Revoked:</strong> ${info.revoked > 0 ? 'Yes' : 'No'}</div>
|
||||
<div class="info-item"><strong>Weak:</strong> ${info.weak ? 'Yes' : 'No'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${info.stats ? `
|
||||
<div class="tx-section">
|
||||
<h4>Additional Info</h4>
|
||||
<div class="info-grid">
|
||||
${Object.entries(info.stats).map(([key, value]) => {
|
||||
// Format the key from camelCase to Title Case
|
||||
const formattedKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
|
||||
|
||||
// Format the value based on type
|
||||
let formattedValue;
|
||||
if (typeof value === 'number') {
|
||||
// Check if it looks like a block number (large integer) or a decimal (days/hours)
|
||||
if ((Number.isInteger(value) && value > 100) || key.toLowerCase().includes('height') || key.toLowerCase().includes('block')) {
|
||||
formattedValue = value.toLocaleString();
|
||||
} else {
|
||||
formattedValue = value.toFixed(2);
|
||||
}
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
return `<div class="info-item"><strong>${formattedKey}:</strong> ${formattedValue}</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="tx-section">
|
||||
<h4>Owner</h4>
|
||||
${ info.owner.hash == '0000000000000000000000000000000000000000000000000000000000000000' ? `
|
||||
<div style="padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;">
|
||||
<div class="mono" style="word-break: break-all; color: #ff6b35;">This name is unowned</div>
|
||||
</div>
|
||||
` : `<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/tx/${info.owner.hash}'">
|
||||
<div class="mono" style="word-break: break-all; color: #ff6b35;">TX: ${info.owner.hash}</div>
|
||||
<div style="color: #b0b0b0; margin-top: 0.5rem;">Output Index: ${info.owner.index}</div>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<div class="tx-section">
|
||||
<h4>Name Hash</h4>
|
||||
<div class="mono" style="word-break: break-all; padding: 1rem; background: rgba(255, 107, 53, 0.05); border-radius: 8px;">${info.nameHash}</div>
|
||||
</div>
|
||||
|
||||
${start ? `
|
||||
<div class="tx-section">
|
||||
<h4>Auction Info</h4>
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><strong>Start Height:</strong> ${start.start.toLocaleString()}</div>
|
||||
<div class="info-item"><strong>Week:</strong> ${start.week}</div>
|
||||
<div class="info-item"><strong>Reserved:</strong> ${start.reserved ? 'Yes' : 'No'}</div>
|
||||
<div class="info-item"><strong>Locked:</strong> ${start.locked ? 'Yes' : 'No'}</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="tx-section">
|
||||
<button class="secondary-btn" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">Show Raw JSON</button>
|
||||
<pre style="display: none;">${JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Format name resource records nicely
|
||||
function formatNameResource(data) {
|
||||
if (!data || data.error) {
|
||||
return `<div class="error">Error: ${data.error || 'Invalid resource data'}</div>`;
|
||||
}
|
||||
|
||||
if (!data.records || data.records.length === 0) {
|
||||
return `<div class="error">No resource records found</div>`;
|
||||
}
|
||||
|
||||
const records = data.records;
|
||||
|
||||
let html = `
|
||||
<div class="tx-details">
|
||||
<div class="tx-section">
|
||||
<h4>DNS Records (${records.length})</h4>
|
||||
<div class="tx-io-list">
|
||||
${records.map((record, i) => {
|
||||
let recordDetails = `<strong>${record.type}</strong>`;
|
||||
|
||||
switch(record.type) {
|
||||
case 'NS':
|
||||
recordDetails += ` → ${record.ns}`;
|
||||
break;
|
||||
case 'DS':
|
||||
recordDetails += `<br><span style="font-size: 0.9rem;">KeyTag: ${record.keyTag}, Algorithm: ${record.algorithm}, DigestType: ${record.digestType}</span><br><span class="mono" style="font-size: 0.85rem; color: #b0b0b0; word-break: break-all;">${record.digest}</span>`;
|
||||
break;
|
||||
case 'TXT':
|
||||
recordDetails += `<br><span style="color: #b0b0b0;">${record.txt.join('<br>')}</span>`;
|
||||
break;
|
||||
case 'GLUE4':
|
||||
case 'GLUE6':
|
||||
recordDetails += ` → ${record.ns}<br><span style="color: #f7931e;">${record.address}</span>`;
|
||||
break;
|
||||
case 'SYNTH4':
|
||||
case 'SYNTH6':
|
||||
recordDetails += ` → <span style="color: #f7931e;">${record.address}</span>`;
|
||||
break;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="tx-io-item">
|
||||
<div class="tx-io-header">
|
||||
<span class="tx-io-index">Record #${i}</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.5rem;">${recordDetails}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tx-section">
|
||||
<button class="secondary-btn" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">Show Raw JSON</button>
|
||||
<pre style="display: none;">${JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Format name summary nicely
|
||||
function formatNameSummary(data) {
|
||||
if (!data || data.error) {
|
||||
return `<div class="error">Error: ${data.error || 'Invalid summary data'}</div>`;
|
||||
}
|
||||
|
||||
const formatValue = (value) => (value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
|
||||
const formatDate = (timestamp) => new Date(timestamp * 1000).toLocaleString();
|
||||
|
||||
let html = `
|
||||
<div class="tx-details">
|
||||
<div class="tx-section">
|
||||
<h4>Name: ${data.name}</h4>
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><strong>State:</strong> <span style="color: #f7931e;">${data.state}</span></div>
|
||||
<div class="info-item"><strong>Height:</strong> ${data.height.toLocaleString()}</div>
|
||||
<div class="info-item"><strong>Value:</strong> ${formatValue(data.value)}</div>
|
||||
<div class="info-item"><strong>Blocks Until Expire:</strong> ${data.blocksUntilExpire.toLocaleString()}</div>
|
||||
<div class="info-item"><strong>Mint Date:</strong> ${formatDate(data.mintTimestamp)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tx-section">
|
||||
<h4>Owner Address</h4>
|
||||
<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/address/${data.owner}'">
|
||||
<div class="mono" style="word-break: break-all; color: #ff6b35;">${data.owner}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tx-section">
|
||||
<h4>Name Hash</h4>
|
||||
<div class="mono" style="word-break: break-all; padding: 1rem; background: rgba(255, 107, 53, 0.05); border-radius: 8px;">${data.hash}</div>
|
||||
</div>
|
||||
|
||||
${data.resources && data.resources[0] && data.resources[0].records ? `
|
||||
<div class="tx-section">
|
||||
<h4>DNS Records (${data.resources[0].records.length})</h4>
|
||||
<div class="tx-io-list">
|
||||
${data.resources[0].records.map((record, i) => {
|
||||
let recordDetails = `<strong>${record.type}</strong>`;
|
||||
|
||||
switch(record.type) {
|
||||
case 'NS':
|
||||
recordDetails += ` → ${record.ns}`;
|
||||
break;
|
||||
case 'DS':
|
||||
recordDetails += `<br><span style="font-size: 0.9rem;">KeyTag: ${record.keyTag}, Algorithm: ${record.algorithm}</span>`;
|
||||
break;
|
||||
case 'TXT':
|
||||
recordDetails += `<br><span style="color: #b0b0b0;">${record.txt.join('<br>')}</span>`;
|
||||
break;
|
||||
case 'GLUE4':
|
||||
case 'GLUE6':
|
||||
recordDetails += ` → ${record.ns}<br><span style="color: #f7931e;">${record.address}</span>`;
|
||||
break;
|
||||
case 'SYNTH4':
|
||||
case 'SYNTH6':
|
||||
recordDetails += ` → <span style="color: #f7931e;">${record.address}</span>`;
|
||||
break;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="tx-io-item">
|
||||
<div class="tx-io-header">
|
||||
<span class="tx-io-index">Record #${i}</span>
|
||||
</div>
|
||||
<div style="margin-top: 0.5rem;">${recordDetails}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="tx-section">
|
||||
<button class="secondary-btn" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">Show Raw JSON</button>
|
||||
<pre style="display: none;">${JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Format transaction data nicely
|
||||
function formatTransactionData(tx) {
|
||||
if (!tx || tx.error) {
|
||||
@@ -569,7 +861,7 @@
|
||||
<span class="tx-io-value">${input.coin ? formatValue(input.coin.value) : 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="tx-io-address">${input.coin ? input.coin.address : (input.address || 'Unknown')}</div>
|
||||
${input.coin && input.coin.covenant.action !== 'NONE' ? `<div class="tx-covenant">Covenant: ${input.coin.covenant.action}</div>` : ''}
|
||||
${input.coin && input.coin.covenant.action !== 'NONE' ? `<div class="tx-covenant" data-covenant-action="${input.coin.covenant.action}" data-covenant="${encodeURIComponent(JSON.stringify(input.coin.covenant))}">Covenant: ${input.coin.covenant.action}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -587,7 +879,7 @@
|
||||
<span class="tx-io-value">${formatValue(output.value)}</span>
|
||||
</div>
|
||||
<div class="tx-io-address">${output.address}</div>
|
||||
${output.covenant.action !== 'NONE' ? `<div class="tx-covenant">Covenant: ${output.covenant.action}</div>` : ''}
|
||||
${output.covenant.action !== 'NONE' ? `<div class="tx-covenant" data-covenant-action="${output.covenant.action}" data-covenant="${encodeURIComponent(JSON.stringify(output.covenant))}">Covenant: ${output.covenant.action}</div>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -627,6 +919,7 @@
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
if (!data.error) updateCovenants();
|
||||
}
|
||||
|
||||
// Display helper
|
||||
@@ -648,11 +941,88 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Update covenant information from API
|
||||
async function updateCovenants() {
|
||||
const elements = document.querySelectorAll('[data-covenant-action]');
|
||||
const covenantsToFetch = [];
|
||||
const elementMap = new Map(); // Map JSON string -> Array of elements
|
||||
|
||||
for (const el of elements) {
|
||||
const action = el.dataset.covenantAction;
|
||||
if (action === 'NONE') continue;
|
||||
|
||||
// Skip if already updated
|
||||
if (el.dataset.covenantUpdated) continue;
|
||||
|
||||
// Get full covenant data
|
||||
let covenantData = null;
|
||||
if (el.dataset.covenant) {
|
||||
try {
|
||||
covenantData = JSON.parse(decodeURIComponent(el.dataset.covenant));
|
||||
} catch (e) {
|
||||
console.error('Failed to parse covenant data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!covenantData) continue;
|
||||
|
||||
const key = JSON.stringify(covenantData);
|
||||
if (!elementMap.has(key)) {
|
||||
elementMap.set(key, []);
|
||||
covenantsToFetch.push(covenantData);
|
||||
}
|
||||
elementMap.get(key).push(el);
|
||||
}
|
||||
|
||||
if (covenantsToFetch.length === 0) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/v1/covenant`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(covenantsToFetch)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const results = await res.json();
|
||||
|
||||
for (const result of results) {
|
||||
const key = JSON.stringify(result.covenant);
|
||||
const els = elementMap.get(key);
|
||||
|
||||
if (els) {
|
||||
for (const el of els) {
|
||||
if (el.classList.contains('tx-covenant')) {
|
||||
el.innerHTML = `Covenant: ${result.display}`;
|
||||
} else {
|
||||
el.innerHTML = result.display;
|
||||
}
|
||||
el.dataset.covenantUpdated = "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch covenant info:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading animation
|
||||
function showLoading(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
element.style.display = 'block';
|
||||
element.innerHTML = `
|
||||
<div class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">Lighing Fire...</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Load status on page load
|
||||
async function loadStatus() {
|
||||
const nodeStatus = await apiCall('status');
|
||||
displayResult('node-status', nodeStatus, 'status');
|
||||
|
||||
const chainStatus = await apiCall('chain');
|
||||
if (chainStatus.chain) {
|
||||
document.getElementById('chain-status').innerHTML = formatChainData(chainStatus.chain);
|
||||
@@ -675,6 +1045,8 @@
|
||||
alert('Please enter a block height or hash');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('block-result');
|
||||
updateURL('block', blockId);
|
||||
const data = await apiCall(`block/${blockId}`);
|
||||
|
||||
@@ -693,6 +1065,8 @@
|
||||
alert('Please enter a block height or hash');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('block-result');
|
||||
updateURL('header', blockId);
|
||||
const data = await apiCall(`header/${blockId}`);
|
||||
|
||||
@@ -711,29 +1085,59 @@
|
||||
alert('Please enter a transaction ID');
|
||||
return;
|
||||
}
|
||||
|
||||
const resultElement = document.getElementById('tx-result');
|
||||
showLoading('tx-result');
|
||||
|
||||
// Scroll to the search section
|
||||
document.querySelector('.search-section').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
updateURL('tx', txId);
|
||||
const data = await apiCall(`tx/${txId}`);
|
||||
|
||||
// Use formatted display instead of raw JSON
|
||||
const resultElement = document.getElementById('tx-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
resultElement.innerHTML = formatTransactionData(data);
|
||||
updateCovenants();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchAddressTx() {
|
||||
const address = document.getElementById('address-input').value.trim().replace(/,/g, '');
|
||||
let address = document.getElementById('address-input').value.trim().replace(/,/g, '');
|
||||
if (!address) {
|
||||
alert('Please enter an address');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('address-result');
|
||||
const resultElement = document.getElementById('address-result');
|
||||
|
||||
// Check for HIP02 alias
|
||||
if (address.startsWith('@')) {
|
||||
const hip02Result = await resolveHip02(address);
|
||||
if (hip02Result.success && hip02Result.address) {
|
||||
address = hip02Result.address;
|
||||
// Update input to show resolved address
|
||||
document.getElementById('address-input').value = address;
|
||||
// Add a note about resolution
|
||||
const note = document.createElement('div');
|
||||
note.className = 'success-message';
|
||||
note.style.marginBottom = '1rem';
|
||||
note.innerHTML = `Resolved <strong>${hip02Result.name || address}</strong> to address <br><span class="mono">${address}</span>`;
|
||||
resultElement.parentNode.insertBefore(note, resultElement);
|
||||
setTimeout(() => note.remove(), 5000);
|
||||
} else {
|
||||
resultElement.innerHTML = `<div class="error">HIP02 Error: ${hip02Result.error || 'Could not resolve alias'}</div>`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateURL('address', address);
|
||||
const data = await apiCall(`tx/address/${address}`);
|
||||
|
||||
// Use formatted display instead of raw JSON
|
||||
const resultElement = document.getElementById('address-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
@@ -742,20 +1146,37 @@
|
||||
}
|
||||
|
||||
async function searchAddressCoins() {
|
||||
const address = document.getElementById('address-input').value.trim().replace(/,/g, '');
|
||||
let address = document.getElementById('address-input').value.trim().replace(/,/g, '');
|
||||
if (!address) {
|
||||
alert('Please enter an address');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('address-result');
|
||||
const resultElement = document.getElementById('address-result');
|
||||
|
||||
// Check for HIP02 alias
|
||||
if (address.startsWith('@')) {
|
||||
const hip02Result = await resolveHip02(address);
|
||||
if (hip02Result.success && hip02Result.address) {
|
||||
address = hip02Result.address;
|
||||
// Update input to show resolved address
|
||||
document.getElementById('address-input').value = address;
|
||||
} else {
|
||||
resultElement.innerHTML = `<div class="error">HIP02 Error: ${hip02Result.error || 'Could not resolve alias'}</div>`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateURL('address', address);
|
||||
const data = await apiCall(`coin/address/${address}`);
|
||||
|
||||
// Use formatted display instead of raw JSON
|
||||
const resultElement = document.getElementById('address-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
resultElement.innerHTML = formatAddressCoins(data);
|
||||
updateCovenants();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,9 +1186,19 @@
|
||||
alert('Please enter a name');
|
||||
return;
|
||||
}
|
||||
updateURL('name', name);
|
||||
const data = await apiCall(`name/${name}`);
|
||||
displayResult('name-result', data);
|
||||
|
||||
showLoading('name-result');
|
||||
const punyName = toPunycode(name);
|
||||
updateURL('name', punyName);
|
||||
const data = await apiCall(`name/${punyName}`);
|
||||
|
||||
// Use formatted display
|
||||
const resultElement = document.getElementById('name-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
resultElement.innerHTML = formatNameInfo(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchNameResource() {
|
||||
@@ -776,8 +1207,18 @@
|
||||
alert('Please enter a name');
|
||||
return;
|
||||
}
|
||||
const data = await apiCall(`nameresource/${name}`);
|
||||
displayResult('name-result', data);
|
||||
|
||||
showLoading('name-result');
|
||||
const punyName = toPunycode(name);
|
||||
const data = await apiCall(`nameresource/${punyName}`);
|
||||
|
||||
// Use formatted display
|
||||
const resultElement = document.getElementById('name-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
resultElement.innerHTML = formatNameResource(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchNameSummary() {
|
||||
@@ -786,8 +1227,18 @@
|
||||
alert('Please enter a name');
|
||||
return;
|
||||
}
|
||||
const data = await apiCall(`namesummary/${name}`);
|
||||
displayResult('name-result', data);
|
||||
|
||||
showLoading('name-result');
|
||||
const punyName = toPunycode(name);
|
||||
const data = await apiCall(`namesummary/${punyName}`);
|
||||
|
||||
// Use formatted display
|
||||
const resultElement = document.getElementById('name-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
resultElement.innerHTML = formatNameSummary(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchNameHash() {
|
||||
@@ -796,26 +1247,25 @@
|
||||
alert('Please enter a name hash');
|
||||
return;
|
||||
}
|
||||
const data = await apiCall(`namehash/${nameHash}`);
|
||||
displayResult('name-result', data);
|
||||
}
|
||||
|
||||
async function searchCoin() {
|
||||
const coinHash = document.getElementById('coin-hash').value.trim().replace(/,/g, '');
|
||||
const coinIndex = document.getElementById('coin-index').value.trim().replace(/,/g, '');
|
||||
if (!coinHash || !coinIndex) {
|
||||
alert('Please enter both coin hash and index');
|
||||
return;
|
||||
}
|
||||
updateURL('coin', `${coinHash}/${coinIndex}`);
|
||||
const data = await apiCall(`coin/${coinHash}/${coinIndex}`);
|
||||
showLoading('name-result');
|
||||
|
||||
// Use formatted display instead of raw JSON
|
||||
const resultElement = document.getElementById('coin-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else {
|
||||
resultElement.innerHTML = formatCoin(data);
|
||||
try {
|
||||
const response = await fetch(`/api/v1/namehash/${nameHash}`);
|
||||
const data = await response.json();
|
||||
|
||||
const resultElement = document.getElementById('name-result');
|
||||
if (data.error) {
|
||||
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
|
||||
} else if (data.name) {
|
||||
// Valid name found, redirect to name page
|
||||
window.location.href = `/name/${data.name}`;
|
||||
} else {
|
||||
resultElement.innerHTML = `<div class="error">No name found for this hash</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
const resultElement = document.getElementById('name-result');
|
||||
resultElement.innerHTML = `<div class="error">Error: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
225
tools.py
Normal file
225
tools.py
Normal file
@@ -0,0 +1,225 @@
|
||||
import dns.resolver
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
import tempfile
|
||||
import subprocess
|
||||
import binascii
|
||||
import datetime
|
||||
import dns.asyncresolver
|
||||
import dns.message
|
||||
import dns.query
|
||||
import dns.rdatatype
|
||||
import httpx
|
||||
from requests_doh import DNSOverHTTPSSession, add_dns_provider
|
||||
import urllib3
|
||||
from cryptography.x509.oid import ExtensionOID
|
||||
|
||||
urllib3.disable_warnings(
|
||||
urllib3.exceptions.InsecureRequestWarning
|
||||
) # Disable insecure request warnings (since we are manually verifying the certificate)
|
||||
|
||||
|
||||
def hip2(domain: str) -> str | None:
|
||||
domain_check = False
|
||||
try:
|
||||
# Get the IP
|
||||
ip = resolve_with_doh(domain)
|
||||
|
||||
# Run the openssl s_client command
|
||||
s_client_command = [
|
||||
"openssl",
|
||||
"s_client",
|
||||
"-showcerts",
|
||||
"-connect",
|
||||
f"{ip}:443",
|
||||
"-servername",
|
||||
domain,
|
||||
]
|
||||
|
||||
s_client_process = subprocess.Popen(
|
||||
s_client_command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
)
|
||||
s_client_output, _ = s_client_process.communicate(input=b"\n")
|
||||
|
||||
certificates = []
|
||||
current_cert = ""
|
||||
for line in s_client_output.split(b"\n"):
|
||||
current_cert += line.decode("utf-8") + "\n"
|
||||
if "-----END CERTIFICATE-----" in line.decode("utf-8"):
|
||||
certificates.append(current_cert)
|
||||
current_cert = ""
|
||||
|
||||
# Remove anything before -----BEGIN CERTIFICATE-----
|
||||
certificates = [
|
||||
cert[cert.find("-----BEGIN CERTIFICATE-----") :] for cert in certificates
|
||||
]
|
||||
|
||||
if certificates:
|
||||
cert = certificates[0]
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_cert_file:
|
||||
temp_cert_file.write(cert)
|
||||
temp_cert_file.seek(
|
||||
0
|
||||
) # Move back to the beginning of the temporary file
|
||||
|
||||
tlsa_command = [
|
||||
"openssl",
|
||||
"x509",
|
||||
"-in",
|
||||
temp_cert_file.name,
|
||||
"-pubkey",
|
||||
"-noout",
|
||||
"|",
|
||||
"openssl",
|
||||
"pkey",
|
||||
"-pubin",
|
||||
"-outform",
|
||||
"der",
|
||||
"|",
|
||||
"openssl",
|
||||
"dgst",
|
||||
"-sha256",
|
||||
"-binary",
|
||||
]
|
||||
|
||||
tlsa_process = subprocess.Popen(
|
||||
" ".join(tlsa_command), shell=True, stdout=subprocess.PIPE
|
||||
)
|
||||
tlsa_output, _ = tlsa_process.communicate()
|
||||
|
||||
tlsa_server = "3 1 1 " + binascii.hexlify(tlsa_output).decode("utf-8")
|
||||
|
||||
# Get domains
|
||||
cert_obj = x509.load_pem_x509_certificate(
|
||||
cert.encode("utf-8"), default_backend()
|
||||
)
|
||||
|
||||
domains = []
|
||||
for ext in cert_obj.extensions:
|
||||
if ext.oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
||||
san_list = ext.value.get_values_for_type(x509.DNSName)
|
||||
domains.extend(san_list)
|
||||
|
||||
# Extract the common name (CN) from the subject
|
||||
common_name = cert_obj.subject.get_attributes_for_oid(
|
||||
x509.NameOID.COMMON_NAME
|
||||
)
|
||||
if common_name:
|
||||
if common_name[0].value not in domains:
|
||||
domains.append(common_name[0].value)
|
||||
|
||||
if domains:
|
||||
if domain in domains:
|
||||
domain_check = True
|
||||
else:
|
||||
# Check if matching wildcard domain exists
|
||||
for d in domains:
|
||||
if d.startswith("*"):
|
||||
if domain.split(".")[1:] == d.split(".")[1:]:
|
||||
domain_check = True
|
||||
break
|
||||
|
||||
expiry_date = cert_obj.not_valid_after_utc
|
||||
# Check if expiry date is past
|
||||
if expiry_date < datetime.datetime.now(datetime.timezone.utc):
|
||||
return
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
try:
|
||||
# Check for TLSA record
|
||||
tlsa = resolve_TLSA_with_doh(domain)
|
||||
|
||||
if not tlsa:
|
||||
return
|
||||
else:
|
||||
if tlsa_server == str(tlsa):
|
||||
if domain_check:
|
||||
# Get the Hip2 addresss from /.well-known/wallets/HNS
|
||||
add_dns_provider("HNSDoH", "https://au.hnsdoh.com/dns-query")
|
||||
|
||||
session = DNSOverHTTPSSession("HNSDoH")
|
||||
r = session.get(
|
||||
f"https://{domain}/.well-known/wallets/HNS", verify=False
|
||||
)
|
||||
return r.text
|
||||
else:
|
||||
return
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
print(f"Hip2: TLSA lookup/verification failed with error: {e}", flush=True)
|
||||
return
|
||||
|
||||
# Catch all exceptions
|
||||
except Exception as e:
|
||||
print(f"Hip2: Lookup failed with error: {e}", flush=True)
|
||||
return
|
||||
|
||||
|
||||
def wallet_txt(domain: str, doh_url="https://au.hnsdoh.com/dns-query") -> str | None:
|
||||
with httpx.Client() as client:
|
||||
q = dns.message.make_query(domain, dns.rdatatype.from_text("TYPE262"))
|
||||
r = dns.query.https(q, doh_url, session=client)
|
||||
|
||||
if not r.answer:
|
||||
return
|
||||
|
||||
wallet_record = None
|
||||
for ans in r.answer:
|
||||
raw = ans[0].to_wire() # type: ignore
|
||||
try:
|
||||
data = raw[1:].decode("utf-8", errors="ignore")
|
||||
except UnicodeDecodeError:
|
||||
return f"Unknown WALLET record format: {raw.hex()}"
|
||||
|
||||
if data.startswith("HNS:"):
|
||||
wallet_record = data[4:]
|
||||
break
|
||||
elif data.startswith("HNS "):
|
||||
wallet_record = data[4:]
|
||||
break
|
||||
elif data.startswith('"HNS" '):
|
||||
wallet_record = data[6:].strip('"')
|
||||
break
|
||||
return wallet_record
|
||||
|
||||
|
||||
def resolve_with_doh(query_name, doh_url="https://au.hnsdoh.com/dns-query"):
|
||||
with httpx.Client() as client:
|
||||
q = dns.message.make_query(query_name, dns.rdatatype.A)
|
||||
r = dns.query.https(q, doh_url, session=client)
|
||||
|
||||
ip = r.answer[0][0].address # type: ignore
|
||||
return ip
|
||||
|
||||
|
||||
def resolve_TLSA_with_doh(query_name, doh_url="https://au.hnsdoh.com/dns-query"):
|
||||
query_name = "_443._tcp." + query_name
|
||||
with httpx.Client() as client:
|
||||
q = dns.message.make_query(query_name, dns.rdatatype.TLSA)
|
||||
r = dns.query.https(q, doh_url, session=client)
|
||||
|
||||
tlsa = r.answer[0][0]
|
||||
return tlsa
|
||||
|
||||
|
||||
def emoji_to_punycode(emoji):
|
||||
try:
|
||||
return emoji.encode("idna").decode("ascii")
|
||||
except Exception:
|
||||
return emoji
|
||||
|
||||
|
||||
def punycode_to_emoji(punycode):
|
||||
try:
|
||||
return punycode.encode("ascii").decode("idna")
|
||||
except Exception:
|
||||
return punycode
|
||||
255
uv.lock
generated
255
uv.lock
generated
@@ -2,6 +2,19 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
@@ -20,6 +33,51 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.5.0"
|
||||
@@ -91,6 +149,62 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
@@ -100,6 +214,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/7d/c871f55054e403fdfd6b8f65fd6d1c4e147ed100d3e9f9ba1fe695403939/dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc", size = 332727, upload-time = "2024-02-18T18:48:48.952Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/a1/8c5287991ddb8d3e4662f71356d9656d91ab3a36618c3dd11b280df0d255/dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", size = 307696, upload-time = "2024-02-18T18:48:46.786Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
doh = [
|
||||
{ name = "h2" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "httpx" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.20.0"
|
||||
@@ -138,6 +268,74 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "4.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "hpack" },
|
||||
{ name = "hyperframe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "4.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperframe"
|
||||
version = "6.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.15"
|
||||
@@ -272,6 +470,24 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.23"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pysocks"
|
||||
version = "1.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.1"
|
||||
@@ -286,10 +502,11 @@ name = "python-webserver-template"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "flask" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-doh" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
@@ -300,10 +517,11 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "cryptography", specifier = ">=46.0.3" },
|
||||
{ name = "flask", specifier = ">=3.1.2" },
|
||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "requests-doh", specifier = ">=1.0.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -350,7 +568,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@@ -358,9 +576,27 @@ dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
socks = [
|
||||
{ name = "pysocks" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests-doh"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "dnspython", extra = ["doh"] },
|
||||
{ name = "requests", extra = ["socks"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/8d/d9b24a0c0975c9330bcc152af2b5c9a34fa5af0307c10366fdc27e75f24e/requests_doh-1.0.0.tar.gz", hash = "sha256:6ce8bc96245030a198ef20d2100b4dcb3b120a05a58df703f8be121a79f8f2fb", size = 13126, upload-time = "2024-09-07T13:42:55.394Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/f1/79f00f86e53510b75a14dc500286f351550cc8207c81e7de1f38072fbcac/requests_doh-1.0.0-py3-none-any.whl", hash = "sha256:eea6583b792b7d3dfde74fd28eedc2b95d6ea896368119eede31f0d6ff2c838c", size = 13879, upload-time = "2024-09-07T13:42:54.189Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -389,6 +625,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
|
||||
Reference in New Issue
Block a user