Compare commits

...

13 Commits

Author SHA1 Message Date
f70454c9a4 fix: Add openssl to dockerfile
All checks were successful
Check Code Quality / RuffCheck (push) Successful in 1m5s
Build Docker / BuildImage (push) Successful in 1m10s
2025-11-21 00:29:42 +11:00
3d5d203831 feat: Add hip02 support
All checks were successful
Build Docker / BuildImage (push) Successful in 1m0s
Check Code Quality / RuffCheck (push) Successful in 1m0s
2025-11-21 00:24:51 +11:00
16d7b9f942 feat: Add scroll when viewing a mempool tx 2025-11-20 23:53:43 +11:00
513a3ebd57 feat: Remove coin index and fix mempool lookups 2025-11-20 23:43:53 +11:00
adfa0fd4a0 feat: Add animations 2025-11-20 23:37:48 +11:00
994b5bc5bf feat: Add theming from gemini
All checks were successful
Build Docker / BuildImage (push) Successful in 39s
Check Code Quality / RuffCheck (push) Successful in 49s
2025-11-20 23:31:51 +11:00
99bf4a6a1a fix: Display unowned domains with a message
All checks were successful
Build Docker / BuildImage (push) Successful in 41s
Check Code Quality / RuffCheck (push) Successful in 51s
2025-11-20 18:31:35 +11:00
7c1c6a3a2a feat: Add support for emojis
All checks were successful
Build Docker / BuildImage (push) Successful in 34s
Check Code Quality / RuffCheck (push) Successful in 44s
2025-11-20 18:27:24 +11:00
8f3a455fbb fix: Don't add decimals for stats with block or height
All checks were successful
Build Docker / BuildImage (push) Successful in 38s
Check Code Quality / RuffCheck (push) Successful in 49s
2025-11-20 18:20:17 +11:00
6a8df7d661 fix: Names with different stats
All checks were successful
Build Docker / BuildImage (push) Successful in 37s
Check Code Quality / RuffCheck (push) Successful in 44s
2025-11-20 18:15:13 +11:00
88c3cb9280 fix: Expiry block
All checks were successful
Build Docker / BuildImage (push) Successful in 51s
Check Code Quality / RuffCheck (push) Successful in 55s
2025-11-20 18:09:21 +11:00
edd076e722 feat: Add OpenGraph info
All checks were successful
Build Docker / BuildImage (push) Successful in 33s
Check Code Quality / RuffCheck (push) Successful in 42s
2025-11-20 17:41:41 +11:00
6acdce438b feat: Add og image
All checks were successful
Build Docker / BuildImage (push) Successful in 42s
Check Code Quality / RuffCheck (push) Successful in 53s
2025-11-20 17:39:30 +11:00
11 changed files with 1291 additions and 250 deletions

View File

@@ -2,7 +2,7 @@ FROM --platform=$BUILDPLATFORM python:3.13-alpine
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Install curl for healthcheck # Install curl for healthcheck
RUN apk add --no-cache curl RUN apk add --no-cache curl openssl
# Set working directory # Set working directory
WORKDIR /app WORKDIR /app

View File

@@ -5,10 +5,11 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"cryptography>=46.0.3",
"flask>=3.1.2", "flask>=3.1.2",
"gunicorn>=23.0.0", "gunicorn>=23.0.0",
"python-dotenv>=1.2.1", "python-dotenv>=1.2.1",
"requests>=2.32.5", "requests-doh>=1.0.0",
] ]
[dependency-groups] [dependency-groups]

View File

