瀏覽代碼

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	README.md
#	main.py
#	plat/base/base_control.py
#	plat/deepcoin_control.py
#	pyproject.toml
#	test/deepcoin/test_deepcoin_control.py
xiao.qiang 1 月之前
父節點
當前提交
87953ee275
共有 12 個文件被更改,包括 1679 次插入61 次删除
  1. 126 59
      poetry.lock
  2. 4 2
      pyproject.toml
  3. 139 0
      testcase/main-bifinance.py
  4. 136 0
      testcase/main-bittap.py
  5. 130 0
      testcase/main-deepcoin.py
  6. 201 0
      testcase/main-digifinex.py
  7. 161 0
      testcase/main-hotcoin.py
  8. 152 0
      testcase/main-ibit.py
  9. 106 0
      testcase/main-mexc.py
  10. 152 0
      testcase/main-trubit.py
  11. 177 0
      testcase/main-websea.py
  12. 195 0
      testcase/main-weex.py

+ 126 - 59
poetry.lock

@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
 
 [[package]]
 name = "adbutils"
@@ -6,7 +6,6 @@ version = "2.8.5"
 description = "Pure Python Adb Library"
 optional = false
 python-versions = ">=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "adbutils-2.8.5-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:b63e090c14fabdd8f5b7e99f97e8b72b53b18d482ca1ad0dd1afe06a1b56e84f"},
     {file = "adbutils-2.8.5-py3-none-manylinux1_x86_64.whl", hash = "sha256:72d3316f053d2e9a2aaefa824459f8cfdd3fb1770dd9b93a69100955dfa59c4e"},
@@ -27,7 +26,6 @@ version = "0.17.4"
 description = "Python graph (network) package"
 optional = false
 python-versions = "*"