@@ -1,5 +1,9 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv export --frozen --output-file=requirements.txt # uv export --frozen --output-file=requirements.txt
anyio==4.11.0 \
--hash=sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc \
--hash=sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4
# via httpx
blinker==1.9.0 \ blinker==1.9.0 \
--hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \ --hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \
--hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc --hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc
@@ -7,7 +11,47 @@ blinker==1.9.0 \
certifi==2025.11.12 \ certifi==2025.11.12 \
--hash=sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b \ --hash=sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b \
--hash=sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316 --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 \ cfgv==3.5.0 \
--hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 \ --hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 \
--hash=sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132 --hash=sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132
@@ -56,10 +100,62 @@ colorama==0.4.6 ; sys_platform == 'win32' \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via click # 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 \ distlib==0.4.0 \
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \ --hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d --hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
# via virtualenv # via virtualenv
dnspython==2.6.1 \
--hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \
--hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc
# via requests-doh
filelock==3.20.0 \ filelock==3.20.0 \
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \ --hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4 --hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
@@ -72,6 +168,32 @@ gunicorn==23.0.0 \
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \ --hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec --hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
# via python-webserver-template # 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 \ identify==2.6.15 \
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \ --hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf --hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
@@ -79,7 +201,10 @@ identify==2.6.15 \
idna==3.11 \ idna==3.11 \
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \
--hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902 --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902
# via requests # via
# anyio
# httpx
# requests
itsdangerous==2.2.0 \ itsdangerous==2.2.0 \
--hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
--hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173
@@ -153,6 +278,14 @@ platformdirs==4.5.0 \
pre-commit==4.4.0 \ pre-commit==4.4.0 \
--hash=sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813 \ --hash=sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813 \
--hash=sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15 --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 \ python-dotenv==1.2.1 \
--hash=sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6 \ --hash=sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6 \
--hash=sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61 --hash=sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61
@@ -188,9 +321,13 @@ pyyaml==6.0.3 \
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6
# via pre-commit # via pre-commit
requests==2.32.5 \ requests==2.32.3 \
--hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
# via requests-doh
requests-doh==1.0.0 \
--hash=sha256:6ce8bc96245030a198ef20d2100b4dcb3b120a05a58df703f8be121a79f8f2fb \
--hash=sha256:eea6583b792b7d3dfde74fd28eedc2b95d6ea896368119eede31f0d6ff2c838c
# via python-webserver-template # via python-webserver-template
ruff==0.14.5 \ ruff==0.14.5 \
--hash=sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68 \ --hash=sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68 \
@@ -212,6 +349,10 @@ ruff==0.14.5 \
--hash=sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2 \ --hash=sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2 \
--hash=sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151 \ --hash=sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151 \
--hash=sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72 --hash=sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
urllib3==2.5.0 \ urllib3==2.5.0 \
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc

View File

@@ -4,11 +4,13 @@ from flask import (
render_template, render_template,
send_from_directory, send_from_directory,
send_file, send_file,
jsonify,
) )
import os import os
import requests import requests
from datetime import datetime from datetime import datetime
import dotenv import dotenv
from tools import hip2, wallet_txt
dotenv.load_dotenv() dotenv.load_dotenv()
@@ -133,11 +135,42 @@ def catch_all(path: str):
@app.route("/api/v1/status") @app.route("/api/v1/status")
def api_status(): def api_status():
return { return jsonify(
{
"status": "ok", "status": "ok",
"service": "FireExplorer", "service": "FireExplorer",
"version": "1.0.0", "version": "1.0.0",
} }
)
@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",
}
)
wallet_record = wallet_txt(domain)
if wallet_record:
return jsonify(
{
"success": True,
"address": wallet_record,
"method": "wallet_txt",
}
)
return jsonify(
{
"success": False,
"error": "No HIP02 or WALLET record found for this domain",
}
)
# endregion # endregion

View File

@@ -6,6 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nathan.Woodburn/</title> <title>Nathan.Woodburn/</title>
<link rel="icon" href="/assets/img/favicon.png" type="image/png"> <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"> <link rel="stylesheet" href="/assets/css/404.css">
</head> </head>

View File