-groups = ["main", "dev"]
 files = [
     {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
     {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
@@ -39,7 +37,6 @@ version = "0.7.0"
 description = "Reusable constraint types to use with typing.Annotated"
 optional = false
 python-versions = ">=3.8"
-groups = ["main"]
 files = [
     {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
     {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@@ -51,7 +48,6 @@ version = "4.9.0"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
 optional = false
 python-versions = ">=3.9"
-groups = ["main"]
 files = [
     {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
     {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
@@ -68,13 +64,23 @@ doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)",
 test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
 trio = ["trio (>=0.26.1)"]
 
+[[package]]
+name = "async-timeout"
+version = "5.0.1"
+description = "Timeout context manager for asyncio programs"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
+    {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
+]
+
 [[package]]
 name = "cached-property"
 version = "1.5.2"
 description = "A decorator for caching properties in classes."
 optional = false
 python-versions = "*"
-groups = ["dev"]
 files = [
     {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
     {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"},
@@ -86,7 +92,6 @@ version = "2025.1.31"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
-groups = ["main", "dev"]
 files = [
     {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
     {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
@@ -98,7 +103,6 @@ version = "3.4.1"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 optional = false
 python-versions = ">=3.7"
-groups = ["main", "dev"]
 files = [
     {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
     {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
@@ -200,7 +204,6 @@ version = "8.1.8"
 description = "Composable command line interface toolkit"
 optional = false
 python-versions = ">=3.7"
-groups = ["main"]
 files = [
     {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
     {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
@@ -215,12 +218,10 @@ version = "0.4.6"
 description = "Cross-platform colored terminal text."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-groups = ["main", "dev"]
 files = [
     {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
 ]
-markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""}
 
 [[package]]
 name = "construct"
@@ -228,7 +229,6 @@ version = "2.10.70"
 description = "A powerful declarative symmetric parser/builder for binary data"
 optional = false
 python-versions = ">=3.6"
-groups = ["dev"]
 files = [
     {file = "construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30"},
     {file = "construct-2.10.70.tar.gz", hash = "sha256:4d2472f9684731e58cc9c56c463be63baa1447d674e0d66aeb5627b22f512c29"},
@@ -243,7 +243,6 @@ version = "5.2.1"
 description = "Decorators for Humans"
 optional = false
 python-versions = ">=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
     {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
@@ -255,7 +254,6 @@ version = "1.2.18"
 description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-groups = ["dev"]
 files = [
     {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"},
     {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"},
@@ -273,7 +271,6 @@ version = "2.1.0"
 description = "A library to handle automated deprecations"
 optional = false
 python-versions = "*"
-groups = ["main", "dev"]
 files = [
     {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
     {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
@@ -288,7 +285,6 @@ version = "1.2.2"
 description = "Backport of PEP 654 (exception groups)"
 optional = false
 python-versions = ">=3.7"
-groups = ["main", "dev"]
 files = [
     {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
     {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
@@ -303,7 +299,6 @@ version = "1.5.0"
 description = "Python Client for Facebook WebDriverAgent"
 optional = false
 python-versions = "*"
-groups = ["dev"]
 files = [
     {file = "facebook-wda-1.5.0.tar.gz", hash = "sha256:992149eaa13a2cd54715bc9ee2e3532fff9feec8ca4b5c694dab61ba775119ad"},
     {file = "facebook_wda-1.5.0-py3-none-any.whl", hash = "sha256:774d3285a2944263afb8c7340fa950ebe3f04d77a07aba61afe6e08db4057976"},
@@ -323,7 +318,6 @@ version = "0.115.12"
 description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
 optional = false
 python-versions = ">=3.8"
-groups = ["main"]
 files = [
     {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"},
     {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"},
@@ -344,7 +338,6 @@ version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
 optional = false
 python-versions = ">=3.7"
-groups = ["main"]
 files = [
     {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
     {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -356,7 +349,6 @@ version = "3.10"
 description = "Internationalized Domain Names in Applications (IDNA)"
 optional = false
 python-versions = ">=3.6"
-groups = ["main", "dev"]
 files = [
     {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
     {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -371,7 +363,6 @@ version = "2.1.0"
 description = "brain-dead simple config-ini parsing"
 optional = false
 python-versions = ">=3.8"
-groups = ["dev"]
 files = [
     {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
     {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
@@ -383,7 +374,6 @@ version = "1.7.0"
 description = "Robust and effective logging for Python 2 and 3"
 optional = false
 python-versions = "*"
-groups = ["dev"]
 files = [
     {file = "logzero-1.7.0-py2.py3-none-any.whl", hash = "sha256:23eb1f717a2736f9ab91ca0d43160fd2c996ad49ae6bad34652d47aba908769d"},
     {file = "logzero-1.7.0.tar.gz", hash = "sha256:7f73ddd3ae393457236f081ffebd044a3aa2e423a47ae6ddb5179ab90d0ad082"},
@@ -398,7 +388,6 @@ version = "5.3.1"
 description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
 optional = false
 python-versions = ">=3.6"
-groups = ["main", "dev"]
 files = [
     {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4058f16cee694577f7e4dd410263cd0ef75644b43802a689c2b3c2a7e69453b"},
     {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:364de8f57d6eda0c16dcfb999af902da31396949efa0e583e12675d09709881b"},
@@ -553,8 +542,6 @@ version = "1.16.3"
 description = "Mach-O header analysis and editing"
 optional = false
 python-versions = "*"
-groups = ["main", "dev"]
-markers = "sys_platform == \"darwin\""
 files = [
     {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
     {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
@@ -569,7 +556,6 @@ version = "2.2.4"
 description = "Fundamental package for array computing in Python"
 optional = false
 python-versions = ">=3.10"
-groups = ["main"]
 files = [
     {file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"},
     {file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"},
@@ -634,7 +620,6 @@ version = "4.11.0.86"
 description = "Wrapper package for OpenCV python bindings."
 optional = false
 python-versions = ">=3.6"
-groups = ["main"]
 files = [
     {file = "opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4"},
     {file = "opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a"},
@@ -657,7 +642,6 @@ version = "24.2"
 description = "Core utilities for Python packages"
 optional = false
 python-versions = ">=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
     {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
@@ -669,8 +653,6 @@ version = "2023.2.7"
 description = "Python PE parsing module"
 optional = false
 python-versions = ">=3.6.0"
-groups = ["main", "dev"]
-markers = "sys_platform == \"win32\""
 files = [
     {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
     {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
@@ -682,7 +664,6 @@ version = "11.1.0"
 description = "Python Imaging Library (Fork)"
 optional = false
 python-versions = ">=3.9"
-groups = ["main", "dev"]
 files = [
     {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"},
     {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"},
@@ -771,7 +752,6 @@ version = "1.5.0"
 description = "plugin and hook calling mechanisms for python"
 optional = false
 python-versions = ">=3.8"
-groups = ["dev"]
 files = [
     {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
     {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@@ -787,7 +767,6 @@ version = "1.11.0"
 description = "library with cross-python path, ini-parsing, io, code, log facilities"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-groups = ["main", "dev"]
 files = [
     {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
@@ -799,7 +778,6 @@ version = "2.10.6"
 description = "Data validation using Python type hints"
 optional = false
 python-versions = ">=3.8"
-groups = ["main"]
 files = [
     {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
     {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
@@ -820,7 +798,6 @@ version = "2.27.2"
 description = "Core functionality for Pydantic validation and serialization"
 optional = false
 python-versions = ">=3.8"
-groups = ["main"]
 files = [
     {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
     {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
@@ -933,7 +910,6 @@ version = "6.12.0"
 description = "PyInstaller bundles a Python application and all its dependencies into a single package."
 optional = false
 python-versions = "<3.14,>=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "pyinstaller-6.12.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:68f1e4cecf88a6272063977fa2a2c69ad37cf568e5901769d7206d0314c74f47"},
     {file = "pyinstaller-6.12.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fea76fc9b55ffa730fcf90beb897cce4399938460b0b6f40507fbebfc752c753"},
@@ -968,7 +944,6 @@ version = "2025.2"
 description = "Community maintained hooks for PyInstaller"
 optional = false
 python-versions = ">=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "pyinstaller_hooks_contrib-2025.2-py3-none-any.whl", hash = "sha256:0b2bc7697075de5eb071ff13ef4a156d3beae6c19c7cbdcd70f37978d2013e30"},
     {file = "pyinstaller_hooks_contrib-2025.2.tar.gz", hash = "sha256:ccdd41bc30290f725f3e48f4a39985d11855af81d614d167e3021e303acb9102"},
@@ -984,7 +959,6 @@ version = "8.3.5"
 description = "pytest: simple powerful testing with Python"
 optional = false
 python-versions = ">=3.8"
-groups = ["dev"]
 files = [
     {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
     {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
@@ -1001,14 +975,32 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""}
 [package.extras]
 dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
 
+[[package]]
+name = "python-socks"
+version = "2.7.1"
+description = "Proxy (SOCKS4, SOCKS5, HTTP CONNECT) client for Python"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+    {file = "python_socks-2.7.1-py3-none-any.whl", hash = "sha256:2603c6454eeaeb82b464ad705be188989e8cf1a4a16f0af3c921d6dd71a49cec"},
+    {file = "python_socks-2.7.1.tar.gz", hash = "sha256:f1a0bb603830fe81e332442eada96757b8f8dec02bd22d1d6f5c99a79704c550"},
+]
+
+[package.dependencies]
+async-timeout = {version = ">=4.0", optional = true, markers = "python_version < \"3.11\" and extra == \"asyncio\""}
+
+[package.extras]
+anyio = ["anyio (>=3.3.4,<5.0.0)"]
+asyncio = ["async-timeout (>=4.0)"]
+curio = ["curio (>=1.4)"]
+trio = ["trio (>=0.24)"]
+
 [[package]]
 name = "pywin32"
 version = "310"
 description = "Python for Window Extensions"
 optional = false
 python-versions = "*"
-groups = ["dev"]
-markers = "sys_platform == \"win32\""
 files = [
     {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"},
     {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"},
@@ -1034,8 +1026,6 @@ version = "0.2.3"
 description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
 optional = false
 python-versions = ">=3.6"
-groups = ["main", "dev"]
-markers = "sys_platform == \"win32\""
 files = [
     {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"},
     {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"},
@@ -1047,7 +1037,6 @@ version = "2.32.3"
 description = "Python HTTP for Humans."
 optional = false
 python-versions = ">=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
     {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -1069,7 +1058,6 @@ version = "0.9.2"
 description = "Easy to use retry decorator."
 optional = false
 python-versions = "*"
-groups = ["main", "dev"]
 files = [
     {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"},
     {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"},
@@ -1085,7 +1073,6 @@ version = "1.2.2"
 description = "Job scheduling for humans."
 optional = false
 python-versions = ">=3.7"
-groups = ["main"]
 files = [
     {file = "schedule-1.2.2-py3-none-any.whl", hash = "sha256:5bef4a2a0183abf44046ae0d164cadcac21b1db011bdd8102e4a0c1e91e06a7d"},
     {file = "schedule-1.2.2.tar.gz", hash = "sha256:15fe9c75fe5fd9b9627f3f19cc0ef1420508f9f9a46f45cd0769ef75ede5f0b7"},
@@ -1100,7 +1087,6 @@ version = "78.1.0"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 optional = false
 python-versions = ">=3.9"
-groups = ["main", "dev"]
 files = [
     {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"},
     {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"},
@@ -1121,7 +1107,6 @@ version = "1.17.0"
 description = "Python 2 and 3 compatibility utilities"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-groups = ["dev"]
 files = [
     {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
     {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
@@ -1133,7 +1118,6 @@ version = "1.3.1"
 description = "Sniff out which async library your code is running under"
 optional = false
 python-versions = ">=3.7"
-groups = ["main"]
 files = [
     {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
     {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -1145,7 +1129,6 @@ version = "0.46.1"
 description = "The little ASGI library that shines."
 optional = false
 python-versions = ">=3.9"
-groups = ["main"]
 files = [
     {file = "starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227"},
     {file = "starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230"},
@@ -1163,7 +1146,6 @@ version = "2.2.1"
 description = "A lil' TOML parser"
 optional = false
 python-versions = ">=3.8"
-groups = ["dev"]
 files = [
     {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
     {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -1205,7 +1187,6 @@ version = "6.4.2"
 description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
 optional = false
 python-versions = ">=3.8"
-groups = ["dev"]
 files = [
     {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"},
     {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"},
@@ -1226,7 +1207,6 @@ version = "4.13.0"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
-groups = ["main"]
 files = [
     {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"},
     {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"},
@@ -1238,7 +1218,6 @@ version = "3.2.9"
 description = "uiautomator for android device"
 optional = false
 python-versions = "<4.0,>=3.8"
-groups = ["main", "dev"]
 files = [
     {file = "uiautomator2-3.2.9-py3-none-any.whl", hash = "sha256:60d06148dbcb5b3dfc57a17435d43db793c078375fc394f659a7691a8e4981bd"},
     {file = "uiautomator2-3.2.9.tar.gz", hash = "sha256:0aad4c6fae2a8a69301afdcc043e966a2d6c0e786a04d7173998be3b4247d29e"},
@@ -1257,7 +1236,6 @@ version = "2.3.0"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 optional = false
 python-versions = ">=3.9"
-groups = ["main", "dev"]
 files = [
     {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
     {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
@@ -1275,7 +1253,6 @@ version = "0.34.0"
 description = "The lightning-fast ASGI server."
 optional = false
 python-versions = ">=3.9"
-groups = ["main"]
 files = [
     {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"},
     {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"},
@@ -1289,13 +1266,104 @@ typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
 [package.extras]
 standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
 
+[[package]]
+name = "websockets"
+version = "15.0.1"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"},
+    {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"},
+    {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"},
+    {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"},
+    {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"},
+    {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"},
+    {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"},
+    {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"},
+    {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"},
+    {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"},
+    {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"},
+    {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"},
+    {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"},
+    {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"},
+    {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"},
+    {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"},
+    {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"},
+    {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"},
+    {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"},
+    {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"},
+    {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"},
+    {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"},
+    {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"},
+    {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"},
+    {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"},
+    {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"},
+    {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"},
+    {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"},
+    {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"},
+    {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"},
+    {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"},
+    {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"},
+    {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"},
+    {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"},
+    {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"},
+    {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"},
+    {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"},
+    {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"},
+    {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"},
+    {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"},
+    {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"},
+    {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"},
+    {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"},
+    {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"},
+    {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"},
+    {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"},
+    {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"},
+    {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"},
+    {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"},
+    {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"},
+    {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"},
+    {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"},
+    {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"},
+    {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"},
+    {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"},
+    {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"},
+    {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"},
+]
+
+[[package]]
+name = "websockets-proxy"
+version = "0.1.3"
+description = "Connect to websockets through a proxy server."
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "websockets_proxy-0.1.3-py3-none-any.whl", hash = "sha256:07ca0b561107e4e207c00a5c86f9281c05be2a3e34aa721d01743760dc5fd067"},
+]
+
+[package.dependencies]
+python-socks = {version = "*", extras = ["asyncio"]}
+websockets = "*"
+
 [[package]]
 name = "weditor"
 version = "0.7.3"
 description = "tool for writing atx script"
 optional = false
 python-versions = ">=3.6"
-groups = ["dev"]
 files = [
     {file = "weditor-0.7.3.tar.gz", hash = "sha256:3eef54ae8901c421f5885c0963defe7879f334ee8c7f747c523c976b589053f4"},
 ]
@@ -1316,7 +1384,6 @@ version = "1.17.2"
 description = "Module for decorators, wrappers and monkey patching."
 optional = false
 python-versions = ">=3.8"
-groups = ["dev"]
 files = [
     {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"},
     {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"},
@@ -1400,6 +1467,6 @@ files = [
 ]
 
 [metadata]
-lock-version = "2.1"
+lock-version = "2.0"
 python-versions = "~3.10"
-content-hash = "c2c4e3821f75a5d78eff2f915192c73e79b15d2715bf56072ef71a4d6013b9ab"
+content-hash = "62f75d1938062b1619d813234f77c5391d7075858db4ad51eb3eba53a69b5fe6"

+ 4 - 2
pyproject.toml

@@ -17,8 +17,8 @@ opencv-python = "^4.11.0.86"
 schedule = "^1.2.2"
 
 [[tool.poetry.source]]
-name = "tsinghua"
-url = "https://pypi.tuna.tsinghua.edu.cn/simple"
+name = "aliyun"
+url = "https://mirrors.aliyun.com/pypi/simple"
 priority = "explicit"
 
 
@@ -27,6 +27,8 @@ priority = "explicit"
 pyinstaller = "^6.12.0"
 pytest = "^8.3.5"
 weditor = "^0.7.3"
+websockets = "^15.0.1"
+websockets-proxy = "^0.1.3"
 
 [build-system]
 requires = ["poetry-core"]

+ 139 - 0
testcase/main-bifinance.py

@@ -0,0 +1,139 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+
+
+async def listen_ibit_futures():
+    uri = f"wss://www.bifinance.com/wsContract/zh_cn/"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        sub = {"id": "Dz5MJY65CMSD5Q8xADpQy86PhmZAFYHr", "cmd": "sub", "topic": "alpha-market-ticker-contract", "data": {}}
+
+        sub = {"id": "B8CFPb34tXQE7QWQCNbNnbswnCkFsdzY", "cmd": "sub", "topic": "usdt-marked-price-btcusdt", "data": {}}
+
+        sub = {"id": "111", "cmd": "sub", "topic": "usdt-marked-price-ethusdt", "data": {}}
+
+        # sub = {"id": "phWjnWnbjGbGCKyyhTPCJMiC7eP5PBFc", "cmd": "sub", "topic": "alpha-market-depth-contract-btcusdt-trade", "data": {}}
+
+        # sub = {"id": "YnmB4W4mjiSTdnCPhCfBxHH34WwidKyW", "cmd": "sub", "topic": "alpha-market-ticker", "data": {}}
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = {"method": "ping"}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 检查是否是gzip压缩数据
+                    if message.startswith(b'\x1f\x8b'):
+                        try:
+                            # 解压gzip数据
+                            decompressed_data = gzip.decompress(message)
+                            message = decompressed_data.decode('utf-8')
+                        except Exception as e:
+                            print(f"解压数据失败: {e}")
+                            print(f"原始数据 (前50字节): {message[:50]}")
+                            continue
+                    else:
+                        # 尝试多种编码解码非gzip二进制数据
+                        decoded = False
+                        # 尝试UTF-8解码
+                        try:
+                            message = message.decode('utf-8')
+                            decoded = True
+                        except UnicodeDecodeError:
+                            pass
+                        
+                        # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                        if not decoded:
+                            try:
+                                message = message.decode('gbk')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+                        
+                        if not decoded:
+                            # 如果所有解码都失败,以十六进制形式显示数据
+                            print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                            continue
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                # 根据不同频道处理数据
+                if 'channel' in data:
+                    channel = data['channel']
+                    if channel == 'account' and 'data' in data:
+                        # 账户信息
+                        for account in data['data']:
+                            print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                    elif channel == 'currentPositions' and 'data' in data:
+                        # 当前持仓
+                        for position in data['data']:
+                            print(f"持仓: {position}")
+                    elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                        # 订单信息
+                        for order in data['data']:
+                            # 安全访问 'p' 字段,如果不存在则显示 N/A
+                            price = order.get('p', 'N/A')
+                            print(f"订单价格: {price}")
+                    # 其他频道可以按需添加处理逻辑
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 136 - 0
testcase/main-bittap.py

@@ -0,0 +1,136 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+
+
+async def listen_ibit_futures():
+    uri = f"wss://stream.bittap.com/endpoint?format=JSON"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        sub = {"method": "SUBSCRIBE", "params": ["f_trade@BTC-USDT-M"], "id": "fsb7qjv"}
+        sub = {"method": "SUBSCRIBE", "params": ["f_markPrice@BTC-USDT-M"], "id": "sld73t7"}
+        # sub = {"method": "SUBSCRIBE", "params": ["f_ticker@BTC-USDT-M"], "id": "pccg3m1"}
+        # sub = {"method": "SUBSCRIBE", "params": ["f_depth30@BTC-USDT-M_0.1"], "id": "3fst1jg"}
+        # sub = {"method": "SUBSCRIBE", "params": ["f_kline_15m@BTC-USDT-M"], "id": "dhhn9h9"}
+        # sub = {"method": "UNSUBSCRIBE", "params": ["f_kline_15m@BTC-USDT-M"]}
+        # sub = {"method": "SUBSCRIBE", "params": ["f_kline_15m@BTC-USDT-M"], "id": "el13nfi"}
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = {"id": "a88y2z7", "method": "PING"}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 检查是否是gzip压缩数据
+                    if message.startswith(b'\x1f\x8b'):
+                        try:
+                            # 解压gzip数据
+                            decompressed_data = gzip.decompress(message)
+                            message = decompressed_data.decode('utf-8')
+                        except Exception as e:
+                            print(f"解压数据失败: {e}")
+                            print(f"原始数据 (前50字节): {message[:50]}")
+                            continue
+                    else:
+                        # 尝试多种编码解码非gzip二进制数据
+                        decoded = False
+                        # 尝试UTF-8解码
+                        try:
+                            message = message.decode('utf-8')
+                            decoded = True
+                        except UnicodeDecodeError:
+                            pass
+
+                        # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                        if not decoded:
+                            try:
+                                message = message.decode('gbk')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                        if not decoded:
+                            # 如果所有解码都失败,以十六进制形式显示数据
+                            print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                            continue
+
+                print(f"接收到消息: {message}")
+                print("====================================================================")
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    # print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                # if 'channel' in data and data['channel'] == 'pong':
+                #     continue
+                #
+                # # 根据不同频道处理数据
+                # if 'channel' in data:
+                #     channel = data['channel']
+                #     if channel == 'account' and 'data' in data:
+                #         # 账户信息
+                #         for account in data['data']:
+                #             print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                #     elif channel == 'currentPositions' and 'data' in data:
+                #         # 当前持仓
+                #         for position in data['data']:
+                #             print(f"持仓: {position}")
+                #     elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                #         # 订单信息
+                #         for order in data['data']:
+                #             # 安全访问 'p' 字段,如果不存在则显示 N/A
+                #             price = order.get('p', 'N/A')
+                #             print(f"订单价格: {price}")
+                #     # 其他频道可以按需添加处理逻辑
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 130 - 0
testcase/main-deepcoin.py

@@ -0,0 +1,130 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+
+
+async def listen_ibit_futures():
+    uri = f"wss://net-wss.deepcoin.com/streamlet/trade/public/swap?device=pc-0748a44d11158f4f2fd1fda01d1775df&platform=pc&isStreamlet=true"
+    # uri = f"wss://net-wss.deepcoin.com/business/public?platform=pc"
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 添加完整的请求头以模拟浏览器请求
+    extra_headers = {
+        # "Upgrade": "websocket",
+        # "Origin": "https://www.deepcoin.com",
+        # "Cache-Control": "no-cache",
+        # "Accept-Language": "zh-CN,zh;q=0.9",
+        # "Pragma": "no-cache",
+        # "Connection": "Upgrade",
+        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
+        # "Sec-WebSocket-Version": "13",
+        # "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits"
+    }
+
+    # 使用代理连接,并添加请求头
+    async with proxy_connect(uri, proxy=proxy, extra_headers=extra_headers) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        sub = {"SendTopicAction": {"Action": "2", "FilterValue": "DeepCoin_BTCUSDT", "LocalNo": 9, "ResumeNo": -2, "TopicID": "7"}}
+        sub = {"SendTopicAction": {"Action": "1", "FilterValue": "DeepCoin_BTCUSDT", "LocalNo": 9, "ResumeNo": -2, "TopicID": "7"}}
+        # sub = {"SendTopicAction": {"Action": "1", "FilterValue": "DeepCoin_BTCUSDT", "LocalNo": 5, "ResumeNo": -30, "TopicID": "2"}}
+        # sub = {"SendTopicAction": {"Action": "2", "FilterValue": "DeepCoin_BTCUSDT_0.1", "LocalNo": 4, "ResumeNo": -2, "TopicID": "25"}}
+        # sub = {"SendTopicAction": {"Action": "1", "FilterValue": "DeepCoin_BTCUSDT_0.1", "LocalNo": 4, "ResumeNo": -2, "TopicID": "25"}}
+        # sub = {"SendTopicAction": {"Action": "2", "FilterValue": "DeepCoin_BTCUSDT_15m", "LocalNo": 6, "ResumeNo": -50, "TopicID": "11"}}
+        # sub = {"SendTopicAction": {"Action": "1", "FilterValue": "DeepCoin_BTCUSDT_15m", "LocalNo": 6, "ResumeNo": -50, "TopicID": "11"}}
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = "ping"
+                await websocket.send(heartbeat_message)
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(10)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 检查是否是gzip压缩数据
+                    if message.startswith(b'\x1f\x8b'):
+                        try:
+                            # 解压gzip数据
+                            decompressed_data = gzip.decompress(message)
+                            message = decompressed_data.decode('utf-8')
+                        except Exception as e:
+                            print(f"解压数据失败: {e}")
+                            print(f"原始数据 (前50字节): {message[:50]}")
+                            continue
+                    else:
+                        # 尝试多种编码解码非gzip二进制数据
+                        decoded = False
+                        # 尝试UTF-8解码
+                        try:
+                            message = message.decode('utf-8')
+                            decoded = True
+                        except UnicodeDecodeError:
+                            pass
+
+                        # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                        if not decoded:
+                            try:
+                                message = message.decode('gbk')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                        if not decoded:
+                            # 如果所有解码都失败,以十六进制形式显示数据
+                            print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                            continue
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 201 - 0
testcase/main-digifinex.py

@@ -0,0 +1,201 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+import zlib
+
+
+async def listen_ibit_futures():
+    uri = f"wss://api.digifinex.com/swap_ws/v2/"
+    uri = f"wss://api.digifinex.com/ws?access_token=J9oxYwf86pX7kbtwFaUbPzeOYE8HGayG&user_msg=true"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        sub = {"id": "701441", "event": "trades.subscribe", "instrument_id": "BTCUSDTPERP"}
+
+        sub = {"id": "362033", "event": "ticker.subscribe", "instrument_id": "BTCUSDTPERP"}
+
+        sub = {"id": "265011", "event": "price_range.subscribe", "instrument_id": "BTCUSDTPERP"}
+
+        sub = {"id": "889274", "event": "index_price.subscribe", "instrument_id": "BTCUSDTPERP"}
+        sub = {"id": "375625", "event": "fund_rate.subscribe", "instrument_id": "BTCUSDTPERP"}
+        sub = {"id": "299202", "event": "depth.subscribe", "level": 100, "instrument_id": "BTCUSDTPERP"}
+        sub = {"id": "220604", "event": "all_ticker.subscribe"}
+        sub = {"id": "667725", "event": "cur_candle.subscribe", "instrument_id": "BTCUSDTPERP", "granularity": "15m"}
+        # sub = {"id": "605176", "event": "mark_price.subscribe", "instrument_id": "BTCUSDTPERP"}
+
+        # sub = {"id": "phWjnWnbjGbGCKyyhTPCJMiC7eP5PBFc", "cmd": "sub", "topic": "alpha-market-depth-contract-btcusdt-trade", "data": {}}
+
+        # sub = {"id": "YnmB4W4mjiSTdnCPhCfBxHH34WwidKyW", "cmd": "sub", "topic": "alpha-market-ticker", "data": {}}
+
+        sub = {"action": "USER_TRADE_TICK", "data": {"currency_id": 30, "base_currency_id": 104, "get_orders": 1, "get_balance": 1, "get_asset_trade": 1, "get_positions": 1}}
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = {"id": "921848", "method": "server.ping", "params": []}
+                heartbeat_message ={"action":"ping","data":{}}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(10)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 首先尝试直接解码为UTF-8,可能不是压缩数据
+                    try:
+                        message = message.decode('utf-8')
+                    except UnicodeDecodeError:
+                        # 如果直接解码失败,再尝试解压
+                        # 检查是否是gzip压缩数据
+                        if message.startswith(b'\x1f\x8b'):
+                            try:
+                                # 解压gzip数据
+                                decompressed_data = gzip.decompress(message)
+                                message = decompressed_data.decode('utf-8')
+                            except Exception as e:
+                                print(f"gzip解压失败: {e}")
+                                print(f"原始数据 (前100字节): {message[:100].hex()}")
+                                continue
+                        # 检查是否是deflate压缩数据(以78da开头)
+                        elif message.startswith(b'\x78\xda') or message.startswith(b'\x78\x9c') or message.startswith(b'\x78\x01'):
+                            # 尝试多种deflate解压方法
+                            decompressed_success = False
+
+                            # 方法1: 使用zlib.decompress和-wbits参数
+                            # if not decompressed_success:
+                            #     try:
+                            #         decompressed_data = zlib.decompress(message, -zlib.MAX_WBITS)
+                            #         message = decompressed_data.decode('utf-8')
+                            #         decompressed_success = True
+                            #     except Exception as e:
+                            #         print(f"deflate解压方法1失败: {e}")
+                            #
+                            # # 方法2: 使用zlib.decompress和默认wbits参数
+                            # if not decompressed_success:
+                            try:
+                                decompressed_data = zlib.decompress(message)
+                                message = decompressed_data.decode('utf-8')
+                                decompressed_success = True
+                            except Exception as e:
+                                print(f"deflate解压方法2失败: {e}")
+                            #
+                            # # 方法3: 使用zlib.decompress和+16参数(gzip格式)
+                            # if not decompressed_success:
+                            #     try:
+                            #         decompressed_data = zlib.decompress(message, zlib.MAX_WBITS | 16)
+                            #         message = decompressed_data.decode('utf-8')
+                            #         decompressed_success = True
+                            #     except Exception as e:
+                            #         print(f"deflate解压方法3失败: {e}")
+                            #
+                            # # 如果所有方法都失败,打印原始数据
+                            # if not decompressed_success:
+                            #     print(f"deflate解压失败: Error -3 while decompressing data: invalid stored block lengths")
+                            #     print(f"原始数据 (前100字节): {message[:100].hex()}")
+                            #     print(f"数据长度: {len(message)}")
+                            #     continue
+                        else:
+                            # 尝试多种编码解码非gzip二进制数据
+                            decoded = False
+                            # 尝试UTF-8解码
+                            try:
+                                message = message.decode('utf-8')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                            # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                            if not decoded:
+                                try:
+                                    message = message.decode('gbk')
+                                    decoded = True
+                                except UnicodeDecodeError:
+                                    pass
+
+                            if not decoded:
+                                # 如果所有解码都失败,以十六进制形式显示数据
+                                print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                                continue
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                # 根据不同频道处理数据
+                # if 'channel' in data:
+                #     channel = data['channel']
+                #     if channel == 'account' and 'data' in data:
+                #         # 账户信息
+                #         for account in data['data']:
+                #             print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                #     elif channel == 'currentPositions' and 'data' in data:
+                #         # 当前持仓
+                #         for position in data['data']:
+                #             print(f"持仓: {position}")
+                #     elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                #         # 订单信息
+                #         for order in data['data']:
+                #             # 安全访问 'p' 字段,如果不存在则显示 N/A
+                #             price = order.get('p', 'N/A')
+                #             print(f"订单价格: {price}")
+                #     # 其他频道可以按需添加处理逻辑
+
+                # 处理event类型的消息
+                if 'event' in data:
+                    event = data['event']
+                    if event == 'mark_price.update' and 'data' in data:
+                        mark_data = data['data']
+                        print(f"标记价格更新: {mark_data.get('instrument_id', 'N/A')} - {mark_data.get('mark_price', 'N/A')}")
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 161 - 0
testcase/main-hotcoin.py

@@ -0,0 +1,161 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+
+
+async def listen_ibit_futures():
+    uri = f"wss://wcws.hotcoins.cn/"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "tickers", "env": 0, "zip": False, "serialize": False}}
+
+        # sub = {"event": "subscribe", "params": {"biz": "deliver", "type": "tickers", "env": 0, "zip": False, "serialize": False}}
+
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "order_fetch_kline", "granularity": "1min", "env": 0, "zip": False, "serialize": False}}
+
+        # sub = {"event": "subscribe", "params": {"type": "depth", "zip": False, "granularity": 100, "serialize": False}}
+
+        # sub = {"event": "subscribe", "params": {"type": "tickers", "env": 0, "zip": False, "serialize": False}}
+
+        sub = {"event": "subscribe", "params": {"type": "fund_rates", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "subscribe", "params": {"biz": "portal", "type": "orders", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "new_currency", "serialize": False, "env": 0}}
+        # sub = {"event": "subscribe", "params": {"biz": "portal", "type": "condition_orders", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "subscribe", "params": {"biz": "portal", "type": "position", "granularity": "2mode", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "mark_candles", "zip": False, "contractCode": "btcusdt", "serialize": False, "env": 0}}
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "candles", "zip": False, "granularity": "15min", "serialize": False, "env": 0}}
+        sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "fund_rate", "zip": False, "contractCode": "btcusdt", "serialize": False, "env": 0}}
+
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "ticker", "zip": False, "contractCode": "btcusdt", "serialize": False, "env": 0}}
+
+        # sub = {"event": "subscribe",
+        #        "params": {"biz": "perpetual", "type": "order_fetch_kline", "contractCode": "btcusdt", "granularity": "1min", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "subscribe", "params": {"biz": "perpetual", "type": "depth", "contractCode": "btcusdt", "zip": False, "granularity": 100, "serialize": False, "env": 0}}
+        # sub = {"event": "unsubscribe", "params": {"biz": "perpetual", "type": "order_fetch_kline", "granularity": "1min", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "unsubscribe", "params": {"type": "tickers", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "unsubscribe", "params": {"type": "fund_rates", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "unsubscribe", "params": {"type": "fund_rates", "env": 0, "zip": False, "serialize": False}}
+        # sub = {"event": "unsubscribe", "params": {"type": "depth", "zip": False, "granularity": 100, "serialize": False}}
+        # sub = {"event": "subscribe",
+        #        "params": {"biz": "perpetual", "type": "candles", "zip": False, "contractCode": "btcusdt", "granularity": "15min", "serialize": False, "env": 0}}
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = {"event": "ping"}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 检查是否是gzip压缩数据
+                    if message.startswith(b'\x1f\x8b'):
+                        try:
+                            # 解压gzip数据
+                            decompressed_data = gzip.decompress(message)
+                            message = decompressed_data.decode('utf-8')
+                        except Exception as e:
+                            print(f"解压数据失败: {e}")
+                            print(f"原始数据 (前50字节): {message[:50]}")
+                            continue
+                    else:
+                        # 尝试多种编码解码非gzip二进制数据
+                        decoded = False
+                        # 尝试UTF-8解码
+                        try:
+                            message = message.decode('utf-8')
+                            decoded = True
+                        except UnicodeDecodeError:
+                            pass
+
+                        # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                        if not decoded:
+                            try:
+                                message = message.decode('gbk')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                        if not decoded:
+                            # 如果所有解码都失败,以十六进制形式显示数据
+                            print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                            continue
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                # 根据不同频道处理数据
+                if 'channel' in data:
+                    channel = data['channel']
+                    if channel == 'account' and 'data' in data:
+                        # 账户信息
+                        for account in data['data']:
+                            print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                    elif channel == 'currentPositions' and 'data' in data:
+                        # 当前持仓
+                        for position in data['data']:
+                            print(f"持仓: {position}")
+                    elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                        # 订单信息
+                        for order in data['data']:
+                            # 安全访问 'p' 字段,如果不存在则显示 N/A
+                            price = order.get('p', 'N/A')
+                            print(f"订单价格: {price}")
+                    # 其他频道可以按需添加处理逻辑
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 152 - 0
testcase/main-ibit.py

@@ -0,0 +1,152 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+
+
+async def listen_ibit_futures():
+    # 获取当前时间戳(毫秒)
+    n = str(int(round(time.time() * 1000)))
+
+    # APP_TOKEN
+    r = '66e50af7-b5ea-4079-a11e-66348a14f6f1376847'
+
+    # 使用 SHA256 计算签名
+    a = hashlib.sha256(f"ibit_{n}##".encode()).hexdigest()
+
+    # 构造 WebSocket URI
+    # uri = f"wss://prod-ibit-futures-ws-p-group.aka-line-a.com/ws/futures?ts={n}&sign={a}&platform=PC&token={r}"
+    uri = f"wss://prod-ibit-market-server-p-co-p-group.aka-line-a.com/ws/quotation?ts={n}&sign={a}&env=local"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        sub = {
+            "event": "sub",
+            "params": {
+                "channels": [
+                    # "account",
+                    # "currentPositions",
+                    # "followPositions",
+                    # "leadPositions",
+                    # "openOrders",
+                    # "planCloseOrders",
+                    # "planOpenOrders",
+                    # "unsettleProfit"
+                ],
+                "cb_id": 1
+            }
+        }
+
+        sub = {"event": "sub", "params": {"channels": [
+            "gzip_market_all_mark_price",
+            # "gzip_market_all_index_price",
+            # "gzip_market_all_latest_price",
+            # "gzip_market_all_24h_ticker",
+            # "gzip_market_all_position_fee_rate",
+            # "gzip_market_abnormal"
+        ]}}
+
+        # sub = {"event": "sub", "params": {
+        #     "channels": [
+        #         "gzip_market_btcusdt_depth_step0",
+        #         "gzip_market_btcusdt_trade_ticker",
+        #         "gzip_market_btcusdt_kline_1day",
+        #         "gzip_market_btcusdt_ticker",
+        #         "gzip_market_depth_one_price_btcusdt",
+        #         "gzip_market_position_fee_rate_btcusdt"
+        #     ]}}
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = {"method": "ping"}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 检查是否是gzip压缩数据
+                    if message.startswith(b'\x1f\x8b'):
+                        try:
+                            # 解压gzip数据
+                            decompressed_data = gzip.decompress(message)
+                            message = decompressed_data.decode('utf-8')
+                        except Exception as e:
+                            print(f"解压数据失败: {e}")
+                            continue
+                    else:
+                        # 非gzip二进制数据转为字符串
+                        message = message.decode('utf-8')
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    continue
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                # 根据不同频道处理数据
+                if 'channel' in data:
+                    channel = data['channel']
+                    if channel == 'account' and 'data' in data:
+                        # 账户信息
+                        for account in data['data']:
+                            print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                    elif channel == 'currentPositions' and 'data' in data:
+                        # 当前持仓
+                        for position in data['data']:
+                            print(f"持仓: {position}")
+                    elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                        # 订单信息
+                        for order in data['data']:
+                            # 安全访问 'p' 字段,如果不存在则显示 N/A
+                            price = order.get('p', 'N/A')
+                            print(f"订单价格: {price}")
+                    # 其他频道可以按需添加处理逻辑
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 106 - 0
testcase/main-mexc.py

@@ -0,0 +1,106 @@
+import asyncio
+import websockets
+import json
+# 1. 从扩展库导入代理连接函数和代理类
+from websockets_proxy import Proxy, proxy_connect
+
+
+async def listen_mexc_futures():
+    uri = "wss://futures.mexc.com/edge"
+
+    # 2. 定义你的代理服务器地址和端口
+    # 请替换成你自己的代理服务器信息
+    # 支持 http, socks4, socks5
+    proxy_url = "http://127.0.0.1:7899"  # 这是一个HTTP代理的例子
+
+    proxy = Proxy.from_url(proxy_url)
+
+    # 3. 使用 proxy_connect 替代 websockets.connect
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 定义异步心跳函数
+        async def send_subscription():
+
+            # 取消订阅增量盘口列表 (通常在开始新的订阅前,先取消旧的)
+            unsub_tick_list_inc = {"method": "unsub.tick.list.inc", "param": {"timezone": "UTC+8"}}
+            # await websocket.send(json.dumps(unsub_tick_list_inc))
+
+            # 取消订阅全量盘口列表
+            unsub_tick_list = {"method": "unsub.tick.list", "param": {"timezone": "UTC+8"}}
+            # await websocket.send(json.dumps(unsub_tick_list))
+
+            # 订阅所有合约的基础信息 (例如合约名称、精度等)
+            sub_contract = {"method": "sub.contract"}
+            # await websocket.send(json.dumps(sub_contract))
+
+            # 订阅个人用户偏好设置
+            sub_personal_user_preference = {"method": "sub.personal.user.preference"}
+            # await websocket.send(json.dumps(sub_personal_user_preference))
+
+            # 批量订阅多个交易对的最新价(Ticker)信息
+            sub_tick_batch = {"method": "sub.tick.batch", "param": {"timezone": "UTC+8",
+                                                                    "symbols": ["DOGE_USD", "ETH_USDT", "BR_USDT", "SOL_USDT", "PI_USDT", "XRP_USDT", "ORCA_USDT", "TUT_USDT",
+                                                                                "MUBARAK_USDT", "BUBB_USDT", "SIREN_USDT", "MUBARAKAH_USDT", "TAT_USDT", "BANANAS31_USDT",
+                                                                                "SZN_USDT", "YZYSOL_USDT", "DLC_USDT", "FORM_USDT", "SLING_USDT", "NIL_USDT"]}}
+            # await websocket.send(json.dumps(sub_tick_batch))
+
+            # 订阅单个交易对的深度盘口数据 (按价格步长0.1聚合)
+            sub_depth_step = {"method": "sub.depth.step", "param": {"symbol": "DOGE_USD", "step": 0.1}}
+            # await websocket.send(json.dumps(sub_depth_step))
+
+            # 订阅单个交易对的实时成交记录
+            sub_deal = {"method": "sub.deal", "param": {"symbol": "DOGE_USD", "compress": "true"}}
+            # await websocket.send(json.dumps(sub_deal))
+
+            # 订阅单个交易对的资金费率
+            sub_funding_rate = {"method": "sub.funding.rate", "param": {"symbol": "DOGE_USD"}}
+            # await websocket.send(json.dumps(sub_funding_rate))
+
+            # 订阅单个交易对的合理价格 (也叫标记价格,用于计算未实现盈亏)
+            sub_fair_price = {"method": "sub.fair.price", "param": {"symbol": "DOGE_USD"}}
+            await websocket.send(json.dumps(sub_fair_price))
+
+            # 订阅单个交易对的指数价格 (通常是多个主流交易所现货价格的加权平均)
+            sub_index_price = {"method": "sub.index.price", "param": {"symbol": "DOGE_USD"}}
+            # await websocket.send(json.dumps(sub_index_price))
+
+            # 订阅单个交易对的K线数据 (这里订阅的是15分钟K线)
+            sub_kline = {"method": "sub.kline", "param": {"symbol": "DOGE_USD", "interval": "Min15"}}
+            # await websocket.send(json.dumps(sub_kline))
+
+            while True:
+                heartbeat_message = {"method": "ping"}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        asyncio.create_task(send_subscription())
+        while True:
+            try:
+                message = await websocket.recv()
+                print(f"接收到消息: {message}")
+                data = json.loads(message)
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                if 'data' in data and isinstance(data['data'], list) and len(data['data']) > 0:
+                    for deal in data['data']:
+                        print(f"成交价格: {deal['p']}")
+
+            except websockets.exceptions.ConnectionClosed as e:
+                print(f"连接关闭: {e}")
+                break
+            except Exception as e:
+                print(f"发生错误: {e}")
+                break
+
+
+async def main():
+    await listen_mexc_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 152 - 0
testcase/main-trubit.py

@@ -0,0 +1,152 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+
+
+async def listen_ibit_futures():
+    uri = f"wss://future.trubit.com/prod1/market"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        # sub = {"priority": "HIGH", "_csclass": "RefdataRequestEvent", "version": "4.9-20220729", "txId": "TX20250831-150609-554-2", "channel": "PC"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "RefdataUpdateEvent"}
+        # sub = {"priority": "HIGH", "_csclass": "FundingRateRequestEvent", "symbol": "BTCUSDT", "txId": "TX20250831-150609-555-3", "channel": "PC"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "FundingRateUpdateEvent", "subKey": "BTCUSDT"}
+
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "DepthUpdateEvent", "subKey": "BTCUSDT"}
+        # sub = {"priority": "HIGH", "_csclass": "DepthRequestEvent", "key": "BTCUSDT", "txId": "TX20250831-150609-555-6", "channel": "PC"}
+        # sub = {"priority": "HIGH", "_csclass": "PositionTotalRequestEvent", "key": "BTCUSDT", "txId": "TX20250831-150609-555-7", "channel": "PC"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "PositionTotalUpdateEvent", "subKey": "BTCUSDT"}
+        # sub = {"priority": "HIGH", "_csclass": "TradeStatisticsRequestEvent"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "TradeStatisticsUpdateEvent"}
+        # sub = {"fromTimeMillions": 1756623969561, "key": "MEXO_BTCUSDT_15M", "priority": "HIGH", "step": 300, "_csclass": "HistoricalPriceProRequestEvent", "txId": "basic"}
+        # sub = {"priority": "HIGH", "_csclass": "TradeStatisticsRequestEvent"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "TradeStatisticsUpdateEvent"}
+        # sub = {"priority": "HIGH", "_csclass": "IndexRequestEvent", "symbols": ["BTCUSDT", "ETHUSDT"], "txId": "TX20250831-150609-732-5", "channel": "PC"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "IndexUpdateEvent", "subKey": "BTCUSDT"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "IndexUpdateEvent", "subKey": "ETHUSDT"}
+        # sub = {"priority": "HIGH", "_csclass": "MarkRequestEvent", "symbols": ["BTCUSDT", "ETHUSDT"], "txId": "TX20250831-150609-732-4", "channel": "PC"}
+        sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "MarkUpdateEvent", "subKey": "BTCUSDT"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "MarkUpdateEvent", "subKey": "ETHUSDT"}
+        # sub = {"fromIndex": 0, "key": "MEXO_BTCUSDT_15M", "priority": "HIGH", "step": 329, "_csclass": "HistoricalPriceProRequestEvent"}
+        # sub = {"priority": "NORMAL", "_csclass": "RemoteSubscribeEvent", "clazz": "HistoricalPriceProUpdateEvent", "subKey": "MEXO_BTCUSDT_15M"}
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = "ping"
+                await websocket.send(heartbeat_message)
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 检查是否是gzip压缩数据
+                    if message.startswith(b'\x1f\x8b'):
+                        try:
+                            # 解压gzip数据
+                            decompressed_data = gzip.decompress(message)
+                            message = decompressed_data.decode('utf-8')
+                        except Exception as e:
+                            print(f"解压数据失败: {e}")
+                            print(f"原始数据 (前50字节): {message[:50]}")
+                            continue
+                    else:
+                        # 尝试多种编码解码非gzip二进制数据
+                        decoded = False
+                        # 尝试UTF-8解码
+                        try:
+                            message = message.decode('utf-8')
+                            decoded = True
+                        except UnicodeDecodeError:
+                            pass
+
+                        # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                        if not decoded:
+                            try:
+                                message = message.decode('gbk')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                        if not decoded:
+                            # 如果所有解码都失败,以十六进制形式显示数据
+                            print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                            continue
+
+                print(f"接收到消息: {message}")
+                print("====================================================================")
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    # print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                # if 'channel' in data and data['channel'] == 'pong':
+                #     continue
+                #
+                # # 根据不同频道处理数据
+                # if 'channel' in data:
+                #     channel = data['channel']
+                #     if channel == 'account' and 'data' in data:
+                #         # 账户信息
+                #         for account in data['data']:
+                #             print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                #     elif channel == 'currentPositions' and 'data' in data:
+                #         # 当前持仓
+                #         for position in data['data']:
+                #             print(f"持仓: {position}")
+                #     elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                #         # 订单信息
+                #         for order in data['data']:
+                #             # 安全访问 'p' 字段,如果不存在则显示 N/A
+                #             price = order.get('p', 'N/A')
+                #             print(f"订单价格: {price}")
+                #     # 其他频道可以按需添加处理逻辑
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 177 - 0
testcase/main-websea.py

@@ -0,0 +1,177 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+import zlib
+
+
+async def listen_ibit_futures():
+    uri = f"wss://cws.websea.com/ws/realTime?compress=0"
+    # uri = f"wss://api.digifinex.com/ws?access_token=J9oxYwf86pX7kbtwFaUbPzeOYE8HGayG&user_msg=true"
+
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        # sub = {"subs": [{"token": "undefined_pc", "type": 16}], "unSubs": []}
+
+        sub = {"subs": [{"symbol": "BTC-USDT", "type": 8}], "unSubs": []}
+
+        # sub = {"subs": [{"type": 9}], "unSubs": []}
+
+        # sub = {"subs": [{"symbol": "BTC-USDT", "depth": "0.1", "type": 1}], "unSubs": []}
+        # sub = {"subs": [{"symbol": "BTC-USDT", "type": 3}], "unSubs": []}
+        # sub = {"subs": [{"symbol": "BTC-USDT", "time": "30min", "type": 5}], "unSubs": []}
+        # sub = {"subs": [{"symbol": "BTC-USDT", "time": "30min", "type": 14}], "unSubs": []}
+        # sub = {"subs": [{"symbol": "BTC-USDT", "time": "30min", "type": 13}], "unSubs": []}
+
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                # heartbeat_message = {"id": "921848", "method": "server.ping", "params": []}
+                print("==ping==>",  int(time.time()))
+                heartbeat_message = str(int(time.time()))    # {"action":"ping","data":{}}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(10)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 首先尝试直接解码为UTF-8,可能不是压缩数据
+                    try:
+                        message = message.decode('utf-8')
+                    except UnicodeDecodeError:
+                        # 如果直接解码失败,再尝试解压
+                        # 检查是否是gzip压缩数据
+                        if message.startswith(b'\x1f\x8b'):
+                            try:
+                                # 解压gzip数据
+                                decompressed_data = gzip.decompress(message)
+                                message = decompressed_data.decode('utf-8')
+                            except Exception as e:
+                                print(f"gzip解压失败: {e}")
+                                print(f"原始数据 (前100字节): {message[:100].hex()}")
+                                continue
+                        # 检查是否是deflate压缩数据(以78da开头)
+                        elif message.startswith(b'\x78\xda') or message.startswith(b'\x78\x9c') or message.startswith(b'\x78\x01'):
+                            # 尝试多种deflate解压方法
+                            decompressed_success = False
+
+                            # 方法1: 使用zlib.decompress和-wbits参数
+                        if not decompressed_success:
+                            try:
+                                decompressed_data = zlib.decompress(message, -zlib.MAX_WBITS)
+                                message = decompressed_data.decode('utf-8')
+                                decompressed_success = True
+                            except Exception as e:
+                                print(f"deflate解压方法1失败: {e}")
+
+                        # 方法2: 使用zlib.decompress和默认wbits参数
+                        if not decompressed_success:
+                            try:
+                                decompressed_data = zlib.decompress(message)
+                                message = decompressed_data.decode('utf-8')
+                                decompressed_success = True
+                            except Exception as e:
+                                print(f"deflate解压方法2失败: {e}")
+
+                        # 方法3: 使用zlib.decompress和+16参数(gzip格式)
+                        if not decompressed_success:
+                            try:
+                                decompressed_data = zlib.decompress(message, zlib.MAX_WBITS | 16)
+                                message = decompressed_data.decode('utf-8')
+                                decompressed_success = True
+                            except Exception as e:
+                                print(f"deflate解压方法3失败: {e}")
+
+                        # 如果所有方法都失败,打印原始数据
+                        if not decompressed_success:
+                            print(f"deflate解压失败: Error -3 while decompressing data: invalid stored block lengths")
+                            print(f"原始数据 (前100字节): {message[:100].hex()}")
+                            print(f"数据长度: {len(message)}")
+                            continue
+                        else:
+                            # 尝试多种编码解码非gzip二进制数据
+                            decoded = False
+                            # 尝试UTF-8解码
+                            try:
+                                message = message.decode('utf-8')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                            # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                            if not decoded:
+                                try:
+                                    message = message.decode('gbk')
+                                    decoded = True
+                                except UnicodeDecodeError:
+                                    pass
+
+                            if not decoded:
+                                # 如果所有解码都失败,以十六进制形式显示数据
+                                print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                                continue
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                # 处理event类型的消息
+                if 'event' in data:
+                    event = data['event']
+                    if event == 'mark_price.update' and 'data' in data:
+                        mark_data = data['data']
+                        print(f"标记价格更新: {mark_data.get('instrument_id', 'N/A')} - {mark_data.get('mark_price', 'N/A')}")
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 195 - 0
testcase/main-weex.py

@@ -0,0 +1,195 @@
+import asyncio
+import time
+import json
+import hashlib
+import websockets
+from websockets_proxy import Proxy, proxy_connect
+import gzip
+import zlib
+
+
+async def listen_ibit_futures():
+    uri = f"wss://ws-gateway1.janapw.com/api/v1/public/ws?vs=75k71CG8BVgD4Y9k6heo7uB56z6Ed6Xv&compress=1&languageType=1"
+    # uri = f"wss://s-ws-gateway1.janapw.com/api/v1/spot/ws?vs=55d7SYJ88r4iKI9Xa2nv78Q48BWTo65e&compress=1&languageType=1"
+    # 代理服务器地址
+    proxy_url = "http://127.0.0.1:7899"
+    proxy = Proxy.from_url(proxy_url)
+
+    # 使用代理连接
+    async with proxy_connect(uri, proxy=proxy) as websocket:
+        print(f"成功通过代理 {proxy_url} 连接到 {uri}")
+
+        # 发送订阅消息
+        sub = {"event": "ping", "time": "1756025810396"}
+
+        sub = {"event": "subscribe", "channel": "ticker.all.v2"}
+        # sub = {"event": "subscribe", "channel": "ticker.all"}
+
+        # sub = {"event": "subscribe", "channel": "trades.v2.10000001"}
+
+        # sub = {"event": "subscribe", "channel": "fundingRate.v2.10000001"}
+        # sub = {"event": "subscribe", "channel": "depth.v2.10000001.200_0.1"}
+        # sub = {"event": "unsubscribe", "channel": "depth.v2.10000001.200_0.1"}
+
+        # sub = {"id": "phWjnWnbjGbGCKyyhTPCJMiC7eP5PBFc", "cmd": "sub", "topic": "alpha-market-depth-contract-btcusdt-trade", "data": {}}
+
+        # sub = {"id": "YnmB4W4mjiSTdnCPhCfBxHH34WwidKyW", "cmd": "sub", "topic": "alpha-market-ticker", "data": {}}
+
+        await websocket.send(json.dumps(sub))
+
+        print("已发送订阅消息")
+
+        # 定义异步心跳函数
+        async def send_heartbeat():
+            while True:
+                heartbeat_message = {"event": "ping", "time": "1756025810396"}
+                await websocket.send(json.dumps(heartbeat_message))
+                print(f"发送心跳消息: {heartbeat_message}")
+                await asyncio.sleep(30)
+
+        # 启动异步心跳任务
+        heartbeat_task = asyncio.create_task(send_heartbeat())
+
+        try:
+            # 接收消息循环
+            while True:
+                message = await websocket.recv()
+
+                # 处理二进制数据(gzip压缩)
+                if isinstance(message, bytes):
+                    # 首先尝试直接解码为UTF-8,可能不是压缩数据
+                    try:
+                        message = message.decode('utf-8')
+                    except UnicodeDecodeError:
+                        # 如果直接解码失败,再尝试解压
+                        # 检查是否是gzip压缩数据
+                        if message.startswith(b'\x1f\x8b'):
+                            try:
+                                # 解压gzip数据
+                                decompressed_data = gzip.decompress(message)
+                                message = decompressed_data.decode('utf-8')
+                            except Exception as e:
+                                print(f"gzip解压失败: {e}")
+                                print(f"原始数据 (前100字节): {message[:100].hex()}")
+                                continue
+                        # 检查是否是deflate压缩数据(以78da开头)
+                        elif message.startswith(b'\x78\xda') or message.startswith(b'\x78\x9c') or message.startswith(b'\x78\x01'):
+                            # 尝试多种deflate解压方法
+                            decompressed_success = False
+
+                            # 方法1: 使用zlib.decompress和-wbits参数
+                            if not decompressed_success:
+                                try:
+                                    decompressed_data = zlib.decompress(message, -zlib.MAX_WBITS)
+                                    message = decompressed_data.decode('utf-8')
+                                    decompressed_success = True
+                                except Exception as e:
+                                    print(f"deflate解压方法1失败: {e}")
+
+                            # 方法2: 使用zlib.decompress和默认wbits参数
+                            if not decompressed_success:
+                                try:
+                                    decompressed_data = zlib.decompress(message)
+                                    message = decompressed_data.decode('utf-8')
+                                    decompressed_success = True
+                                except Exception as e:
+                                    print(f"deflate解压方法2失败: {e}")
+
+                            # 方法3: 使用zlib.decompress和+16参数(gzip格式)
+                            if not decompressed_success:
+                                try:
+                                    decompressed_data = zlib.decompress(message, zlib.MAX_WBITS | 16)
+                                    message = decompressed_data.decode('utf-8')
+                                    decompressed_success = True
+                                except Exception as e:
+                                    print(f"deflate解压方法3失败: {e}")
+
+                            # 如果所有方法都失败,打印原始数据
+                            if not decompressed_success:
+                                print(f"deflate解压失败: Error -3 while decompressing data: invalid stored block lengths")
+                                print(f"原始数据 (前100字节): {message[:100].hex()}")
+                                print(f"数据长度: {len(message)}")
+                                continue
+                        else:
+                            # 尝试多种编码解码非gzip二进制数据
+                            decoded = False
+                            # 尝试UTF-8解码
+                            try:
+                                message = message.decode('utf-8')
+                                decoded = True
+                            except UnicodeDecodeError:
+                                pass
+
+                            # 如果UTF-8解码失败,尝试其他编码或以十六进制形式显示
+                            if not decoded:
+                                try:
+                                    message = message.decode('gbk')
+                                    decoded = True
+                                except UnicodeDecodeError:
+                                    pass
+
+                            if not decoded:
+                                # 如果所有解码都失败,以十六进制形式显示数据
+                                print(f"无法解码的二进制数据 (前100字节): {message[:100].hex()}")
+                                continue
+
+                print(f"接收到消息: {message}")
+
+                # 解析JSON数据
+                try:
+                    data = json.loads(message)
+                except json.JSONDecodeError as e:
+                    print(f"JSON解析失败: {e}")
+                    print(f"原始消息: {message}")
+                    continue
+
+                if 'channel' in data and data['channel'] == 'pong':
+                    continue
+
+                # 根据不同频道处理数据
+                if 'channel' in data:
+                    channel = data['channel']
+                    if channel == 'account' and 'data' in data:
+                        # 账户信息
+                        for account in data['data']:
+                            print(f"账户余额: {account.get('balance', 'N/A')} {account.get('currency', 'N/A')}")
+                    elif channel == 'currentPositions' and 'data' in data:
+                        # 当前持仓
+                        for position in data['data']:
+                            print(f"持仓: {position}")
+                    elif channel in ['openOrders', 'planCloseOrders', 'planOpenOrders'] and 'data' in data:
+                        # 订单信息
+                        for order in data['data']:
+                            # 安全访问 'p' 字段,如果不存在则显示 N/A
+                            price = order.get('p', 'N/A')
+                            print(f"订单价格: {price}")
+                    # 其他频道可以按需添加处理逻辑
+
+                # 处理event类型的消息
+                if 'event' in data:
+                    event = data['event']
+                    if event == 'mark_price.update' and 'data' in data:
+                        mark_data = data['data']
+                        print(f"标记价格更新: {mark_data.get('instrument_id', 'N/A')} - {mark_data.get('mark_price', 'N/A')}")
+
+        except websockets.exceptions.ConnectionClosed as e:
+            print(f"连接关闭: {e}")
+        except Exception as e:
+            print(f"发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+        finally:
+            # 取消心跳任务
+            heartbeat_task.cancel()
+            try:
+                await heartbeat_task
+            except asyncio.CancelledError:
+                pass
+
+
+async def main():
+    await listen_ibit_futures()
+
+
+if __name__ == "__main__":
+    asyncio.run(main())