@@ -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 { body {
background-color: #000000; background-color: var(--bg-color);
color: #ffffff; background-image:
} radial-gradient(at 0% 0%, rgba(59, 130, 246, 0.15) 0px, transparent 50%),
h1 { radial-gradient(at 100% 0%, rgba(236, 72, 153, 0.15) 0px, transparent 50%);
font-size: 50px; color: var(--text-primary);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0; 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 { .centre {
margin-top: 10%; margin: auto;
text-align: center; text-align: center;
padding: 2rem;
} }
a { a {
color: #ffffff; color: var(--accent-color);
text-decoration: none; text-decoration: none;
transition: color 0.2s ease;
} }
a:hover { a:hover {
color: #a78bfa;
text-decoration: underline; text-decoration: underline;
} }

View File

@@ -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; margin: 0;
padding: 0; padding: 0;
@@ -5,9 +17,12 @@
} }
body { body {
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%); background-color: var(--bg-color);
color: #e0e0e0; background-image:
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; 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; line-height: 1.6;
min-height: 100vh; min-height: 100vh;
} }
@@ -20,24 +35,56 @@ body {
/* Header */ /* Header */
header { header {
background: linear-gradient(135deg, #8b2f0a 0%, #6b3d0a 100%); background: rgba(15, 23, 42, 0.8);
padding: 2rem 0; backdrop-filter: blur(12px);
text-align: center; -webkit-backdrop-filter: blur(12px);
box-shadow: 0 4px 20px rgba(255, 107, 53, 0.3); padding: 1.5rem 0;
margin: 0 0 2rem 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 { 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; margin: 0;
color: #ffffff; display: flex;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); align-items: center;
gap: 0.5rem;
}
header a:hover {
text-decoration: none;
} }
.subtitle { .subtitle {
color: rgba(255, 255, 255, 0.9); color: var(--text-secondary);
font-size: 1.2rem; font-size: 0.9rem;
margin-top: 0.5rem; font-weight: 500;
display: none; /* Hidden on mobile, shown on desktop */
}
@media (min-width: 768px) {
.subtitle {
display: block;
}
} }
/* Main Content */ /* Main Content */
@@ -45,26 +92,46 @@ main {
padding: 2rem 0; padding: 2rem 0;
} }
section {
scroll-margin-top: 140px;
}
/* Cards */ /* Cards */
.card { .card {
background: rgba(30, 30, 30, 0.8); background: var(--card-bg);
border-radius: 12px; backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 16px;
padding: 2rem; padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: var(--glass-shadow);
border: 1px solid rgba(255, 107, 53, 0.2); 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 */
}
.card:hover {
border-color: rgba(139, 92, 246, 0.3);
} }
.card h2 { .card h2 {
color: #ff6b35; color: var(--text-primary);
margin-bottom: 1.5rem; 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 { .card h3 {
color: #f7931e; color: var(--text-secondary);
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 1.3rem; font-size: 1.1rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
/* Status Section */ /* Status Section */
@@ -77,25 +144,30 @@ main {
.status-card { .status-card {
padding: 1.5rem; padding: 1.5rem;
display: flex;
flex-direction: column;
height: 100%;
} }
.status-content { .status-content {
color: #b0b0b0; color: var(--text-primary);
font-size: 0.9rem; font-size: 1.1rem;
font-weight: 500;
margin-top: auto;
} }
/* Info Grid */ /* Info Grid */
.info-grid { .info-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 0.75rem; gap: 1rem;
} }
.info-item { .info-item {
padding: 0.5rem; padding: 1rem;
background: rgba(20, 20, 20, 0.5); background: rgba(15, 23, 42, 0.4);
border-radius: 6px; border-radius: 12px;
border: 1px solid rgba(255, 107, 53, 0.15); border: 1px solid var(--card-border);
} }
.info-item.no-border { .info-item.no-border {
@@ -109,86 +181,125 @@ main {
} }
.info-item strong { .info-item strong {
color: #ff6b35; color: var(--text-secondary);
display: inline-block; display: block;
min-width: 100px; font-size: 0.8rem;
margin-bottom: 0.25rem;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
.mono { .mono {
font-family: 'Courier New', monospace; font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.85rem; font-size: 0.9rem;
word-break: break-all; word-break: break-all;
color: var(--text-primary);
} }
.view-all-btn { .view-all-btn {
display: inline-block; display: inline-flex;
padding: 0.5rem 1rem; align-items: center;
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); justify-content: center;
padding: 0.6rem 1.2rem;
background: var(--primary-gradient);
color: #ffffff !important; color: #ffffff !important;
text-decoration: none !important; text-decoration: none !important;
border-radius: 6px; border-radius: 8px;
font-weight: 600; font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease; transition: all 0.3s ease;
border: none;
cursor: pointer;
} }
.view-all-btn:hover { .view-all-btn:hover {
transform: translateY(-2px); 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 Transaction List */
.mempool-txs-container { .mempool-txs-container {
margin-top: 1rem; margin-top: 1.5rem;
} }
.mempool-header { .mempool-header {
padding: 0.75rem; padding: 0.75rem;
background: rgba(255, 107, 53, 0.1); background: rgba(139, 92, 246, 0.1);
border-radius: 6px; border-radius: 8px;
margin-bottom: 0.75rem; margin-bottom: 1rem;
color: #ff6b35; color: var(--accent-color);
font-weight: 600;
font-size: 0.9rem;
} }
.tx-list { .tx-list {
max-height: 400px; max-height: 400px;
overflow-y: auto; 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 { .tx-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.75rem; gap: 1rem;
padding: 0.5rem; padding: 0.75rem;
background: rgba(20, 20, 20, 0.5); background: rgba(15, 23, 42, 0.4);
border: 1px solid rgba(255, 107, 53, 0.15); border: 1px solid var(--card-border);
border-radius: 6px; border-radius: 8px;
margin-bottom: 0.5rem; margin-bottom: 0.75rem;
transition: all 0.2s ease;
animation: staggerFade 0.4s ease forwards;
cursor: pointer;
}
.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 { .tx-hash {
flex: 1; flex: 1;
font-size: 0.8rem; font-family: 'JetBrains Mono', monospace;
color: #b0b0b0; font-size: 0.85rem;
color: var(--text-secondary);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
} }
.tx-view-btn { .tx-view-btn {
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); background: rgba(139, 92, 246, 0.1);
color: #ffffff; color: var(--accent-color);
border: none; border: 1px solid rgba(139, 92, 246, 0.2);
border-radius: 4px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-size: 0.85rem; font-size: 0.8rem;
font-weight: 600; font-weight: 600;
transition: all 0.3s ease; transition: all 0.2s ease;
flex-shrink: 0; flex-shrink: 0;
} }
.tx-view-btn:hover { .tx-view-btn:hover {
transform: translateY(-1px); background: var(--accent-color);
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.4); color: white;
} }
/* Transaction Modal */ /* Transaction Modal */
@@ -198,23 +309,39 @@ main {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: rgba(0, 0, 0, 0.8); background: rgba(15, 23, 42, 0.9);
backdrop-filter: blur(8px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 1000; z-index: 1000;
padding: 1rem; padding: 1rem;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.tx-modal.active {
opacity: 1;
pointer-events: auto;
} }
.tx-modal-content { .tx-modal-content {
background: rgba(30, 30, 30, 0.95); background: #1e293b;
border-radius: 12px; border-radius: 16px;
max-width: 900px; max-width: 900px;
width: 100%; width: 100%;
max-height: 80vh; max-height: 85vh;
border: 1px solid rgba(255, 107, 53, 0.3); border: 1px solid var(--card-border);
display: flex; display: flex;
flex-direction: column; 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 { .tx-modal-header {
@@ -222,33 +349,30 @@ main {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 1.5rem; padding: 1.5rem;
border-bottom: 1px solid rgba(255, 107, 53, 0.2); border-bottom: 1px solid var(--card-border);
} }
.tx-modal-header h3 { .tx-modal-header h3 {
margin: 0; margin: 0;
color: #ff6b35; color: var(--text-primary);
font-size: 1.2rem;
} }
.tx-modal-close { .tx-modal-close {
background: none; background: transparent;
border: none; border: none;
color: #e0e0e0; color: var(--text-secondary);
font-size: 2rem; font-size: 1.5rem;
cursor: pointer; cursor: pointer;
padding: 0; padding: 0.5rem;
width: 2rem; border-radius: 8px;
height: 2rem; transition: all 0.2s ease;
display: flex; line-height: 1;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.3s ease;
} }
.tx-modal-close:hover { .tx-modal-close:hover {
background: rgba(255, 107, 53, 0.2); background: rgba(255, 255, 255, 0.1);
color: #ff6b35; color: var(--text-primary);
} }
.tx-modal-body { .tx-modal-body {
@@ -258,8 +382,8 @@ main {
} }
.tx-modal-body pre { .tx-modal-body pre {
color: #b0b0b0; color: var(--text-secondary);
font-family: 'Courier New', monospace; font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem; font-size: 0.9rem;
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
@@ -268,70 +392,78 @@ main {
/* Transaction Details */ /* Transaction Details */
.tx-details { .tx-details {
color: #e0e0e0; color: var(--text-primary);
} }
.tx-section { .tx-section {
margin-bottom: 1.5rem; margin-bottom: 2rem;
} }
.tx-section h4 { .tx-section h4 {
color: #ff6b35; color: var(--accent-color);
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 1.1rem; font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
.tx-io-list { .tx-io-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.75rem; gap: 1rem;
} }
.tx-io-item { .tx-io-item {
background: rgba(20, 20, 20, 0.5); background: rgba(15, 23, 42, 0.4);
border: 1px solid rgba(255, 107, 53, 0.15); border: 1px solid var(--card-border);
border-radius: 6px; border-radius: 8px;
padding: 0.75rem; padding: 1rem;
} }
.tx-io-header { .tx-io-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 0.5rem; margin-bottom: 0.75rem;
} }
.tx-io-index { .tx-io-index {
color: #ff6b35; color: var(--text-secondary);
font-weight: 600; font-size: 0.85rem;
font-size: 0.9rem; background: rgba(255, 255, 255, 0.05);
padding: 0.2rem 0.5rem;
border-radius: 4px;
} }
.tx-io-value { .tx-io-value {
color: #f7931e; color: var(--success-color);
font-weight: 600; font-weight: 600;
font-size: 1rem; font-size: 1rem;
font-family: 'JetBrains Mono', monospace;
} }
.tx-io-address { .tx-io-address {
color: #b0b0b0; color: var(--text-primary);
font-family: 'Courier New', monospace; font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem; font-size: 0.9rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
word-break: break-all; word-break: break-all;
} }
.tx-io-hash { .tx-io-hash {
color: #808080; color: var(--text-secondary);
font-size: 0.75rem; font-size: 0.8rem;
word-break: break-all; word-break: break-all;
font-family: 'JetBrains Mono', monospace;
} }
.tx-covenant { .tx-covenant {
color: #ff6b35; color: var(--accent-color);
font-size: 0.85rem; font-size: 0.85rem;
margin-top: 0.5rem; margin-top: 0.75rem;
font-style: italic; padding-top: 0.75rem;
border-top: 1px solid var(--card-border);
font-family: 'JetBrains Mono', monospace;
} }
/* Tabs */ /* Tabs */
@@ -340,38 +472,53 @@ main {
gap: 0.5rem; gap: 0.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
flex-wrap: wrap; flex-wrap: wrap;
background: rgba(15, 23, 42, 0.4);
padding: 0.5rem;
border-radius: 12px;
border: 1px solid var(--card-border);
} }
.tab-btn { .tab-btn {
background: rgba(50, 50, 50, 0.5); flex: 1;
color: #e0e0e0; background: transparent;
border: 1px solid rgba(255, 107, 53, 0.3); color: var(--text-secondary);
padding: 0.75rem 1.5rem; border: none;
padding: 0.75rem 1rem;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 0.95rem;
transition: all 0.3s ease; font-weight: 500;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
} }
.tab-btn:hover { .tab-btn:hover {
background: rgba(255, 107, 53, 0.2); color: var(--text-primary);
border-color: #ff6b35; background: rgba(255, 255, 255, 0.08);
} }
.tab-btn.active { .tab-btn.active {
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); background: var(--primary-gradient);
color: #ffffff; color: #ffffff;
border-color: #ff6b35; box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
transform: translateY(-1px);
} }
.tab-content { .tab-content {
display: none; display: none;
animation: fadeIn 0.3s ease;
} }
.tab-content.active { .tab-content.active {
display: block; display: block;
} }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Search Box */ /* Search Box */
.search-box { .search-box {
display: flex; display: flex;
@@ -383,39 +530,42 @@ main {
.search-box input { .search-box input {
flex: 1; flex: 1;
min-width: 200px; min-width: 200px;
padding: 0.75rem 1rem; padding: 0.8rem 1.2rem;
background: rgba(20, 20, 20, 0.8); background: rgba(15, 23, 42, 0.6);
border: 1px solid rgba(255, 107, 53, 0.3); border: 1px solid var(--card-border);
border-radius: 8px; border-radius: 10px;
color: #e0e0e0; color: var(--text-primary);
font-size: 1rem; font-size: 1rem;
transition: all 0.2s ease;
} }
.search-box input:focus { .search-box input:focus {
outline: none; outline: none;
border-color: #ff6b35; border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2); 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 { .search-box input::placeholder {
color: rgba(224, 224, 224, 0.5); color: var(--text-secondary);
} }
.search-box button { .search-box button {
padding: 0.75rem 1.5rem; padding: 0.8rem 1.5rem;
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); background: var(--primary-gradient);
color: #ffffff; color: #ffffff;
border: none; border: none;
border-radius: 8px; border-radius: 10px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
transition: all 0.3s ease; transition: all 0.3s ease;
white-space: nowrap;
} }
.search-box button:hover { .search-box button:hover {
transform: translateY(-2px); 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 { .search-box button:active {
@@ -423,30 +573,32 @@ main {
} }
.secondary-btn { .secondary-btn {
padding: 0.75rem 1.5rem; padding: 0.8rem 1.5rem;
background: rgba(50, 50, 50, 0.8); background: transparent;
color: #ff6b35; color: var(--text-primary);
border: 1px solid #ff6b35; border: 1px solid var(--card-border);
border-radius: 8px; border-radius: 10px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
transition: all 0.3s ease; transition: all 0.2s ease;
} }
.secondary-btn:hover { .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 */
.result-box { .result-box {
background: rgba(10, 10, 10, 0.8); background: rgba(15, 23, 42, 0.4);
border: 1px solid rgba(255, 107, 53, 0.2); border: 1px solid var(--card-border);
border-radius: 8px; border-radius: 12px;
padding: 1rem; padding: 1.5rem;
min-height: 100px; min-height: 100px;
max-height: 600px; max-height: 600px;
overflow-y: auto; overflow-y: auto;
margin-top: 1rem;
} }
.result-box:empty { .result-box:empty {
@@ -454,16 +606,20 @@ main {
} }
.result-box pre { .result-box pre {
color: #b0b0b0; color: var(--text-secondary);
font-family: 'Courier New', monospace; font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem; font-size: 0.9rem;
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
} }
.result-box .error { .result-box .error {
color: #ff4444; color: #ef4444;
font-weight: 600; font-weight: 600;
background: rgba(239, 68, 68, 0.1);
padding: 1rem;
border-radius: 8px;
border: 1px solid rgba(239, 68, 68, 0.2);
} }
/* Scrollbar */ /* Scrollbar */
@@ -472,58 +628,55 @@ main {
} }
.result-box::-webkit-scrollbar-track { .result-box::-webkit-scrollbar-track {
background: rgba(20, 20, 20, 0.5); background: rgba(15, 23, 42, 0.3);
border-radius: 4px; border-radius: 4px;
} }
.result-box::-webkit-scrollbar-thumb { .result-box::-webkit-scrollbar-thumb {
background: rgba(255, 107, 53, 0.5); background: rgba(148, 163, 184, 0.3);
border-radius: 4px; border-radius: 4px;
} }
.result-box::-webkit-scrollbar-thumb:hover { .result-box::-webkit-scrollbar-thumb:hover {
background: rgba(255, 107, 53, 0.7); background: rgba(148, 163, 184, 0.5);
} }
/* Footer */ /* Footer */
footer { footer {
background: rgba(20, 20, 20, 0.8); background: rgba(15, 23, 42, 0.8);
backdrop-filter: blur(12px);
padding: 2rem 0; padding: 2rem 0;
text-align: center; text-align: center;
margin-top: 3rem; margin-top: 4rem;
border-top: 1px solid rgba(255, 107, 53, 0.2); border-top: 1px solid var(--card-border);
} }
footer p { footer p {
color: rgba(224, 224, 224, 0.7); color: var(--text-secondary);
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.timestamp { .timestamp {
font-size: 0.9rem; font-size: 0.85rem;
color: rgba(224, 224, 224, 0.5); opacity: 0.7;
} }
/* Links */ /* Links */
a { a {
color: #ff6b35; color: var(--accent-color);
text-decoration: none; text-decoration: none;
transition: color 0.3s ease; transition: color 0.2s ease;
} }
a:hover { a:hover {
color: #f7931e; color: #a78bfa;
text-decoration: underline; text-decoration: underline;
} }
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
header h1 { header h1 {
font-size: 2rem; font-size: 1.5rem;
}
.subtitle {
font-size: 1rem;
} }
.card { .card {
@@ -542,6 +695,11 @@ a:hover {
.status-section { .status-section {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.tab-btn {
padding: 0.5rem;
font-size: 0.85rem;
}
} }
/* Loading Animation */ /* Loading Animation */
@@ -557,4 +715,87 @@ a:hover {
.status-content:empty::after { .status-content:empty::after {
content: 'Loading...'; content: 'Loading...';
animation: pulse 1.5s ease-in-out infinite; 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

@@ -4,33 +4,43 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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="icon" href="/assets/img/favicon.png" type="image/png">
<link rel="stylesheet" href="/assets/css/index.css"> <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> </head>
<body> <body>
<header> <header>
<div class="container"> <div class="container">
<h1><img src="/assets/img/favicon.png" alt="Fire Icon" style="height: 1em; vertical-align: middle;"> Fire Explorer</h1> <div class="brand">
<p class="subtitle">Handshake Blockchain Explorer</p> <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> </div>
</header> </header>
<main class="container"> <main class="container">
<!-- 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>
<!-- Search Section --> <!-- Search Section -->
<section class="search-section"> <section class="search-section">
<div class="card"> <div class="card">
@@ -68,6 +78,9 @@
<button onclick="searchAddressTx()">Get Transactions</button> <button onclick="searchAddressTx()">Get Transactions</button>
<button onclick="searchAddressCoins()">Get Coins</button> <button onclick="searchAddressCoins()">Get Coins</button>
</div> </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 id="address-result" class="result-box"></div>
</div> </div>
@@ -88,16 +101,16 @@
</div> </div>
</section> </section>
<!-- Coin Lookup --> <!-- Status Cards -->
<section class="additional-section"> <section class="status-section">
<div class="card"> <div class="card status-card">
<h2>Coin Lookup</h2> <h3>Chain Info</h3>
<div class="search-box"> <div id="chain-status" class="status-content">Loading...</div>
<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>
<div id="coin-result" class="result-box"></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> </div>
</section> </section>
</main> </main>
@@ -165,11 +178,6 @@
searchName(); searchName();
break; 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();
} }
} }
@@ -193,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 // Format chain data nicely
function formatChainData(chain) { function formatChainData(chain) {
return ` return `
@@ -209,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 // Format mempool data nicely
function formatMempoolData(mempool) { function formatMempoolData(mempool) {
// Check if mempool is an array of transaction IDs // Check if mempool is an array of transaction IDs
@@ -224,9 +271,8 @@
</div> </div>
<div class="tx-list"> <div class="tx-list">
${mempool.map(txId => ` ${mempool.map(txId => `
<div class="tx-item"> <div class="tx-item" onclick="openTx('${txId}')">
<span class="tx-hash mono">${txId}</span> <span class="tx-hash mono">${txId}</span>
<button class="tx-view-btn" onclick="window.location.href='/tx/${txId}'">View</button>
</div> </div>
`).join('')} `).join('')}
</div> </div>
@@ -529,6 +575,11 @@
const formatValue = (value) => (value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS'; const formatValue = (value) => (value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
const formatDate = (timestamp) => new Date(timestamp * 1000).toLocaleString(); 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 = ` let html = `
<div class="tx-details"> <div class="tx-details">
<div class="tx-section"> <div class="tx-section">
@@ -540,7 +591,10 @@
<div class="info-item"><strong>Value:</strong> ${formatValue(info.value)}</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>Highest Bid:</strong> ${formatValue(info.highest)}</div>
<div class="info-item"><strong>Renewals:</strong> ${info.renewals}</div> <div class="info-item"><strong>Renewals:</strong> ${info.renewals}</div>
<div class="info-item"><strong>Next Renewal:</strong> Height ${info.renewal.toLocaleString()}</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>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>Revoked:</strong> ${info.revoked > 0 ? 'Yes' : 'No'}</div>
<div class="info-item"><strong>Weak:</strong> ${info.weak ? 'Yes' : 'No'}</div> <div class="info-item"><strong>Weak:</strong> ${info.weak ? 'Yes' : 'No'}</div>
@@ -549,22 +603,42 @@
${info.stats ? ` ${info.stats ? `
<div class="tx-section"> <div class="tx-section">
<h4>Expiration Info</h4> <h4>Additional Info</h4>
<div class="info-grid"> <div class="info-grid">
<div class="info-item"><strong>Blocks Until Expire:</strong> ${info.stats.blocksUntilExpire.toLocaleString()}</div> ${Object.entries(info.stats).map(([key, value]) => {
<div class="info-item"><strong>Days Until Expire:</strong> ${info.stats.daysUntilExpire.toFixed(2)}</div> // Format the key from camelCase to Title Case
<div class="info-item"><strong>Renewal Period Start:</strong> ${info.stats.renewalPeriodStart.toLocaleString()}</div> const formattedKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
<div class="info-item"><strong>Renewal Period End:</strong> ${info.stats.renewalPeriodEnd.toLocaleString()}</div>
// 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> </div>
` : ''} ` : ''}
<div class="tx-section"> <div class="tx-section">
<h4>Owner</h4> <h4>Owner</h4>
<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/tx/${info.owner.hash}'"> ${ 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 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 style="color: #b0b0b0; margin-top: 0.5rem;">Output Index: ${info.owner.index}</div>
</div> </div>
`}
</div> </div>
<div class="tx-section"> <div class="tx-section">
@@ -866,6 +940,18 @@
} }
} }
// 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 // Load status on page load
async function loadStatus() { async function loadStatus() {
const chainStatus = await apiCall('chain'); const chainStatus = await apiCall('chain');
@@ -890,6 +976,8 @@
alert('Please enter a block height or hash'); alert('Please enter a block height or hash');
return; return;
} }
showLoading('block-result');
updateURL('block', blockId); updateURL('block', blockId);
const data = await apiCall(`block/${blockId}`); const data = await apiCall(`block/${blockId}`);
@@ -908,6 +996,8 @@
alert('Please enter a block height or hash'); alert('Please enter a block height or hash');
return; return;
} }
showLoading('block-result');
updateURL('header', blockId); updateURL('header', blockId);
const data = await apiCall(`header/${blockId}`); const data = await apiCall(`header/${blockId}`);
@@ -926,11 +1016,17 @@
alert('Please enter a transaction ID'); alert('Please enter a transaction ID');
return; 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); updateURL('tx', txId);
const data = await apiCall(`tx/${txId}`); const data = await apiCall(`tx/${txId}`);
// Use formatted display instead of raw JSON // Use formatted display instead of raw JSON
const resultElement = document.getElementById('tx-result');
if (data.error) { if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`; resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else { } else {
@@ -939,16 +1035,39 @@
} }
async function searchAddressTx() { 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) { if (!address) {
alert('Please enter an address'); alert('Please enter an address');
return; 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 alias <strong>${hip02Result.name || address}</strong> to address`;
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); updateURL('address', address);
const data = await apiCall(`tx/address/${address}`); const data = await apiCall(`tx/address/${address}`);
// Use formatted display instead of raw JSON // Use formatted display instead of raw JSON
const resultElement = document.getElementById('address-result');
if (data.error) { if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`; resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else { } else {
@@ -957,16 +1076,32 @@
} }
async function searchAddressCoins() { 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) { if (!address) {
alert('Please enter an address'); alert('Please enter an address');
return; 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); updateURL('address', address);
const data = await apiCall(`coin/address/${address}`); const data = await apiCall(`coin/address/${address}`);
// Use formatted display instead of raw JSON // Use formatted display instead of raw JSON
const resultElement = document.getElementById('address-result');
if (data.error) { if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`; resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else { } else {
@@ -980,8 +1115,11 @@
alert('Please enter a name'); alert('Please enter a name');
return; return;
} }
updateURL('name', name);
const data = await apiCall(`name/${name}`); showLoading('name-result');
const punyName = toPunycode(name);
updateURL('name', punyName);
const data = await apiCall(`name/${punyName}`);
// Use formatted display // Use formatted display
const resultElement = document.getElementById('name-result'); const resultElement = document.getElementById('name-result');
@@ -998,7 +1136,10 @@
alert('Please enter a name'); alert('Please enter a name');
return; return;
} }
const data = await apiCall(`nameresource/${name}`);
showLoading('name-result');
const punyName = toPunycode(name);
const data = await apiCall(`nameresource/${punyName}`);
// Use formatted display // Use formatted display
const resultElement = document.getElementById('name-result'); const resultElement = document.getElementById('name-result');
@@ -1015,7 +1156,10 @@
alert('Please enter a name'); alert('Please enter a name');
return; return;
} }
const data = await apiCall(`namesummary/${name}`);
showLoading('name-result');
const punyName = toPunycode(name);
const data = await apiCall(`namesummary/${punyName}`);
// Use formatted display // Use formatted display
const resultElement = document.getElementById('name-result'); const resultElement = document.getElementById('name-result');
@@ -1032,6 +1176,8 @@
alert('Please enter a name hash'); alert('Please enter a name hash');
return; return;
} }
showLoading('name-result');
const data = await apiCall(`namehash/${nameHash}`); const data = await apiCall(`namehash/${nameHash}`);
// Check if result is valid and redirect to name page // Check if result is valid and redirect to name page
@@ -1046,25 +1192,6 @@
} }
} }
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}`);
// 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);
}
}
// Load status when page loads // Load status when page loads
window.addEventListener('load', () => { window.addEventListener('load', () => {
loadStatus(); loadStatus();

225
tools.py Normal file
View 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
View File

@@ -2,6 +2,19 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.13" 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]] [[package]]
name = "blinker" name = "blinker"
version = "1.9.0" 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" }, { 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]] [[package]]
name = "cfgv" name = "cfgv"
version = "3.5.0" 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" }, { 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]] [[package]]
name = "distlib" name = "distlib"
version = "0.4.0" 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" }, { 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]] [[package]]
name = "filelock" name = "filelock"
version = "3.20.0" 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" }, { 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]] [[package]]
name = "identify" name = "identify"
version = "2.6.15" 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" }, { 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]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.2.1" version = "1.2.1"
@@ -286,10 +502,11 @@ name = "python-webserver-template"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "cryptography" },
{ name = "flask" }, { name = "flask" },
{ name = "gunicorn" }, { name = "gunicorn" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "requests" }, { name = "requests-doh" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -300,10 +517,11 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "cryptography", specifier = ">=46.0.3" },
{ name = "flask", specifier = ">=3.1.2" }, { name = "flask", specifier = ">=3.1.2" },
{ name = "gunicorn", specifier = ">=23.0.0" }, { name = "gunicorn", specifier = ">=23.0.0" },
{ name = "python-dotenv", specifier = ">=1.2.1" }, { name = "python-dotenv", specifier = ">=1.2.1" },
{ name = "requests", specifier = ">=2.32.5" }, { name = "requests-doh", specifier = ">=1.0.0" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
@@ -350,7 +568,7 @@ wheels = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.5" version = "2.32.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "certifi" }, { name = "certifi" },
@@ -358,9 +576,27 @@ dependencies = [
{ name = "idna" }, { name = "idna" },
{ name = "urllib3" }, { 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 = [ 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]] [[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" }, { 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]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.5.0" version = "2.5.0"